Docker omzeilt UFW: 4 geteste oplossingen voor je VPS

10 min leestijd·Matthieu|

Docker manipuleert iptables rechtstreeks en negeert UFW-regels. Je containerpoorten zijn blootgesteld aan het internet, zelfs met ufw deny actief. Hier zijn vier oplossingen met hun afwegingen, elk geverifieerd door een scan vanaf een externe host.

Je UFW-firewall liegt tegen je. Als je Docker draait op een VPS met UFW ingeschakeld, staat elke gepubliceerde containerpoort wagenwijd open op het internet. ufw deny 8080 uitvoeren doet niets. Docker omzeilt UFW volledig.

Deze tutorial toont het probleem in actie en doorloopt vier oplossingen met verschillende afwegingen. Elke oplossing bevat een verificatiestap: de poort scannen vanaf een externe host om te bevestigen dat deze daadwerkelijk geblokkeerd is. Niet alleen ufw status controleren.

Deze handleiding werkt op Ubuntu 24.04 en Debian 12. Beide gebruiken standaard de iptables-compatibiliteitslaag, dus dezelfde fixes zijn van toepassing. Debian 12 gebruikt nftables als standaard backend, maar UFW en Docker communiceren via de iptables-interface, die transparant naar nftables mapt.

Vereisten: Een VPS met Docker geïnstalleerd, UFW ingeschakeld en SSH-toegang. Een tweede machine (je laptop of een andere server) voor externe portscans.

Waarom omzeilt Docker de UFW-firewallregels?

Docker schrijft iptables-regels in de nat- en filter-tabellen om verkeer naar containers te routeren. UFW beheert alleen de INPUT-keten. Wanneer een pakket arriveert voor een gepubliceerde containerpoort, leiden Dockers NAT-regels het om via de FORWARD-keten voordat UFW's INPUT-regels het kunnen zien. Het pakket bereikt UFW nooit. Dit betekent dat ufw deny geen enkel effect heeft op door Docker gepubliceerde poorten.

Hier is de pakketstroom voor een verzoek aan poort 8080 op je VPS, waar een container is gepubliceerd met -p 8080:80:

  1. Het pakket arriveert op de netwerkinterface
  2. Het gaat de PREROUTING-keten van de nat-tabel in
  3. Dockers DNAT-regel herschrijft de bestemming naar het container-IP (bijv. 172.17.0.2:80)
  4. Het pakket gaat naar de FORWARD-keten van de filter-tabel (niet INPUT)
  5. Dockers DOCKER-keten accepteert het doorgestuurde pakket
  6. Het pakket bereikt de container

UFW ziet het nooit omdat UFW alleen de INPUT-keten bewaakt. Het pakket neemt het FORWARD-pad.

Dit is geen bug. Docker heeft iptables-controle nodig om containernetwerking te laten werken. Maar het creëert een serieus beveiligingslek als je ervan uitging dat UFW je server beschermde.

Hoe verifieer ik dat Docker mijn UFW-firewall omzeilt?

Voordat je een fix toepast, bekijk het probleem zelf. Start een testcontainer die HTTP serveert op poort 8080:

docker run -d --name ufw-test -p 8080:80 nginx:alpine

Controleer dat UFW actief is en inkomend verkeer standaard weigert:

sudo ufw status verbose

Je zou Default: deny (incoming) in de uitvoer moeten zien. Weiger nu expliciet poort 8080:

sudo ufw deny 8080

Controleer of UFW de poort als geblokkeerd toont:

sudo ufw status | grep 8080
8080                       DENY        Anywhere
8080 (v6)                  DENY        Anywhere (v6)

UFW meldt de poort als geweigerd. Test nu vanaf je lokale machine (niet de server):

curl -s -o /dev/null -w "%{http_code}" http://YOUR_SERVER_IP:8080
200

Het antwoord is 200. De container is volledig toegankelijk vanaf het internet ondanks dat UFW de poort weigert. Als je nmap geïnstalleerd hebt op je lokale machine:

nmap -p 8080 YOUR_SERVER_IP
PORT     STATE SERVICE
8080/tcp open  http-proxy

De poort is open. UFW beschermt hem niet.

Verwijder de deny-regel (je past hierna een echte fix toe):

sudo ufw delete deny 8080

Laat de testcontainer draaien. Je gebruikt hem om elke oplossing te verifiëren.

Welke Docker UFW-fix moet ik gebruiken?

Er bestaan vier oplossingen. Elk heeft andere afwegingen. Kies degene die bij je setup past.

Oplossing Netwerkimpact Overleeft herstart Compose-compatibel Complexiteit Geschikt voor
DOCKER-USER-keten Geen Ja Ja Gemiddeld Volledige controle, meerdere publieke poorten
ufw-docker-tool Geen Ja Ja Laag Geautomatiseerd beheer, dynamische containers
iptables=false Verbreekt inter-containernetwerking, geen internet vanuit containers Ja Ja Laag Geïsoleerde enkele containers (zeldzaam)
Bind op 127.0.0.1 Geen Ja Ja Laag Services achter een reverse proxy

Snelle beslissing:

  1. Staan al je containers achter een reverse proxy (Nginx, Traefik, Caddy)? Gebruik 127.0.0.1-binding. Dit is het eenvoudigst.
  2. Heb je containers nodig die publiek toegankelijk zijn op specifieke poorten? Gebruik de DOCKER-USER-keten voor handmatige controle of ufw-docker voor geautomatiseerd beheer.
  3. Draai je containers die nooit netwerktoegang mogen hebben? Gebruik iptables=false, maar begrijp de afwegingen.

Hoe fix ik Docker dat UFW omzeilt met de DOCKER-USER-keten?

De DOCKER-USER-keten is een tijdelijke aanduiding die Docker leeg laat voor beheerders. Regels die je hier toevoegt, worden geëvalueerd vóór Dockers eigen doorstuurregels. Door UFW-integratieregels in deze keten in te voegen via /etc/ufw/after.rules, laat je Docker-verkeer door UFW gaan.

Deze methode geeft je volledige controle. Het werkt met zowel docker run als Docker Compose. Het overleeft herstarts omdat de regels worden geladen wanneer UFW start.

Open /etc/ufw/after.rules:

sudo cp /etc/ufw/after.rules /etc/ufw/after.rules.bak
sudo nano /etc/ufw/after.rules

Voeg het volgende blok toe aan het einde van het bestand, na de bestaande COMMIT-regel:

# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:ufw-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward

-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j RETURN
-A DOCKER-USER -m conntrack --ctstate INVALID -j DROP
-A DOCKER-USER -i docker0 -o docker0 -j ACCEPT

-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -j ufw-docker-logging-deny -m conntrack --ctstate NEW -d 10.0.0.0/8
-A DOCKER-USER -j ufw-docker-logging-deny -m conntrack --ctstate NEW -d 172.16.0.0/12
-A DOCKER-USER -j ufw-docker-logging-deny -m conntrack --ctstate NEW -d 192.168.0.0/16

-A DOCKER-USER -j RETURN

-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw-docker-logging-deny -j DROP

COMMIT
# END UFW AND DOCKER

Wat dit blok doet:

  • -A DOCKER-USER -j ufw-user-forward: stuurt Docker-verkeer eerst door UFW's doorstuurregels
  • conntrack --ctstate RELATED,ESTABLISHED: staat retourverkeer voor bestaande verbindingen toe (houdt bestaande verbindingen in stand na regelwijzigingen)
  • conntrack --ctstate INVALID: verwerpt misvormde pakketten
  • -i docker0 -o docker0: staat container-naar-container-communicatie op hetzelfde bridge-netwerk toe
  • De -j RETURN -s-regels staan verkeer toe vanuit privésubnetten
  • Nieuwe verbindingen (ctstate NEW) naar Docker-subnetten van buitenaf worden gelogd en verworpen
  • De loggingregel beperkt tot 3 berichten per minuut om logoverstroming te voorkomen

Herlaad UFW om de nieuwe regels toe te passen:

sudo ufw reload

Verifieer dat de regels zijn geladen:

sudo iptables -L DOCKER-USER -n -v

Je zou je nieuwe regels in de ketenuitvoer moeten zien. Als de keten alleen een standaard RETURN-regel toont, is het after.rules-blok niet correct geladen. Controleer de bestandssyntax.

Een specifieke containerpoort toestaan via UFW

Met de DOCKER-USER-keten actief zijn alle containerpoorten standaard geblokkeerd voor externe toegang. Om een specifieke poort toe te staan, moet je het container-IP en de containerpoort refereren, niet de hostpoort. De reden: Dockers DNAT-regel in de PREROUTING-keten herschrijft de bestemming voordat het pakket de FORWARD-keten bereikt. Op het moment dat je regel wordt geëvalueerd, is de bestemming het interne containeradres.

Zoek eerst het container-IP:

docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ufw-test
172.17.0.2

Sta dan verkeer naar die container toe:

sudo ufw route allow proto tcp from any to 172.17.0.2 port 80

Verifieer dat UFW de route-regel toont:

sudo ufw status | grep "ALLOW FWD"
172.17.0.2 80/tcp          ALLOW FWD   Anywhere

Opmerking: omdat de regel een container-IP target dat kan veranderen bij herstart, gebruik het ufw-docker-tool (volgende sectie) voor automatische IP-tracking. De handmatige DOCKER-USER-aanpak is beter wanneer je de container-IPs zelf beheert (statische IPs in Docker Compose of wanneer je regelupdates script).

Verificatie vanaf een externe host

Test vanaf je lokale machine de toegestane poort:

curl -s -o /dev/null -w "%{http_code}" http://YOUR_SERVER_IP:8080
200

De poort is toegankelijk omdat je expliciet doorsturen naar de container hebt toegestaan. Test nu een niet-toegestane poort (bijvoorbeeld als je een container op poort 9090 had):

nmap -p 9090 YOUR_SERVER_IP
PORT     STATE    SERVICE
9090/tcp filtered unknown

filtered betekent dat de firewall pakketten stil verwerpt. De DOCKER-USER-keten werkt.

Hoe gebruik ik het ufw-docker-tool om container-firewallregels te beheren?

Het chaifeng/ufw-docker-tool automatiseert de DOCKER-USER-keten-setup en biedt commando's om containerpoorten toe te staan of te weigeren. Het wijzigt /etc/ufw/after.rules voor je en voegt een CLI toe voor het beheren van regels per container.

Installatie:

sudo wget -O /usr/local/bin/ufw-docker \
  https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
sudo chmod +x /usr/local/bin/ufw-docker

Bekijk het script voordat je het uitvoert. Het is een shellscript dat je kunt lezen met cat /usr/local/bin/ufw-docker.

Voer de installer uit om /etc/ufw/after.rules te patchen:

sudo ufw-docker install

Dit voegt DOCKER-USER-ketenregels toe vergelijkbaar met de vorige sectie, inclusief conntrack-gebaseerde filtering en logging. Het patcht ook after6.rules voor IPv6. Herlaad UFW:

sudo ufw reload

Verifieer de installatie:

sudo ufw-docker check

Containerpoorten beheren

Externe toegang tot poort 80 van een container genaamd web toestaan:

sudo ufw-docker allow web 80/tcp

Regels voor een container tonen:

sudo ufw-docker list web

Een regel verwijderen:

sudo ufw-docker delete allow web 80/tcp

Alle Docker-firewallregels tonen:

sudo ufw-docker status

Docker Compose-voorbeeld met ufw-docker

services:
  web:
    image: nginx:alpine
    container_name: web
    ports:
      - "8080:80"
    restart: unless-stopped

Start de stack en sta dan de poort toe:

docker compose up -d
sudo ufw-docker allow web 80/tcp

Opmerking: het ufw-docker-commando refereert de containerpoort (80), niet de hostpoort (8080). Het tool lost de mapping automatisch op.

Alternatief: ufw-docker-automated

Het shinebayar-g/ufw-docker-automated-project kiest een andere aanpak. Het draait als Docker-container zelf, luistert naar Docker API-events en maakt automatisch UFW-regels aan wanneer containers starten of stoppen. Dit lost een beperking van het ufw-docker-tool op: wanneer een container herstart en een nieuw IP krijgt, wordt de oude regel ongeldig.

Met ufw-docker-automated voeg je labels toe aan je containers:

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    labels:
      - "UFW_MANAGED=TRUE"
    restart: unless-stopped

Het tool bewaakt containers met het UFW_MANAGED=TRUE-label en maakt automatisch overeenkomstige UFW-regels aan. Het ondersteunt ook UFW_ALLOW_FROM om toegang te beperken tot specifieke IPs of CIDR-bereiken.

Verificatie vanaf een externe host

Na het toestaan van een poort met ufw-docker, bevestig vanaf je lokale machine:

curl -s -o /dev/null -w "%{http_code}" http://YOUR_SERVER_IP:8080
200

Test een niet-vermelde poort om te bevestigen dat deze geblokkeerd is:

nmap -p 9090 YOUR_SERVER_IP
PORT     STATE    SERVICE
9090/tcp filtered unknown

Wat gebeurt er als ik Dockers iptables-beheer uitschakel?

"iptables": false instellen in Dockers daemon-configuratie stopt Docker met het aanmaken van iptables-regels. Dit is de eenvoudigste fix maar heeft de ernstigste bijwerkingen.

Wat niet meer werkt:

  • Containers kunnen geen verbinding maken met het internet (geen masquerading-regels)
  • Container-naar-container-communicatie over verschillende netwerken stopt
  • Poortpublicatie (-p) stopt volledig. Je moet alle doorstuurregels zelf beheren.

Deze aanpak is alleen geschikt als je geïsoleerde containers draait die geen internettoegang nodig hebben en je alle netwerking handmatig regelt.

Bewerk of maak de Docker daemon-configuratie:

sudo nano /etc/docker/daemon.json
{
  "iptables": false
}

Als het bestand al inhoud heeft, voeg de "iptables": false-sleutel toe aan het bestaande JSON-object. Maak geen tweede JSON-object.

Herstart Docker:

sudo systemctl restart docker

Verifieer dat Docker geen iptables-regels aanmaakt:

sudo iptables -L DOCKER -n 2>/dev/null

Met Docker 28.x kan de DOCKER-keten nog bestaan maar zou alleen een DROP-regel moeten bevatten in plaats van de gebruikelijke doorstuurregels. Geen ACCEPT-regels betekent dat Docker geen verkeer naar containers routeert.

Verificatie vanaf een externe host

Start de testcontainer opnieuw (deze is gestopt bij de Docker-herstart):

docker start ufw-test

Vanaf je lokale machine:

nmap -p 8080 YOUR_SERVER_IP
PORT     STATE  SERVICE
8080/tcp closed http-proxy

closed (niet filtered) omdat Docker niet langer de NAT-regels aanmaakt om verkeer door te sturen. De poort luistert niet vanuit extern perspectief.

Om terug te draaien, verwijder "iptables": false uit daemon.json en herstart Docker:

sudo systemctl restart docker

Hoe bind ik Docker Compose-poorten alleen aan localhost?

Voor containers achter een reverse proxy (Nginx, Traefik, Caddy) is de eenvoudigste fix om poorten nooit op 0.0.0.0 te publiceren. Bind ze in plaats daarvan aan 127.0.0.1. De reverse proxy maakt verbinding met de container via localhost. Extern verkeer bereikt de reverse proxy op poorten 80/443, die UFW normaal controleert.

Dit vereist geen iptables-wijzigingen, geen extra tools en werkt op elk besturingssysteem.

Wijzig in je docker-compose.yml de poortbinding:

services:
  app:
    image: your-app:latest
    ports:
      - "127.0.0.1:8080:80"
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    restart: unless-stopped

De app-service bindt alleen aan 127.0.0.1:8080. Deze is niet toegankelijk van buiten de server. De nginx-service bindt aan 0.0.0.0:80 en 0.0.0.0:443 (de standaard wanneer geen IP is opgegeven), die je met UFW controleert.

Voor docker run, de equivalente syntax:

docker run -d --name app -p 127.0.0.1:8080:80 your-app:latest

Binding verifiëren

Op de server, bevestig dat de poort alleen aan localhost is gebonden:

ss -tlnp | grep 8080
LISTEN 0      4096      127.0.0.1:8080      0.0.0.0:*    users:(("docker-proxy",pid=12345,fd=4))

Let goed op: het luisteradres is 127.0.0.1:8080, niet 0.0.0.0:8080. Dit betekent dat alleen lokale verbindingen worden geaccepteerd.

Verificatie vanaf een externe host

Vanaf je lokale machine:

nmap -p 8080 YOUR_SERVER_IP
PORT     STATE  SERVICE
8080/tcp closed http-proxy

De poort is gesloten voor extern verkeer. Je reverse proxy regelt de publieke toegang op poorten 80 en 443, die UFW normaal beschermt.

Dit is de aanbevolen aanpak voor de meeste VPS-setups waar je webapplicaties achter een reverse proxy draait.

Probleemoplossing

Regels verdwijnen na Docker-herstart

Als je de DOCKER-USER-keten-aanpak hebt gebruikt en regels verdwijnen bij Docker-herstart, controleer dat je regels in /etc/ufw/after.rules staan en niet handmatig zijn toegevoegd met iptables-commando's. Handmatige iptables-regels overleven geen serviceherstarts. Het after.rules-bestand wordt elke keer geladen wanneer UFW start.

sudo ufw reload
sudo iptables -L DOCKER-USER -n -v

Container kan DNS niet resolven na toepassing van DOCKER-USER-regels

Het after.rules-blok bevat een conntrack --ctstate RELATED,ESTABLISHED-regel die DNS-antwoordverkeer terug naar containers toestaat. Als DNS-resolutie faalt in containers, verifieer dat de conntrack-regel is geladen:

sudo iptables -L DOCKER-USER -n -v | grep "RELATED,ESTABLISHED"

Als deze ontbreekt, controleer de syntax van je after.rules-blok en herlaad UFW.

ufw-docker-commando's falen met "container not found"

Het ufw-docker allow-commando vereist dat de container draait. Het resolvet de containernaam naar een IP-adres. Als de container gestopt is, faalt het commando. Start de container eerst, voeg dan de regel toe.

Debian 12 nftables-compatibiliteit

Debian 12 gebruikt nftables als firewall-backend, maar UFW en Docker gebruiken de iptables-compatibiliteitslaag (iptables-nft). De oplossingen in deze handleiding werken identiek op Debian 12 en Ubuntu 24.04. Verifieer dat je het nft-backend gebruikt:

sudo iptables --version
iptables v1.8.10 (nf_tables)

Het (nf_tables)-achtervoegsel bevestigt dat de iptables-nft-compatibiliteitslaag actief is. Alle DOCKER-USER-ketenregels werken via deze laag.

Logs controleren op geblokkeerd verkeer

Als een verbinding onverwacht geblokkeerd is na toepassing van de DOCKER-USER-keten-fix, controleer de UFW Docker-logs:

sudo journalctl -k | grep "UFW DOCKER BLOCK"

De logvermeldingen tonen het bron-IP en de bestemmingspoort van geblokkeerde pakketten.

Testresources opruimen

Verwijder de testcontainer wanneer je klaar bent:

docker rm -f ufw-test

Als je een UFW deny-regel hebt toegevoegd tijdens het testen, verwijder deze:

sudo ufw delete deny 8080

Copyright 2026 Virtua.Cloud. Alle rechten voorbehouden. Deze inhoud is een origineel werk van het Virtua.Cloud-team. Reproductie, herpublicatie of herdistributie zonder schriftelijke toestemming is verboden.

Klaar om het zelf te proberen?

Deploy uw eigen server in seconden. Linux, Windows of FreeBSD.

Bekijk VPS-aanbod
Docker omzeilt UFW: 4 geteste oplossingen voor VPS