
Linux Firewall mit nftables 2026 – Modernes Setup
Linux Firewall mit nftables 2026: Modernes Setup mit Sets, Maps, NAT und Docker-Integration – inkl. Migration von iptables, Performance-Werten und Praxis-Befehl
Der Standard-Logging-Treiber von Docker ist seit Jahren json-file – und er hat ein strukturelles Problem: Er schreibt unbegrenzt. Ohne max-size und max-file wächst eine einzelne Container-Logdatei so lange, bis die Partition voll ist. Bei einem typischen Node.js- oder Python-Service mit Request-Logging sind 20 MB pro Tag und Container keine Seltenheit, bei einem geschwätzigen Java-Service mit Log4j sind 200 MB pro Tag realistisch.
Rechnen wir das hoch: 50 Container × 20 MB/Tag ergeben 1 GB pro Tag, also rund 365 GB pro Jahr. Auf einem VPS mit 160 GB NVMe ist die Platte nach fünf Monaten voll – und dann startet der Container nicht mehr, weil Docker die Logdatei nicht mehr schreiben kann. Genau dieses Szenario ist 2026 immer noch einer der häufigsten Gründe für nächtliche Pager-Alarme.
Dazu kommt das zweite Problem: docker logs funktioniert nur lokal und nur pro Container. Sobald du einen Request über vier Microservices verfolgen willst, stehst du vor vier SSH-Sessions und suchst manuell nach einer Correlation-ID. Bei einem Incident mit 15 Minuten Downtime kostet das schnell mehr als ein ganzer Monat Hosting.
Die Lösung ist ein zentraler Log-Stack, der Logs sammelt, indexiert, durchsuchbar macht und Alarme auslöst. 2026 hat sich dafür der Stack aus Grafana Loki, Promtail (bzw. dem Nachfolger Grafana Alloy) und Grafana als De-facto-Standard etabliert – vor allem, weil er deutlich günstiger zu betreiben ist als ELK.
Der entscheidende Unterschied liegt im Index. Elasticsearch indexiert jedes Wort jeder Logzeile – das ermöglicht Volltextsuche, kostet aber massiv Speicher und RAM. Loki indexiert ausschließlich die Labels (also Container-Name, Service, Namespace, Host) und speichert den Log-Inhalt als komprimierte Chunks. Die Suche läuft dann per Brute-Force-Scan über die passenden Chunks.
In der Praxis bedeutet das: Ein Elasticsearch-Cluster für 50 GB Logs pro Monat braucht typischerweise 16–32 GB RAM und 500 GB bis 1 TB Storage. Loki mit gleicher Logmenge läuft auf 4–8 GB RAM und 100–150 GB Storage, weil die Chunks mit Snappy bzw. Zstd komprimiert werden und im Schnitt eine Kompressionsrate von 8:1 bis 12:1 erreicht wird.
| Kriterium | Grafana Loki | Elasticsearch / ELK |
|---|---|---|
| Index-Strategie | Nur Labels | Volltext (invertierter Index) |
| RAM-Bedarf (50 GB/Monat) | 4–8 GB | 16–32 GB |
| Storage-Bedarf | 100–150 GB | 500 GB–1 TB |
| Abfragesprache | LogQL | KQL / Lucene DSL |
| Betriebsaufwand | Niedrig (Single Binary möglich) | Hoch (Cluster, Shards, ILM) |
| Kosten VPS/Monat | ab ca. 8 € | ab ca. 40 € |
Der Nachteil: Loki ist bei ungezielten Volltextsuchen über Terabytes langsamer. Für den typischen DevOps-Alltag – „zeig mir alle Errors von Service X in den letzten 30 Minuten“ – ist das irrelevant. Wer wirklich Volltext-Forensik über Jahre braucht, fährt mit ELK oder OpenSearch besser.
Der Stack besteht aus drei Komponenten mit klar getrennten Aufgaben. Promtail läuft als Agent auf jedem Docker-Host, liest die Logdateien aus /var/lib/docker/containers/ und schickt sie mit Labels an Loki. Loki nimmt die Streams entgegen, komprimiert sie, speichert sie als Chunks und hält einen kleinen Label-Index. Grafana ist die Abfrage- und Visualisierungsschicht.
Wichtige Ports für die Firewall-Konfiguration:
Seit 2024 empfiehlt Grafana offiziell Grafana Alloy als Nachfolger von Promtail, da Promtail in den Maintenance-Modus übergegangen ist. Die Konfiguration ist bei Alloy in River-Syntax statt YAML geschrieben. Promtail funktioniert 2026 weiterhin einwandfrei und ist für Docker-only-Setups einfacher – wir zeigen deshalb beide Wege, konzentrieren uns aber auf Promtail, weil die Docker-Service-Discovery dort deutlich kompakter ist.
Das folgende Setup läuft auf einem VPS mit 4 GB RAM problemlos. Wichtig ist, dass Loki und Promtail im selben Docker-Netzwerk liegen und die Logs über ein benanntes Volume persistent bleiben.
services:
loki:
image: grafana/loki:3.4.1
container_name: loki
restart: unless-stopped
command: -config.file=/etc/loki/loki-config.yaml
volumes:
- ./loki-config.yaml:/etc/loki/loki-config.yaml:ro
- loki-data:/loki
ports:
- "127.0.0.1:3100:3100"
networks: [monitoring]
promtail:
image: grafana/promtail:3.4.1
container_name: promtail
restart: unless-stopped
command: -config.file=/etc/promtail/promtail-config.yaml
volumes:
- ./promtail-config.yaml:/etc/promtail/promtail-config.yaml:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
networks: [monitoring]
grafana:
image: grafana/grafana:11.5.0
container_name: grafana
restart: unless-stopped
environment:
GF_SECURITY_ADMIN_PASSWORD: "ChAng3M3-Str0ng!"
GF_USERS_ALLOW_SIGN_UP: "false"
volumes:
- grafana-data:/var/lib/grafana
ports:
- "3000:3000"
networks: [monitoring]
volumes:
loki-data:
grafana-data:
networks:
monitoring:
driver: bridge
Start mit docker compose up -d. Danach erreichst du Grafana unter http://SERVER-IP:3000. Der Docker-Socket wird read-only gemountet – das ist Pflicht, denn ein beschreibbarer Socket-Mount ist gleichbedeutend mit Root-Zugriff auf den Host.
Achte darauf, dass Loki nur auf 127.0.0.1 lauscht. Wenn du Loki öffentlich erreichbar machst, kann jeder mit der Push-API deinen Storage fluten – ein klassischer Denial-of-Wallet-Angriff bei Cloud-Storage-Backends.
Die wichtigste Einstellung ist die Retention. Ohne sie wächst Loki unbegrenzt. Für die meisten Setups sind 30 Tage ein guter Kompromiss zwischen Debugging-Fähigkeit und Kosten.
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
log_level: warn
common:
instance_addr: 127.0.0.1
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
limits_config:
retention_period: 720h
ingestion_rate_mb: 10
ingestion_burst_size_mb: 20
max_query_series: 5000
reject_old_samples: true
reject_old_samples_max_age: 168h
compactor:
working_directory: /loki/compactor
retention_enabled: true
delete_request_store: filesystem
compaction_interval: 10m
retention_period: 720h entspricht 30 Tagen. Der Compactor löscht abgelaufene Chunks alle 10 Minuten. Ohne aktivierten Compactor wird die Retention-Einstellung schlicht ignoriert – ein häufiger Konfigurationsfehler.
Für produktive Setups mit mehr als 100 GB Logvolumen lohnt sich Object Storage. S3-kompatibel sind Hetzner Object Storage (1 TB ab ca. 5 €/Monat), Backblaze B2 (6 $/TB/Monat) oder Wasabi (6,99 $/TB/Monat). Der Wechsel erfolgt über einen s3-Block in der storage_config mit endpoint, bucketnames, access_key_id und secret_access_key.
Promtail kann Container automatisch über den Docker-Socket entdecken. Das ist der eleganteste Weg, weil neue Container ohne Neustart des Agents erfasst werden.
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
relabel_configs:
- source_labels: ['__meta_docker_container_name']
regex: '/(.*)'
target_label: container
- source_labels: ['__meta_docker_container_log_stream']
target_label: stream
- source_labels: ['__meta_docker_container_label_com_docker_compose_service']
target_label: service
- source_labels: ['__meta_docker_container_label_com_docker_compose_project']
target_label: project
pipeline_stages:
- docker: {}
- match:
selector: '{service="nginx"}'
stages:
- regex:
expression: '^(?P<ip>\S+) - - \[(?P<ts>[^\]]+)\] "(?P<method>\S+) (?P<path>\S+)'
- labels:
method:
path:
Der docker: {}-Stage ist entscheidend: Er parst das JSON-Format von Docker und extrahiert den eigentlichen Log-Text sowie den Zeitstempel. Ohne ihn landen die Logs mit dem JSON-Wrapper in Loki und sind praktisch unlesbar.
Die labels-Direktive im Regex-Stage erzeugt neue Labels aus den extrahierten Feldern. Achtung: Jedes zusätzliche Label mit hoher Kardinalität (z. B. Request-IDs oder User-IDs) sprengt den Loki-Index. Halte Labels bei unter 100.000 Kombinationen.
LogQL kombiniert Label-Filter (schnell, indexiert) mit Line-Filtern (langsam, aber flexibel). Die Grundregel: So viele Filter wie möglich in den Label-Selektor, so wenige wie nötig in die Pipeline.
# Alle Logs eines Service
{service="api"}
# Nur Fehler
{service="api"} |= "ERROR"
# Fehler ausschließen
{service="api"} != "healthcheck"
# Regex-Filter
{service="nginx"} |~ "5[0-9]{2} "
# JSON parsen und filtern
{service="api"} | json | level="error" | duration_ms > 1000
# Fehlerrate pro Minute
sum(count_over_time({service="api"} |= "ERROR" [1m]))
# Top 5 Container nach Logvolumen
topk(5, sum by (container) (rate({job="docker"}[5m])))
# Langsamste Requests
{service="api"} | json | unwrap duration_ms | quantile_over_time(0.99, {} [5m])
Der unwrap-Operator ist der wichtigste für Metriken aus Logs: Er wandelt einen numerischen Wert aus einer Logzeile in eine Zeitreihe um. Damit baust du Latenz-Perzentile direkt aus Logs, ohne dass die Anwendung Prometheus-Metriken exportieren muss.
Für die Fehlersuche über Services hinweg nutzt du eine Correlation-ID. Voraussetzung ist, dass alle Services sie loggen:
{job="docker"} |= "req-8f3a9c21"
Diese Query liefert alle Logzeilen aller Container, die diese Request-ID enthalten – sortiert nach Zeit. Aus 15 Minuten manueller Suche werden 3 Sekunden.
Nach dem Login unter http://SERVER-IP:3000 (Standard: admin / das gesetzte Passwort) fügst du unter Connections → Data Sources eine Loki-Quelle mit der URL http://loki:3100 hinzu. Da beide im selben Docker-Netzwerk liegen, funktioniert der Container-Name als Hostname.
Für ein solides Basis-Dashboard brauchst du vier Panels:
sum by (service) (rate({job="docker"}[5m])) als Time Seriessum(count_over_time({job="docker"} |= "ERROR" [1m])) als Stat{service="api"}topk(5, sum by (container) (count_over_time({job="docker"} |= "ERROR" [1h])))Für Alerting definierst du in Grafana eine Alert Rule auf Basis einer LogQL-Metrik-Query. Ein praxistauglicher Alert: Fehlerrate über 10 Events pro Minute für 5 Minuten.
sum(count_over_time({job="docker"} |= "ERROR" [1m])) > 10
Als Contact Point reicht ein Webhook auf Slack oder Discord, oder Mail über einen SMTP-Relay. Grafana 11 bringt Alertmanager direkt mit – ein separater Prometheus-Alertmanager ist für diesen Stack nicht nötig. Setze die group_wait-Zeit auf 30 s und repeat_interval auf 4 h, sonst wird dein Team in der Nacht zugespammt.
Logs allein reichen nicht. Wenn ein Container 100 % CPU zieht, willst du das sehen, bevor die Logs vollaufen. Ergänze den Stack um drei Komponenten:
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.50.0
container_name: cadvisor
restart: unless-stopped
privileged: true
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
ports:
- "127.0.0.1:8080:8080"
networks: [monitoring]
node-exporter:
image: prom/node-exporter:v1.8.2
container_name: node-exporter
restart: unless-stopped
pid: host
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--path.rootfs=/rootfs'
ports:
- "127.0.0.1:9100:9100"
networks: [monitoring]
cAdvisor braucht privileged: true – das ist ein Sicherheitskompromiss. Wer das vermeiden will, nutzt stattdessen den Docker-Daemon-Metriken-Endpunkt oder setzt spezifischere Capabilities. Für ein dediziertes Monitoring-VPS ist privileged vertretbar, auf einem Shared-Host nicht.
Sinnvolle Alerts auf Metrik-Basis: Container-Restart-Loop (rate(container_restart_count[10m]) > 0), RAM-Auslastung über 90 % für 10 Minuten, Disk-Auslastung über 85 %, und Host-Load über der doppelten CPU-Kernzahl.
Die realistische Verbrauchsmessung eines Stacks mit 30 Containern und rund 2 GB Logs pro Tag:
| Komponente | RAM | CPU | Disk (30 Tage) |
|---|---|---|---|
| Loki | 300–600 MB | 0,2–0,5 vCPU | ca. 7 GB (komprimiert) |
| Promtail | 40–80 MB | 0,05 vCPU | – |
| Grafana | 150–250 MB | 0,1 vCPU | 1–2 GB |
| Prometheus | 400–800 MB | 0,3 vCPU | 5–10 GB |
| cAdvisor | 80–150 MB | 0,1 vCPU | – |
| Gesamt | ca. 1,2–2 GB | ca. 1 vCPU | ca. 15–20 GB |
Damit reicht ein VPS mit 4 GB RAM und 80 GB NVMe. Passende Angebote (Stand 2026, Preise gerundet):
| Anbieter | Modell | Specs | Preis/Monat |
|---|---|---|---|
| Hetzner | CX22 | 2 vCPU, 4 GB, 40 GB | ca. 4,35 € |
| Hetzner | CX32 | 4 vCPU, 8 GB, 80 GB | ca. 7,60 € |
| Netcup | VPS 2000 G11 | 4 vCPU, 8 GB, 160 GB | ca. 8,99 € |
| Contabo | VPS S | 4 vCPU, 8 GB, 200 GB | ca. 5,99 € |
| IONOS | VPS Linux L | 4 vCPU, 8 GB, 160 GB | ca. 12,00 € |
Für die 40-GB-Variante bei Hetzner reicht der Platz für etwa drei Monate Retention. Wer ein Jahr aufbewahren will, nimmt Object Storage dazu: 1 TB bei Hetzner kostet rund 5 €/Monat inklusive Traffic. Damit liegt der komplette Monitoring-Stack bei unter 15 € monatlich – im Vergleich zu einem Elasticsearch-Cluster mit 40–80 € eine klare Ersparnis.
Der Docker-Socket ist der kritischste Punkt im gesamten Stack. Ein beschreibbarer Mount erlaubt das Starten privilegierter Container und damit Root-Zugriff auf den Host. Setze immer :ro und ziehe zusätzlich den Socket-Proxy tecnativa/docker-socket-proxy in Betracht, der nur die benötigten API-Endpunkte freigibt.
Weitere Absicherungen:
GF_USERS_ALLOW_SIGN_UP=false setzen127.0.0.1 binden, Zugriff per SSH-Tunnel oder Reverse-Proxy mit Basic Authauth_enabled: true in Loki, sobald mehr als ein Mandant Logs schickt{"log-driver":"json-file","log-opts":{"max-size":"10m","max-file":"3"}} in /etc/docker/daemon.jsonDie doppelte Absicherung über Docker-Log-Rotation ist wichtig: Wenn Loki ausfällt, laufen die Container trotzdem weiter und die Logs bleiben lokal begrenzt. Ohne diese Rotation füllt sich die Platte innerhalb von Stunden.
Für die Aufbewahrung personenbezogener Daten gilt die DSGVO: Logs mit IP-Adressen oder User-IDs sollten nach 7 bis 14 Tagen gelöscht werden, sofern keine berechtigte Aufbewahrungspflicht besteht. Setze dafür pro Stream unterschiedliche Retention-Periods über limits_config und Stream-Matcher.
1. „entry too far behind“ beim Push. Loki lehnt Logs ab, die älter als reject_old_samples_max_age sind. Ursache ist meist ein falsch geparster Zeitstempel. Prüfe, ob der docker: {}-Stage aktiv ist.
2. Keine Logs in Grafana, aber Promtail läuft. Prüfe die Position-Datei unter /tmp/positions.yaml. Wurde sie gelöscht oder ist der Container neu gestartet, liest Promtail alles neu ein – oder überspringt bereits gelesene Dateien. Bei einem Wechsel von json-file auf einen anderen Log-Treiber findet Promtail gar nichts mehr.
3. Loki startet nicht mit „too many open files“. Erhöhe LimitNOFILE in der systemd-Unit oder setze ulimits im Compose-File auf nofile: 65536.
4. Grafana zeigt „Data source connected, but no labels“. Meist ein Netzwerkproblem – Grafana erreicht http://loki:3100 nicht, weil der Container in einem anderen Docker-Netzwerk hängt. Prüfe mit docker exec grafana wget -qO- http://loki:3100/ready.
5. Hohe Kardinalität. Wenn Loki plötzlich 8 GB RAM zieht und Queries langsam werden, hast du zu viele Labels. Prüfe mit curl -s http://localhost:3100/loki/api/v1/labels und entferne alles, was pro Request einen neuen Wert annimmt.
Ein nützlicher Health-Check für den Betrieb: curl -s http://localhost:3100/ready sollte ready zurückgeben, und curl -s http://localhost:3100/metrics | grep loki_ingester_streams zeigt die Anzahl aktiver Streams. Über 10.000 Streams auf einem kleinen VPS sind ein Warnsignal.
Für ein reines Docker-Setup ist Promtail 2026 weiterhin die einfachere Wahl, weil die Konfiguration kompakt in YAML bleibt und die Docker-Service-Discovery direkt eingebaut ist. Grafana Alloy ist der offizielle Nachfolger und lohnt sich, wenn du Logs, Metriken und Traces in einem einzigen Agenten bündeln willst. Alloy nutzt die River-Syntax, die sich deutlich von Promtail-YAML unterscheidet, und kann bestehende Promtail-Konfigurationen per promtail.convert-Befehl importieren. Für neue Setups mit mehr als drei Hosts würde ich direkt auf Alloy setzen, für Einzelhost-Setups bleibt Promtail pragmatischer.
Rechne mit der Rohmenge geteilt durch 8 bis 12. Bei 2 GB Logs pro Tag sind das 60 GB Rohdaten pro Monat und etwa 5 bis 7,5 GB komprimierte Chunks in Loki. Dazu kommt der Label-Index mit rund 1 bis 2 Prozent der Rohmenge. Für 30 Tage bei 2 GB/Tag brauchst du also etwa 8 bis 10 GB Storage. Bei 10 GB Logs pro Tag entsprechend 40 bis 50 GB. Object Storage ist ab etwa 50 GB Logvolumen pro Monat günstiger als Block Storage.
Ja. Loki wird als einzelne Binary ausgeliefert und lässt sich per systemd betreiben. Der Vorteil im Container ist die einfachere Aktualisierung und die isolierte Konfiguration. Wenn du Loki nativ betreibst, achte auf ausreichende nofile-Limits und darauf, dass der Nutzer Schreibrechte auf /loki hat. Für die meisten Setups ist Docker Compose aber der schnellere Weg, weil Loki, Grafana und Prometheus dann gemeinsam im gleichen Netzwerk laufen und du keine Ports nach außen öffnen musst.
Auf einem Hetzner CX32 mit 4 vCPU, 8 GB RAM und 80 GB NVMe liegst du bei rund 7,60 € monatlich für den Server. Ein Domainname kostet etwa 1 € pro Monat, TLS über Let's Encrypt ist kostenlos. Mit Object Storage für ein Jahr Retention kommen rund 5 € pro TB dazu. In Summe sind das 8 bis 15 € monatlich – deutlich weniger als ein vergleichbarer Elasticsearch-Cluster, der bei gleicher Logmenge mindestens 40 € an Serverkosten verursacht.
Zum einen über Object Storage mit aktivierter Versionierung, zum anderen über regelmäßige Snapshots des VPS. Der Loki-Index ist reproduzierbar, die Chunks nicht – verlierst du die Chunks, sind die Logs weg. Aktiviere deshalb entweder S3-Backend mit Versionierung oder erstelle mindestens täglich einen Snapshot des loki-data-Volumes. Für den Compactor gilt: delete_request_store muss auf denselben Storage-Typ zeigen wie die Chunks, sonst schlägt das Löschen abgelaufener Daten fehl und die Retention greift nicht.