Docker bypassa UFW: 4 soluzioni testate per il tuo VPS

12 min di lettura·Matthieu|

Docker manipola iptables direttamente e ignora le regole UFW. Le porte dei tuoi container sono esposte su Internet anche con ufw deny attivo. Ecco quattro soluzioni con i relativi compromessi, ciascuna verificata con una scansione da un host esterno.

Il tuo firewall UFW ti sta mentendo. Se esegui Docker su un VPS con UFW attivo, ogni porta pubblicata di un container è completamente aperta su Internet. Eseguire ufw deny 8080 non serve a nulla. Docker bypassa UFW completamente.

Questo tutorial mostra il problema in azione, poi illustra quattro soluzioni con diversi compromessi. Ogni soluzione include un passaggio di verifica: scansionare la porta da un host esterno per confermare che sia effettivamente bloccata. Non limitarsi a controllare ufw status.

Questa guida funziona su Ubuntu 24.04 e Debian 12. Entrambi usano il livello di compatibilità iptables per impostazione predefinita, quindi le stesse soluzioni si applicano. Debian 12 usa nftables come backend predefinito, ma UFW e Docker interagiscono tramite l'interfaccia iptables, che si mappa su nftables in modo trasparente.

Prerequisiti: Un VPS con Docker installato, UFW attivo e accesso SSH. Una seconda macchina (il tuo portatile o un altro server) per la scansione porte esterna.

Perché Docker bypassa le regole del firewall UFW?

Docker scrive regole iptables nelle tabelle nat e filter per instradare il traffico verso i container. UFW gestisce solo la catena INPUT. Quando un pacchetto arriva per una porta pubblicata di un container, le regole NAT di Docker lo reindirizzano attraverso la catena FORWARD prima che le regole INPUT di UFW possano vederlo. Il pacchetto non raggiunge mai UFW. Questo significa che ufw deny non ha alcun effetto sulle porte pubblicate da Docker.

Ecco il flusso del pacchetto per una richiesta alla porta 8080 del tuo VPS, dove un container è pubblicato con -p 8080:80:

  1. Il pacchetto arriva all'interfaccia di rete
  2. Entra nella catena PREROUTING della tabella nat
  3. La regola DNAT di Docker riscrive la destinazione all'IP del container (es. 172.17.0.2:80)
  4. Il pacchetto passa alla catena FORWARD della tabella filter (non INPUT)
  5. La catena DOCKER accetta il pacchetto inoltrato
  6. Il pacchetto raggiunge il container

UFW non lo vede mai perché monitora solo la catena INPUT. Il pacchetto segue il percorso FORWARD.

Non è un bug. Docker ha bisogno del controllo di iptables per far funzionare il networking dei container. Ma crea una grave falla di sicurezza se pensavi che UFW proteggesse il tuo server.

Come verifico che Docker sta bypassando il mio firewall UFW?

Prima di applicare qualsiasi fix, osserva il problema di persona. Avvia un container di test che serve HTTP sulla porta 8080:

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

Controlla che UFW sia attivo e neghi il traffico in ingresso per impostazione predefinita:

sudo ufw status verbose

Dovresti vedere Default: deny (incoming) nell'output. Ora nega esplicitamente la porta 8080:

sudo ufw deny 8080

Controlla che UFW mostri la porta come bloccata:

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

UFW riporta la porta come negata. Ora testa dalla tua macchina locale (non dal server):

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

La risposta è 200. Il container è completamente accessibile da Internet nonostante UFW neghi la porta. Se hai nmap installato sulla tua macchina locale:

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

La porta è aperta. UFW non la protegge.

Rimuovi la regola deny (applicherai un vero fix dopo):

sudo ufw delete deny 8080

Mantieni il container di test in esecuzione. Lo userai per verificare ogni soluzione.

Quale fix Docker UFW dovrei usare?

Esistono quattro soluzioni. Ognuna ha compromessi diversi. Scegli quella adatta alla tua configurazione.

Soluzione Impatto rete Sopravvive al riavvio Compatibile Compose Complessità Ideale per
Catena DOCKER-USER Nessuno Medio Controllo totale, più porte pubbliche
Strumento ufw-docker Nessuno Basso Gestione automatizzata, container dinamici
iptables=false Interrompe la rete tra container, nessun accesso Internet dai container Basso Container isolati singoli (raro)
Bind su 127.0.0.1 Nessuno Basso Servizi dietro un reverse proxy

Decisione rapida:

  1. Tutti i tuoi container sono dietro un reverse proxy (Nginx, Traefik, Caddy)? Usa il binding a 127.0.0.1. È il più semplice.
  2. Hai bisogno di container accessibili pubblicamente su porte specifiche? Usa la catena DOCKER-USER per il controllo manuale o ufw-docker per la gestione automatizzata.
  3. Esegui container che non devono mai accedere alla rete? Usa iptables=false, ma comprendi i compromessi.

Come correggo il bypass di UFW da parte di Docker con la catena DOCKER-USER?

La catena DOCKER-USER è un segnaposto che Docker lascia vuoto per gli amministratori. Le regole che aggiungi qui vengono valutate prima delle regole di inoltro di Docker. Inserendo regole di integrazione UFW in questa catena tramite /etc/ufw/after.rules, fai passare il traffico Docker attraverso UFW.

Questo metodo ti dà il controllo totale. Funziona sia con docker run che con Docker Compose. Sopravvive ai riavvii perché le regole vengono caricate all'avvio di UFW.

Apri /etc/ufw/after.rules:

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

Aggiungi il seguente blocco alla fine del file, dopo la riga COMMIT esistente:

# 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

Cosa fa questo blocco:

  • -A DOCKER-USER -j ufw-user-forward: invia il traffico Docker prima alle regole di inoltro di UFW
  • conntrack --ctstate RELATED,ESTABLISHED: consente il traffico di ritorno per le connessioni stabilite (mantiene le connessioni esistenti dopo le modifiche alle regole)
  • conntrack --ctstate INVALID: scarta i pacchetti malformati
  • -i docker0 -o docker0: consente la comunicazione tra container sulla stessa rete bridge
  • Le righe -j RETURN -s consentono il traffico proveniente da sottoreti private
  • Le nuove connessioni (ctstate NEW) verso le sottoreti Docker dall'esterno vengono registrate e scartate
  • La regola di logging limita a 3 messaggi al minuto per evitare di saturare i log

Ricarica UFW per applicare le nuove regole:

sudo ufw reload

Verifica che le regole siano caricate:

sudo iptables -L DOCKER-USER -n -v

Dovresti vedere le tue nuove regole nell'output della catena. Se la catena mostra solo una regola RETURN predefinita, il blocco after.rules non è stato caricato correttamente. Controlla la sintassi del file.

Consentire una porta container specifica tramite UFW

Con la catena DOCKER-USER attiva, tutte le porte dei container sono bloccate per impostazione predefinita dall'esterno. Per consentire una porta specifica, devi fare riferimento all'IP del container e alla porta del container, non alla porta dell'host. Il motivo: la regola DNAT di Docker nella catena PREROUTING riscrive la destinazione prima che il pacchetto raggiunga la catena FORWARD. Al momento della valutazione della tua regola, la destinazione è l'indirizzo interno del container.

Prima trova l'IP del container:

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

Poi consenti il traffico verso quel container:

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

Verifica che UFW mostri la regola di route:

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

Nota: poiché la regola punta a un IP di container che può cambiare al riavvio, usa lo strumento ufw-docker (sezione successiva) per il tracciamento automatico degli IP. L'approccio manuale DOCKER-USER è migliore quando gestisci gli IP dei container tu stesso (IP statici in Docker Compose o quando automatizzi gli aggiornamenti delle regole).

Verifica da un host esterno

Dalla tua macchina locale, testa la porta consentita:

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

La porta è accessibile perché hai esplicitamente consentito l'inoltro al container. Ora testa una porta non consentita (ad esempio, se avessi un container sulla porta 9090):

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

filtered significa che il firewall sta scartando silenziosamente i pacchetti. La catena DOCKER-USER funziona.

Come uso lo strumento ufw-docker per gestire le regole firewall dei container?

Lo strumento chaifeng/ufw-docker automatizza la configurazione della catena DOCKER-USER e fornisce comandi per consentire o negare le porte dei container. Modifica /etc/ufw/after.rules per te e aggiunge un CLI per la gestione delle regole per container.

Installazione:

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

Esamina lo script prima di eseguirlo. È uno script shell che puoi leggere con cat /usr/local/bin/ufw-docker.

Esegui l'installazione per modificare /etc/ufw/after.rules:

sudo ufw-docker install

Questo aggiunge regole alla catena DOCKER-USER simili alla sezione precedente, incluso il filtraggio basato su conntrack e il logging. Modifica anche after6.rules per IPv6. Ricarica UFW:

sudo ufw reload

Verifica l'installazione:

sudo ufw-docker check

Gestione delle porte dei container

Consentire l'accesso esterno alla porta 80 di un container chiamato web:

sudo ufw-docker allow web 80/tcp

Elencare le regole di un container:

sudo ufw-docker list web

Rimuovere una regola:

sudo ufw-docker delete allow web 80/tcp

Mostrare tutte le regole firewall Docker:

sudo ufw-docker status

Esempio Docker Compose con ufw-docker

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

Avvia lo stack, poi consenti la porta:

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

Nota: il comando ufw-docker fa riferimento alla porta del container (80), non alla porta dell'host (8080). Lo strumento risolve il mapping automaticamente.

Alternativa: ufw-docker-automated

Il progetto shinebayar-g/ufw-docker-automated adotta un approccio diverso. Viene eseguito come container Docker, ascolta gli eventi dell'API Docker e crea automaticamente regole UFW quando i container si avviano o si fermano. Questo risolve una limitazione dello strumento ufw-docker: quando un container si riavvia e ottiene un nuovo IP, la vecchia regola diventa invalida.

Con ufw-docker-automated, aggiungi label ai tuoi container:

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

Lo strumento monitora i container con il label UFW_MANAGED=TRUE e crea automaticamente le regole UFW corrispondenti. Supporta anche UFW_ALLOW_FROM per limitare l'accesso a IP o range CIDR specifici.

Verifica da un host esterno

Dopo aver consentito una porta con ufw-docker, conferma dalla tua macchina locale:

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

Testa una porta non elencata per confermare che è bloccata:

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

Cosa succede se disattivo la gestione iptables di Docker?

Impostare "iptables": false nella configurazione del daemon Docker impedisce a Docker di creare regole iptables. È il fix più semplice ma ha gli effetti collaterali più gravi.

Cosa smette di funzionare:

  • I container non possono accedere a Internet (nessuna regola di masquerading)
  • La comunicazione tra container su reti diverse smette di funzionare
  • La pubblicazione delle porte (-p) smette di funzionare completamente. Devi gestire tutte le regole di inoltro tu stesso.

Questo approccio è appropriato solo se esegui container isolati che non hanno bisogno di accesso a Internet e gestisci tutto il networking manualmente.

Modifica o crea il file di configurazione del daemon Docker:

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

Se il file ha già del contenuto, aggiungi la chiave "iptables": false all'oggetto JSON esistente. Non creare un secondo oggetto JSON.

Riavvia Docker:

sudo systemctl restart docker

Verifica che Docker non stia creando regole iptables:

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

Con Docker 28.x, la catena DOCKER potrebbe ancora esistere ma dovrebbe contenere solo una regola DROP invece delle solite regole di inoltro. Nessuna regola ACCEPT significa che Docker non sta instradando traffico verso i container.

Verifica da un host esterno

Riavvia il container di test (si è fermato al riavvio di Docker):

docker start ufw-test

Dalla tua macchina locale:

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

closed (non filtered) perché Docker non sta più creando le regole NAT per inoltrare il traffico. La porta non è in ascolto dalla prospettiva esterna.

Per ripristinare, rimuovi "iptables": false da daemon.json e riavvia Docker:

sudo systemctl restart docker

Come vincolo le porte Docker Compose solo a localhost?

Per i container dietro un reverse proxy (Nginx, Traefik, Caddy), la soluzione più semplice è non pubblicare mai le porte su 0.0.0.0. Vincola a 127.0.0.1 invece. Il reverse proxy si connette al container tramite localhost. Il traffico esterno raggiunge il reverse proxy sulle porte 80/443, che UFW controlla normalmente.

Questo non richiede modifiche a iptables, nessuno strumento aggiuntivo, e funziona su qualsiasi OS.

Nel tuo docker-compose.yml, cambia il binding della porta:

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

Il servizio app è vincolato solo a 127.0.0.1:8080. Non è accessibile dall'esterno del server. Il servizio nginx è vincolato a 0.0.0.0:80 e 0.0.0.0:443 (il valore predefinito quando non si specifica un IP), che controlli con UFW.

Per docker run, la sintassi equivalente:

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

Verificare il binding

Sul server, conferma che la porta è vincolata solo a localhost:

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

Osserva bene: l'indirizzo di ascolto è 127.0.0.1:8080, non 0.0.0.0:8080. Questo significa che vengono accettate solo connessioni locali.

Verifica da un host esterno

Dalla tua macchina locale:

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

La porta è chiusa al traffico esterno. Il tuo reverse proxy gestisce l'accesso pubblico sulle porte 80 e 443, che UFW protegge normalmente.

Questo è l'approccio consigliato per la maggior parte delle configurazioni VPS in cui si eseguono applicazioni web dietro un reverse proxy.

Risoluzione dei problemi

Le regole scompaiono dopo il riavvio di Docker

Se hai usato l'approccio della catena DOCKER-USER e le regole scompaiono al riavvio di Docker, verifica che le tue regole siano in /etc/ufw/after.rules e non aggiunte manualmente con comandi iptables. Le regole iptables manuali non sopravvivono ai riavvii del servizio. Il file after.rules viene caricato ogni volta che UFW si avvia.

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

Il container non riesce a risolvere il DNS dopo l'applicazione delle regole DOCKER-USER

Il blocco after.rules include una regola conntrack --ctstate RELATED,ESTABLISHED che consente al traffico di risposta DNS di tornare ai container. Se la risoluzione DNS fallisce nei container, verifica che la regola conntrack sia caricata:

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

Se manca, controlla la sintassi del tuo blocco after.rules e ricarica UFW.

I comandi ufw-docker falliscono con "container not found"

Il comando ufw-docker allow richiede che il container sia in esecuzione. Risolve il nome del container in un indirizzo IP. Se il container è fermo, il comando fallisce. Avvia prima il container, poi aggiungi la regola.

Compatibilità nftables su Debian 12

Debian 12 usa nftables come backend del firewall, ma UFW e Docker usano il livello di compatibilità iptables (iptables-nft). Le soluzioni in questa guida funzionano in modo identico su Debian 12 e Ubuntu 24.04. Verifica di usare il backend nft:

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

Il suffisso (nf_tables) conferma che il livello di compatibilità iptables-nft è attivo. Tutte le regole della catena DOCKER-USER funzionano attraverso questo livello.

Controllare i log del traffico bloccato

Se una connessione è bloccata inaspettatamente dopo aver applicato il fix della catena DOCKER-USER, controlla i log UFW Docker:

sudo journalctl -k | grep "UFW DOCKER BLOCK"

Le voci di log mostrano l'IP sorgente e la porta di destinazione dei pacchetti bloccati.

Pulizia delle risorse di test

Rimuovi il container di test quando hai finito:

docker rm -f ufw-test

Se hai aggiunto una regola UFW deny durante i test, rimuovila:

sudo ufw delete deny 8080

Copyright 2026 Virtua.Cloud. Tutti i diritti riservati. Questo contenuto è un'opera originale del team Virtua.Cloud. La riproduzione, ripubblicazione o redistribuzione senza autorizzazione scritta è vietata.

Pronto a provare?

Distribuisci il tuo server in pochi secondi. Linux, Windows o FreeBSD.

Vedi piani VPS