DSGVO-konforme Website Analytics mit nginx, VictoriaLogs und Grafana
ayedo Redaktion 13 Minuten Lesezeit

DSGVO-konforme Website Analytics mit nginx, VictoriaLogs und Grafana

Website-Analytics ohne Drittanbieter-Tracking: Mit nginx JSON-Logs, VictoriaLogs und Grafana bauen Sie eine DSGVO-konforme Analytics-Lösung, die komplett auf Ihrer eigenen Infrastruktur läuft.

DSGVO-konforme Website Analytics mit nginx, VictoriaLogs und Grafana

TL;DR

  • Google Analytics, Matomo Cloud und andere SaaS-Tracking-Tools sind aus DSGVO-Sicht problematisch: Daten fließen an Dritte, Cookie-Banner nerven Besucher, und die rechtliche Grauzone bleibt.
  • nginx Access Logs enthalten bereits alle relevanten Daten für Website Analytics – Seitenaufrufe, Besucher-IPs, Referrer, User Agents, Response Times.
  • Mit strukturiertem JSON-Logging, VictoriaLogs als Log-Backend und Grafana als Dashboard entstehen vollwertige Website-Statistiken ohne ein einziges Tracking-Pixel.
  • Alle Daten bleiben auf Ihrer eigenen Infrastruktur. Kein Cookie-Banner nötig. Keine Drittanbieter-Abhängigkeit.
  • VictoriaLogs, Grafana und Kubernetes stehen als Managed Service von ayedo zur Verfügung – inklusive Bring-your-own-App für Ihre Web-Applikationen.

Das Problem mit klassischen Analytics-Tools

Die meisten Website-Betreiber greifen zu Google Analytics oder vergleichbaren SaaS-Lösungen. Das bringt drei fundamentale Probleme:

  1. DSGVO-Compliance: Daten werden an Drittanbieter übertragen, oft in die USA. Einwilligungen sind erforderlich, Cookie-Banner unvermeidlich.
  2. Datenkontrolle: Sie geben Besucherdaten an externe Dienstleister weiter – und verlieren die Hoheit darüber.
  3. Abhängigkeit: Preisänderungen, API-Änderungen oder Abkündigungen treffen Sie unvorbereitet.

Dabei enthält jeder Webserver bereits alle Daten, die Sie für aussagekräftige Website-Statistiken brauchen: die Access Logs.


Die Architektur

Die Lösung besteht aus drei Komponenten:

nginx (JSON Access Logs)
    |
    v
VictoriaLogs (Log-Aggregation & Indexierung)
    |
    v
Grafana (Dashboards & Visualisierung)

nginx liefert strukturierte JSON-Logs mit allen relevanten Request-Informationen. VictoriaLogs sammelt, indexiert und speichert die Logs. Grafana visualisiert die Daten als Dashboard.

In einer Kubernetes-Umgebung werden die Container-Logs automatisch durch einen Log-Collector (z.B. Vector, Fluent Bit oder den VictoriaLogs Agent) an VictoriaLogs weitergeleitet. Es ist kein zusätzlicher Sidecar oder Log-Shipper im Application Pod erforderlich.


Schritt 1: nginx JSON-Logging konfigurieren

Standard-nginx-Logs im combined-Format sind schwer zu parsen. Strukturierte JSON-Logs lassen sich dagegen automatisch von VictoriaLogs verarbeiten.

Hauptkonfiguration (nginx.conf)

Definieren Sie ein JSON Log-Format im http-Block:

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format json_access escape=json
        '{'
        '"msg":"$request_method $request_uri $status $body_bytes_sent $request_time",'
        '"remote_addr":"$remote_addr",'
        '"http_x_forwarded_for":"$http_x_forwarded_for",'
        '"request_method":"$request_method",'
        '"request_uri":"$request_uri",'
        '"server_protocol":"$server_protocol",'
        '"status":$status,'
        '"body_bytes_sent":$body_bytes_sent,'
        '"http_referer":"$http_referer",'
        '"http_user_agent":"$http_user_agent",'
        '"request_time":$request_time,'
        '"host":"$host"'
        '}';

    access_log /var/log/nginx/access.log json_access;

    sendfile        on;
    keepalive_timeout  65;

    include /etc/nginx/conf.d/*.conf;
}

Wichtig: Das msg-Feld wird von VictoriaLogs als _msg interpretiert – das ist die menschenlesbare Log-Zeile, die in Log-Explorern angezeigt wird.

Dockerfile

Wenn Sie nginx in einem Container betreiben, ersetzen Sie die Standard-Konfiguration:

FROM nginx:alpine
COPY nginx.conf /etc/nginx/nginx.conf
COPY default.conf /etc/nginx/conf.d/default.conf
COPY /your-static-site /usr/share/nginx/html

Schritt 2: VictoriaLogs einrichten

VictoriaLogs ist ein hochperformantes Log-Backend, das JSON-Logs automatisch parst und die Felder indexiert. Es ist deutlich ressourcenschonender als vergleichbare Lösungen wie Elasticsearch oder Loki.

Kubernetes Deployment

In einem Kubernetes-Cluster deployen Sie VictoriaLogs z.B. via Helm:

helm repo add victoriametrics https://victoriametrics.github.io/helm-charts/
helm install victoria-logs victoriametrics/victoria-logs-single \
  --namespace victoria-logs \
  --create-namespace

Log Collection

Konfigurieren Sie Ihren Log-Collector (z.B. Vector), um Container-Logs an VictoriaLogs weiterzuleiten. Die JSON-Felder werden automatisch als log.*-Felder indexiert:

  • log.request_uri – die aufgerufene URL
  • log.status – HTTP Status Code
  • log.http_user_agent – Browser/Bot-Identifikation
  • log.http_x_forwarded_for – echte Client-IP (bei Reverse Proxy)
  • log.http_referer – Referrer-URL
  • log.request_time – Antwortzeit in Sekunden
  • log.body_bytes_sent – übertragene Bytes

Schritt 3: Grafana Dashboard erstellen

Mit VictoriaLogs als Datasource in Grafana können Sie ein vollständiges Analytics-Dashboard bauen.

Grafana Dashboard mit VictoriaLogs Website-Statistiken

Hier die wichtigsten Panels und ihre LogsQL-Queries:

Basis-Filter

Alle Queries beginnen mit einem Basis-Filter, der die Logs auf Ihre Applikation einschränkt und Kubernetes Health Checks ausschließt:

kubernetes.pod_namespace:"my-namespace" AND kubernetes.pod_name:my-app* AND NOT log.http_user_agent:kube*

Total Requests

Zählt alle eingehenden Requests (exklusive Health Checks):

kubernetes.pod_namespace:"my-namespace" AND kubernetes.pod_name:my-app* AND NOT log.http_user_agent:kube*
| stats count() total_requests

Unique Visitors

Zählt eindeutige Besucher-IPs über den X-Forwarded-For-Header:

kubernetes.pod_namespace:"my-namespace" AND kubernetes.pod_name:my-app* AND NOT log.http_user_agent:kube* AND log.http_x_forwarded_for:~".+"
| stats by (log.http_x_forwarded_for) count()
| stats count() unique_visitors

Der Filter log.http_x_forwarded_for:~".+" schließt internen Traffic ohne echte Client-IP aus.

Page Views (ohne statische Assets)

Filtert CSS, JavaScript, Bilder und Fonts heraus – nur echte Seitenaufrufe:

kubernetes.pod_namespace:"my-namespace" AND kubernetes.pod_name:my-app* AND NOT log.http_user_agent:kube* AND NOT log.request_uri:~"\\.(css|js|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|mp4|webm|webp)$"
| stats count() page_views

Status Codes Over Time

Visualisiert HTTP Status Codes als gestapeltes Balkendiagramm:

kubernetes.pod_namespace:"my-namespace" AND kubernetes.pod_name:my-app* AND NOT log.http_user_agent:kube* AND log.status:*
| stats by (log.status) count() hits

Verwenden Sie {{log.status}} als Legend Format im Grafana Panel.

Top Pages

Die meistbesuchten Seiten als Tabelle:

kubernetes.pod_namespace:"my-namespace" AND kubernetes.pod_name:my-app* AND NOT log.http_user_agent:kube* AND NOT log.request_uri:~"\\.(css|js|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|mp4|webm|webp)$"
| stats by (log.request_uri) count() hits
| sort by (hits) desc
| limit 25

Top Referrers

Woher kommen Ihre Besucher?

kubernetes.pod_namespace:"my-namespace" AND kubernetes.pod_name:my-app* AND NOT log.http_user_agent:kube* AND log.http_referer:~".+"
| stats by (log.http_referer) count() hits
| sort by (hits) desc
| limit 20

Error Rate (4xx/5xx)

Fehlerhafte Requests überwachen:

kubernetes.pod_namespace:"my-namespace" AND kubernetes.pod_name:my-app* AND NOT log.http_user_agent:kube* AND log.status:~"[45].."
| stats count() errors

Average Response Time

Performance im Blick behalten:

kubernetes.pod_namespace:"my-namespace" AND kubernetes.pod_name:my-app* AND NOT log.http_user_agent:kube*
| stats avg(log.request_time) avg_response_time

Fertiges Dashboard (Copy & Paste)

Das komplette Dashboard als Grafana JSON – importieren Sie es über Dashboards > Import > Paste JSON. Ersetzen Sie YOUR_VICTORIALOGS_DATASOURCE_UID durch die UID Ihrer VictoriaLogs Datasource und passen Sie die Kubernetes-Filter (my-namespace, my-app) an Ihre Umgebung an.

Dashboard JSON anzeigen
{
  "title": "Website Analytics",
  "tags": ["nginx", "analytics", "victorialogs"],
  "timezone": "browser",
  "editable": true,
  "time": { "from": "now-24h", "to": "now" },
  "refresh": "5m",
  "templating": {
    "list": [
      {
        "name": "namespace",
        "type": "custom",
        "query": "my-namespace",
        "current": { "text": "my-namespace", "value": "my-namespace" },
        "hide": 0
      },
      {
        "name": "app",
        "type": "custom",
        "query": "my-app",
        "current": { "text": "my-app", "value": "my-app" },
        "hide": 0
      }
    ]
  },
  "panels": [
    {
      "collapsed": false,
      "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 },
      "title": "Overview",
      "type": "row"
    },
    {
      "title": "Total Requests",
      "type": "stat",
      "datasource": { "type": "victoriametrics-logs-datasource", "uid": "YOUR_VICTORIALOGS_DATASOURCE_UID" },
      "gridPos": { "h": 5, "w": 4, "x": 0, "y": 1 },
      "fieldConfig": {
        "defaults": {
          "color": { "mode": "thresholds" },
          "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] },
          "unit": "short"
        }
      },
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "reduceOptions": { "calcs": ["sum"], "fields": "", "values": false }
      },
      "targets": [{
        "expr": "kubernetes.pod_namespace:\"$namespace\" AND kubernetes.pod_name:$app* AND NOT log.http_user_agent:kube*\n| stats count() total_requests",
        "queryType": "stats",
        "refId": "A"
      }]
    },
    {
      "title": "Unique Visitors (by IP)",
      "type": "stat",
      "datasource": { "type": "victoriametrics-logs-datasource", "uid": "YOUR_VICTORIALOGS_DATASOURCE_UID" },
      "gridPos": { "h": 5, "w": 4, "x": 4, "y": 1 },
      "fieldConfig": {
        "defaults": {
          "color": { "mode": "thresholds" },
          "thresholds": { "mode": "absolute", "steps": [{ "color": "blue", "value": null }] },
          "unit": "short"
        }
      },
      "options": {
        "colorMode": "value",
        "graphMode": "none",
        "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }
      },
      "targets": [{
        "expr": "kubernetes.pod_namespace:\"$namespace\" AND kubernetes.pod_name:$app* AND NOT log.http_user_agent:kube* AND log.http_x_forwarded_for:~\".+\"\n| stats by (log.http_x_forwarded_for) count()\n| stats count() unique_visitors",
        "queryType": "stats",
        "refId": "A"
      }]
    },
    {
      "title": "Page Views (HTML only)",
      "type": "stat",
      "datasource": { "type": "victoriametrics-logs-datasource", "uid": "YOUR_VICTORIALOGS_DATASOURCE_UID" },
      "gridPos": { "h": 5, "w": 4, "x": 8, "y": 1 },
      "fieldConfig": {
        "defaults": {
          "color": { "mode": "thresholds" },
          "thresholds": { "mode": "absolute", "steps": [{ "color": "purple", "value": null }] },
          "unit": "short"
        }
      },
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "reduceOptions": { "calcs": ["sum"], "fields": "", "values": false }
      },
      "targets": [{
        "expr": "kubernetes.pod_namespace:\"$namespace\" AND kubernetes.pod_name:$app* AND NOT log.http_user_agent:kube* AND NOT log.request_uri:~\"\\\\.(css|js|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|mp4|webm|webp)$\"\n| stats count() page_views",
        "queryType": "stats",
        "refId": "A"
      }]
    },
    {
      "title": "Error Rate (4xx/5xx)",
      "type": "stat",
      "datasource": { "type": "victoriametrics-logs-datasource", "uid": "YOUR_VICTORIALOGS_DATASOURCE_UID" },
      "gridPos": { "h": 5, "w": 4, "x": 12, "y": 1 },
      "fieldConfig": {
        "defaults": {
          "color": { "mode": "thresholds" },
          "thresholds": { "mode": "absolute", "steps": [
            { "color": "green", "value": null },
            { "color": "yellow", "value": 10 },
            { "color": "red", "value": 50 }
          ]},
          "unit": "short"
        }
      },
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "reduceOptions": { "calcs": ["sum"], "fields": "", "values": false }
      },
      "targets": [{
        "expr": "kubernetes.pod_namespace:\"$namespace\" AND kubernetes.pod_name:$app* AND NOT log.http_user_agent:kube* AND log.status:~\"[45]..\"\n| stats count() errors",
        "queryType": "stats",
        "refId": "A"
      }]
    },
    {
      "title": "Avg Response Time",
      "type": "stat",
      "datasource": { "type": "victoriametrics-logs-datasource", "uid": "YOUR_VICTORIALOGS_DATASOURCE_UID" },
      "gridPos": { "h": 5, "w": 4, "x": 16, "y": 1 },
      "fieldConfig": {
        "defaults": {
          "color": { "mode": "thresholds" },
          "thresholds": { "mode": "absolute", "steps": [
            { "color": "green", "value": null },
            { "color": "yellow", "value": 0.5 },
            { "color": "red", "value": 1 }
          ]},
          "unit": "s"
        }
      },
      "options": {
        "colorMode": "value",
        "graphMode": "none",
        "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }
      },
      "targets": [{
        "expr": "kubernetes.pod_namespace:\"$namespace\" AND kubernetes.pod_name:$app* AND NOT log.http_user_agent:kube*\n| stats avg(log.request_time) avg_response_time",
        "queryType": "stats",
        "refId": "A"
      }]
    },
    {
      "title": "Total Bandwidth",
      "type": "stat",
      "datasource": { "type": "victoriametrics-logs-datasource", "uid": "YOUR_VICTORIALOGS_DATASOURCE_UID" },
      "gridPos": { "h": 5, "w": 4, "x": 20, "y": 1 },
      "fieldConfig": {
        "defaults": {
          "color": { "mode": "thresholds" },
          "thresholds": { "mode": "absolute", "steps": [{ "color": "orange", "value": null }] },
          "unit": "decbytes"
        }
      },
      "options": {
        "colorMode": "value",
        "graphMode": "none",
        "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }
      },
      "targets": [{
        "expr": "kubernetes.pod_namespace:\"$namespace\" AND kubernetes.pod_name:$app* AND NOT log.http_user_agent:kube*\n| stats sum(log.body_bytes_sent) total_bytes",
        "queryType": "stats",
        "refId": "A"
      }]
    },
    {
      "collapsed": false,
      "gridPos": { "h": 1, "w": 24, "x": 0, "y": 6 },
      "title": "Traffic Over Time",
      "type": "row"
    },
    {
      "title": "Requests per Minute",
      "type": "timeseries",
      "datasource": { "type": "victoriametrics-logs-datasource", "uid": "YOUR_VICTORIALOGS_DATASOURCE_UID" },
      "gridPos": { "h": 8, "w": 12, "x": 0, "y": 7 },
      "fieldConfig": {
        "defaults": {
          "color": { "mode": "palette-classic" },
          "custom": { "drawStyle": "line", "fillOpacity": 20, "lineInterpolation": "smooth", "lineWidth": 2, "showPoints": "never", "spanNulls": false, "stacking": { "mode": "none" } },
          "unit": "reqps"
        }
      },
      "options": {
        "legend": { "displayMode": "list", "placement": "bottom" },
        "tooltip": { "mode": "multi", "sort": "desc" }
      },
      "targets": [{
        "expr": "kubernetes.pod_namespace:\"$namespace\" AND kubernetes.pod_name:$app* AND NOT log.http_user_agent:kube*\n| stats count() requests",
        "legendFormat": "Requests/s",
        "queryType": "statsRange",
        "refId": "A"
      }]
    },
    {
      "title": "Status Codes Over Time",
      "type": "timeseries",
      "datasource": { "type": "victoriametrics-logs-datasource", "uid": "YOUR_VICTORIALOGS_DATASOURCE_UID" },
      "gridPos": { "h": 8, "w": 12, "x": 12, "y": 7 },
      "fieldConfig": {
        "defaults": {
          "color": { "mode": "palette-classic" },
          "custom": { "drawStyle": "bars", "fillOpacity": 80, "lineWidth": 1, "stacking": { "mode": "normal" } },
          "unit": "short"
        },
        "overrides": [
          { "matcher": { "id": "byRegexp", "options": "2.." }, "properties": [{ "id": "color", "value": { "fixedColor": "green", "mode": "fixed" } }] },
          { "matcher": { "id": "byRegexp", "options": "3.." }, "properties": [{ "id": "color", "value": { "fixedColor": "blue", "mode": "fixed" } }] },
          { "matcher": { "id": "byRegexp", "options": "4.." }, "properties": [{ "id": "color", "value": { "fixedColor": "yellow", "mode": "fixed" } }] },
          { "matcher": { "id": "byRegexp", "options": "5.." }, "properties": [{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }] }
        ]
      },
      "options": {
        "legend": { "displayMode": "list", "placement": "bottom" },
        "tooltip": { "mode": "multi", "sort": "desc" }
      },
      "targets": [{
        "expr": "kubernetes.pod_namespace:\"$namespace\" AND kubernetes.pod_name:$app* AND NOT log.http_user_agent:kube* AND log.status:*\n| stats by (log.status) count() hits",
        "legendFormat": "{{log.status}}",
        "queryType": "statsRange",
        "refId": "A"
      }]
    },
    {
      "collapsed": false,
      "gridPos": { "h": 1, "w": 24, "x": 0, "y": 15 },
      "title": "Content Analysis",
      "type": "row"
    },
    {
      "title": "Top Pages",
      "type": "table",
      "datasource": { "type": "victoriametrics-logs-datasource", "uid": "YOUR_VICTORIALOGS_DATASOURCE_UID" },
      "gridPos": { "h": 10, "w": 12, "x": 0, "y": 16 },
      "fieldConfig": {
        "overrides": [{ "matcher": { "id": "byName", "options": "Time" }, "properties": [{ "id": "custom.hidden", "value": true }] }]
      },
      "options": { "cellHeight": "sm", "showHeader": true },
      "targets": [{
        "expr": "kubernetes.pod_namespace:\"$namespace\" AND kubernetes.pod_name:$app* AND NOT log.http_user_agent:kube* AND NOT log.request_uri:~\"\\\\.(css|js|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|mp4|webm|webp)$\"\n| stats by (log.request_uri) count() hits\n| sort by (hits) desc\n| limit 25",
        "queryType": "instant",
        "refId": "A"
      }],
      "transformations": [{ "id": "extractFields", "options": { "format": "auto", "keepTime": true, "replace": true, "source": "labels" } }]
    },
    {
      "title": "Top Referrers",
      "type": "table",
      "datasource": { "type": "victoriametrics-logs-datasource", "uid": "YOUR_VICTORIALOGS_DATASOURCE_UID" },
      "gridPos": { "h": 10, "w": 12, "x": 12, "y": 16 },
      "fieldConfig": {
        "overrides": [{ "matcher": { "id": "byName", "options": "Time" }, "properties": [{ "id": "custom.hidden", "value": true }] }]
      },
      "options": { "cellHeight": "sm", "showHeader": true },
      "targets": [{
        "expr": "kubernetes.pod_namespace:\"$namespace\" AND kubernetes.pod_name:$app* AND NOT log.http_user_agent:kube* AND log.http_referer:~\".+\"\n| stats by (log.http_referer) count() hits\n| sort by (hits) desc\n| limit 20",
        "queryType": "instant",
        "refId": "A"
      }],
      "transformations": [{ "id": "extractFields", "options": { "format": "auto", "keepTime": true, "replace": true, "source": "labels" } }]
    },
    {
      "collapsed": false,
      "gridPos": { "h": 1, "w": 24, "x": 0, "y": 26 },
      "title": "Visitors & User Agents",
      "type": "row"
    },
    {
      "title": "Unique Visitors Over Time",
      "type": "timeseries",
      "datasource": { "type": "victoriametrics-logs-datasource", "uid": "YOUR_VICTORIALOGS_DATASOURCE_UID" },
      "gridPos": { "h": 8, "w": 12, "x": 0, "y": 27 },
      "fieldConfig": {
        "defaults": {
          "color": { "fixedColor": "blue", "mode": "fixed" },
          "custom": { "drawStyle": "bars", "fillOpacity": 60, "lineWidth": 1, "stacking": { "mode": "none" } },
          "unit": "short"
        }
      },
      "options": {
        "legend": { "displayMode": "list", "placement": "bottom" },
        "tooltip": { "mode": "single" }
      },
      "targets": [{
        "expr": "kubernetes.pod_namespace:\"$namespace\" AND kubernetes.pod_name:$app* AND NOT log.http_user_agent:kube* AND log.http_x_forwarded_for:~\".+\"\n| stats count_uniq(log.http_x_forwarded_for) unique_visitors",
        "legendFormat": "Unique Visitors",
        "queryType": "statsRange",
        "refId": "A"
      }]
    },
    {
      "title": "Top User Agents",
      "type": "table",
      "datasource": { "type": "victoriametrics-logs-datasource", "uid": "YOUR_VICTORIALOGS_DATASOURCE_UID" },
      "gridPos": { "h": 8, "w": 12, "x": 12, "y": 27 },
      "fieldConfig": {
        "overrides": [{ "matcher": { "id": "byName", "options": "Time" }, "properties": [{ "id": "custom.hidden", "value": true }] }]
      },
      "options": { "cellHeight": "sm", "showHeader": true },
      "targets": [{
        "expr": "kubernetes.pod_namespace:\"$namespace\" AND kubernetes.pod_name:$app* AND NOT log.http_user_agent:kube*\n| stats by (log.http_user_agent) count() hits\n| sort by (hits) desc\n| limit 15",
        "queryType": "instant",
        "refId": "A"
      }],
      "transformations": [{ "id": "extractFields", "options": { "format": "auto", "keepTime": true, "replace": true, "source": "labels" } }]
    },
    {
      "collapsed": false,
      "gridPos": { "h": 1, "w": 24, "x": 0, "y": 35 },
      "title": "Errors & Performance",
      "type": "row"
    },
    {
      "title": "404 Not Found Pages",
      "type": "table",
      "datasource": { "type": "victoriametrics-logs-datasource", "uid": "YOUR_VICTORIALOGS_DATASOURCE_UID" },
      "gridPos": { "h": 8, "w": 12, "x": 0, "y": 36 },
      "fieldConfig": {
        "overrides": [{ "matcher": { "id": "byName", "options": "Time" }, "properties": [{ "id": "custom.hidden", "value": true }] }]
      },
      "options": { "cellHeight": "sm", "showHeader": true },
      "targets": [{
        "expr": "kubernetes.pod_namespace:\"$namespace\" AND kubernetes.pod_name:$app* AND log.status:404\n| stats by (log.request_uri) count() hits\n| sort by (hits) desc\n| limit 20",
        "queryType": "instant",
        "refId": "A"
      }],
      "transformations": [{ "id": "extractFields", "options": { "format": "auto", "keepTime": true, "replace": true, "source": "labels" } }]
    },
    {
      "title": "Response Time Over Time",
      "type": "timeseries",
      "datasource": { "type": "victoriametrics-logs-datasource", "uid": "YOUR_VICTORIALOGS_DATASOURCE_UID" },
      "gridPos": { "h": 8, "w": 12, "x": 12, "y": 36 },
      "fieldConfig": {
        "defaults": {
          "color": { "mode": "palette-classic" },
          "custom": { "drawStyle": "line", "fillOpacity": 10, "lineInterpolation": "smooth", "lineWidth": 2, "showPoints": "never", "spanNulls": false, "stacking": { "mode": "none" } },
          "unit": "s"
        }
      },
      "options": {
        "legend": { "displayMode": "list", "placement": "bottom" },
        "tooltip": { "mode": "multi", "sort": "desc" }
      },
      "targets": [{
        "expr": "kubernetes.pod_namespace:\"$namespace\" AND kubernetes.pod_name:$app* AND NOT log.http_user_agent:kube*\n| stats avg(log.request_time) avg_time, max(log.request_time) max_time",
        "legendFormat": "{{__name__}}",
        "queryType": "statsRange",
        "refId": "A"
      }]
    },
    {
      "collapsed": false,
      "gridPos": { "h": 1, "w": 24, "x": 0, "y": 44 },
      "title": "Raw Access Logs",
      "type": "row"
    },
    {
      "title": "Access Logs",
      "type": "logs",
      "datasource": { "type": "victoriametrics-logs-datasource", "uid": "YOUR_VICTORIALOGS_DATASOURCE_UID" },
      "gridPos": { "h": 12, "w": 24, "x": 0, "y": 45 },
      "options": {
        "dedupStrategy": "none",
        "enableLogDetails": true,
        "showLabels": true,
        "showTime": true,
        "sortOrder": "Descending",
        "wrapLogMessage": false
      },
      "targets": [{
        "expr": "kubernetes.pod_namespace:\"$namespace\" AND kubernetes.pod_name:$app*\n| sort by (_time)\n| limit 200",
        "queryType": "instant",
        "refId": "A"
      }]
    }
  ]
}

DSGVO-Konformität

Warum ist diese Lösung DSGVO-konform?

  1. Keine Drittanbieter: Alle Daten bleiben auf Ihrer eigenen Infrastruktur. Kein Datentransfer an Google, Meta oder andere Analytics-Anbieter.
  2. Kein Cookie-Banner nötig: Es werden keine Cookies gesetzt und kein clientseitiges Tracking durchgeführt. Die Datenerfassung basiert ausschließlich auf serverseitigen Access Logs.
  3. Datenminimierung: Sie erfassen nur die Daten, die nginx ohnehin protokolliert. Keine Fingerprinting-Techniken, keine Session-Tracking-Scripts.
  4. Volle Kontrolle: Retention Policies, Zugriffskontrolle und Löschfristen definieren Sie selbst.
  5. Berechtigtes Interesse: Die Verarbeitung von Access Logs zur Gewährleistung der Netzwerksicherheit und Performance-Optimierung ist durch Art. 6 Abs. 1 lit. f DSGVO gedeckt.

Empfehlung: Anonymisieren oder kürzen Sie die IP-Adressen in den Logs, wenn Sie auch die letzten rechtlichen Graubereiche ausschließen möchten. Dies kann direkt in der nginx-Konfiguration oder durch eine VictoriaLogs Retention Policy erfolgen.


Vorteile gegenüber SaaS Analytics

Kriterium SaaS Analytics (z.B. GA) nginx + VictoriaLogs + Grafana
DSGVO Cookie-Banner + Einwilligung Kein Banner nötig
Datenspeicherort USA / Drittanbieter Eigene Infrastruktur
Kosten Oft nutzungsabhängig Fixkosten (Infrastruktur)
Anpassbarkeit Eingeschränkt Volle Kontrolle über Queries
Performance-Daten Eingeschränkt Response Times, Error Rates
Bot-Erkennung Integriert Über User-Agent Filter
Real-Time Minuten Verzögerung Sekunden

Managed Service von ayedo

Sie möchten diese Lösung nutzen, ohne den gesamten Stack selbst zu betreiben? ayedo bietet alle Komponenten als Managed Service:

  • Kubernetes – Vollständig gemanagte Cluster auf Public Cloud, Private Cloud oder On-Premise Infrastruktur
  • VictoriaLogs – Log-Aggregation mit automatischer Retention und Hochverfügbarkeit
  • Grafana – Dashboarding und Alerting mit SSO-Integration
  • Bring-your-own-App – Deployen Sie Ihre Web-Applikationen (nginx, Node.js, etc.) auf gemanagte Kubernetes-Cluster mit integriertem Logging

Der gesamte Observability-Stack wird von ayedo betrieben, überwacht und aktualisiert. Sie konzentrieren sich auf Ihre Applikation – wir kümmern uns um die Infrastruktur.

Kontaktieren Sie uns für eine individuelle Beratung.


ayedo betreibt Kubernetes-basierte Infrastruktur für Unternehmen, die Wert auf digitale Souveränität, Compliance und operative Exzellenz legen. Mehr erfahren

Ähnliche Artikel

AWS RDS vs. MariaDB

Datenbanken konsumieren oder selbst beherrschen AWS RDS und MariaDB stehen nicht für konkurrierende …

20.05.2026