Docker contourne UFW : 4 solutions testées pour votre VPS

12 min de lecture·Matthieu|

Docker manipule iptables directement et ignore les règles UFW. Vos ports de conteneurs sont exposés sur Internet même avec ufw deny actif. Voici quatre solutions avec leurs compromis, chacune vérifiée par un scan depuis un hôte externe.

Votre pare-feu UFW vous ment. Si vous exécutez Docker sur un VPS avec UFW activé, chaque port publié d'un conteneur est ouvert sur Internet. Lancer ufw deny 8080 ne change rien. Docker contourne UFW complètement.

Ce tutoriel montre le problème en action, puis détaille quatre correctifs avec différents compromis. Chaque solution inclut une étape de vérification : scanner le port depuis un hôte externe pour confirmer qu'il est bien bloqué. Pas juste vérifier ufw status.

Ce guide fonctionne sur Ubuntu 24.04 et Debian 12. Les deux utilisent la couche de compatibilité iptables par défaut, donc les mêmes correctifs s'appliquent. Debian 12 utilise nftables comme backend par défaut, mais UFW et Docker interagissent via l'interface iptables, qui s'appuie sur nftables de manière transparente.

Prérequis : Un VPS avec Docker installé, UFW activé et un accès SSH. Une seconde machine (votre ordinateur portable ou un autre serveur) pour le scan de ports externe.

Pourquoi Docker contourne-t-il les règles du pare-feu UFW ?

Docker écrit des règles iptables dans les tables nat et filter pour router le trafic vers les conteneurs. UFW gère uniquement la chaîne INPUT. Quand un paquet arrive pour un port publié d'un conteneur, les règles NAT de Docker le redirigent via la chaîne FORWARD avant que les règles INPUT d'UFW ne puissent l'examiner. Le paquet n'atteint jamais UFW. Cela signifie que ufw deny n'a aucun effet sur les ports publiés par Docker.

Voici le flux d'un paquet qui arrive sur le port 8080 de votre VPS, où un conteneur est publié avec -p 8080:80 :

  1. Le paquet arrive sur l'interface réseau
  2. Il entre dans la chaîne PREROUTING de la table nat
  3. La règle DNAT de Docker réécrit la destination vers l'IP du conteneur (par ex. 172.17.0.2:80)
  4. Le paquet passe dans la chaîne FORWARD de la table filter (pas INPUT)
  5. La chaîne DOCKER accepte le paquet transféré
  6. Le paquet atteint le conteneur

UFW ne le voit jamais, car UFW surveille uniquement la chaîne INPUT. Le paquet emprunte le chemin FORWARD.

Ce n'est pas un bug. Docker a besoin du contrôle d'iptables pour faire fonctionner le réseau des conteneurs. Mais cela crée une faille de sécurité sérieuse si vous pensiez qu'UFW protégeait votre serveur.

Comment vérifier que Docker contourne mon pare-feu UFW ?

Avant d'appliquer un correctif, constatez le problème vous-même. Démarrez un conteneur de test qui sert du HTTP sur le port 8080 :

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

Vérifiez qu'UFW est actif et refuse le trafic entrant par défaut :

sudo ufw status verbose

Vous devriez voir Default: deny (incoming) dans la sortie. Maintenant, refusez explicitement le port 8080 :

sudo ufw deny 8080

Vérifiez qu'UFW indique que le port est bloqué :

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

UFW affiche le port comme refusé. Testez maintenant depuis votre machine locale (pas le serveur) :

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

La réponse est 200. Le conteneur est entièrement accessible depuis Internet malgré le refus d'UFW sur ce port. Si vous avez nmap installé sur votre machine locale :

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

Le port est ouvert. UFW ne le protège pas.

Supprimez la règle deny (vous allez appliquer un vrai correctif ensuite) :

sudo ufw delete deny 8080

Gardez le conteneur de test en cours d'exécution. Vous l'utiliserez pour vérifier chaque solution.

Quel correctif Docker UFW choisir ?

Quatre solutions existent. Chacune a des compromis différents. Choisissez celle qui correspond à votre configuration.

Solution Impact réseau Survit au redémarrage Compatible Compose Complexité Idéal pour
Chaîne DOCKER-USER Aucun Oui Oui Moyen Contrôle total, plusieurs ports publics
Outil ufw-docker Aucun Oui Oui Faible Gestion automatisée, conteneurs dynamiques
iptables=false Casse le réseau inter-conteneurs, pas d'Internet depuis les conteneurs Oui Oui Faible Conteneurs isolés uniques (rare)
Bind sur 127.0.0.1 Aucun Oui Oui Faible Services derrière un reverse proxy

Décision rapide :

  1. Tous vos conteneurs sont derrière un reverse proxy (Nginx, Traefik, Caddy) ? Utilisez le binding 127.0.0.1. C'est le plus simple.
  2. Vous avez besoin de conteneurs accessibles publiquement sur des ports spécifiques ? Utilisez la chaîne DOCKER-USER pour un contrôle manuel ou ufw-docker pour une gestion automatisée.
  3. Vous exécutez des conteneurs qui ne doivent jamais accéder au réseau ? Utilisez iptables=false, mais comprenez les compromis.

Comment corriger le contournement d'UFW par Docker avec la chaîne DOCKER-USER ?

La chaîne DOCKER-USER est un espace réservé que Docker laisse vide pour les administrateurs. Les règles que vous y ajoutez sont évaluées avant les propres règles de transfert de Docker. En insérant des règles d'intégration UFW dans cette chaîne via /etc/ufw/after.rules, vous faites passer le trafic Docker par UFW.

Cette méthode vous donne un contrôle total. Elle fonctionne avec docker run et Docker Compose. Elle survit aux redémarrages car les règles sont chargées au démarrage d'UFW.

Ouvrez /etc/ufw/after.rules :

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

Ajoutez le bloc suivant à la fin du fichier, après la ligne COMMIT existante :

# 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

Ce que fait ce bloc :

  • -A DOCKER-USER -j ufw-user-forward : envoie le trafic Docker d'abord dans les règles de transfert d'UFW
  • conntrack --ctstate RELATED,ESTABLISHED : autorise le trafic retour des connexions établies (maintient les connexions existantes après modification des règles)
  • conntrack --ctstate INVALID : supprime les paquets malformés
  • -i docker0 -o docker0 : autorise la communication entre conteneurs sur le même réseau bridge
  • Les lignes -j RETURN -s autorisent le trafic provenant des sous-réseaux privés
  • Les nouvelles connexions (ctstate NEW) vers les sous-réseaux Docker depuis l'extérieur sont journalisées et rejetées
  • La règle de journalisation limite le débit à 3 messages par minute pour éviter de saturer vos logs

Rechargez UFW pour appliquer les nouvelles règles :

sudo ufw reload

Vérifiez que les règles sont chargées :

sudo iptables -L DOCKER-USER -n -v

Vous devriez voir vos nouvelles règles dans la sortie de la chaîne. Si la chaîne n'affiche qu'une règle RETURN par défaut, le bloc after.rules n'a pas été chargé correctement. Vérifiez la syntaxe du fichier.

Autoriser un port de conteneur spécifique via UFW

Avec la chaîne DOCKER-USER active, tous les ports de conteneurs sont bloqués par défaut depuis l'extérieur. Pour autoriser un port spécifique, vous devez référencer l'IP du conteneur et le port du conteneur, pas le port hôte. La raison : la règle DNAT de Docker dans la chaîne PREROUTING réécrit la destination avant que le paquet n'atteigne la chaîne FORWARD. Au moment où votre règle est évaluée, la destination est l'adresse interne du conteneur.

Trouvez d'abord l'IP du conteneur :

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

Puis autorisez le trafic vers ce conteneur :

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

Vérifiez qu'UFW affiche la règle de route :

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

Note : puisque la règle cible une IP de conteneur qui peut changer au redémarrage, utilisez l'outil ufw-docker (section suivante) pour un suivi automatique des IP. L'approche manuelle DOCKER-USER convient mieux quand vous gérez les IP de conteneurs vous-même (IP statiques dans Docker Compose ou quand vous scriptez les mises à jour de règles).

Vérification depuis un hôte externe

Depuis votre machine locale, testez le port autorisé :

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

Le port est accessible car vous avez explicitement autorisé le transfert vers le conteneur. Testez maintenant un port non autorisé (par exemple, si vous aviez un conteneur sur le port 9090) :

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

filtered signifie que le pare-feu rejette silencieusement les paquets. La chaîne DOCKER-USER fonctionne.

Comment utiliser l'outil ufw-docker pour gérer les règles de pare-feu des conteneurs ?

L'outil chaifeng/ufw-docker automatise la configuration de la chaîne DOCKER-USER et fournit des commandes pour autoriser ou refuser les ports des conteneurs. Il modifie /etc/ufw/after.rules pour vous et ajoute un CLI pour gérer les règles par conteneur.

Installation :

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

Examinez le script avant de l'exécuter. C'est un script shell que vous pouvez lire avec cat /usr/local/bin/ufw-docker.

Lancez l'installation pour modifier /etc/ufw/after.rules :

sudo ufw-docker install

Cela ajoute des règles de chaîne DOCKER-USER similaires à la section précédente, incluant le filtrage basé sur conntrack et la journalisation. Cela modifie aussi after6.rules pour IPv6. Rechargez UFW :

sudo ufw reload

Vérifiez l'installation :

sudo ufw-docker check

Gestion des ports de conteneurs

Autoriser l'accès externe au port 80 d'un conteneur nommé web :

sudo ufw-docker allow web 80/tcp

Lister les règles d'un conteneur :

sudo ufw-docker list web

Supprimer une règle :

sudo ufw-docker delete allow web 80/tcp

Afficher toutes les règles de pare-feu Docker :

sudo ufw-docker status

Exemple Docker Compose avec ufw-docker

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

Démarrez la stack, puis autorisez le port :

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

Note : la commande ufw-docker référence le port du conteneur (80), pas le port hôte (8080). L'outil résout le mapping automatiquement.

Alternative : ufw-docker-automated

Le projet shinebayar-g/ufw-docker-automated adopte une approche différente. Il s'exécute comme un conteneur Docker, écoute les événements de l'API Docker et crée automatiquement des règles UFW quand les conteneurs démarrent ou s'arrêtent. Cela résout une limitation de l'outil ufw-docker : quand un conteneur redémarre et obtient une nouvelle IP, l'ancienne règle devient invalide.

Avec ufw-docker-automated, vous ajoutez des labels à vos conteneurs :

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

L'outil surveille les conteneurs avec le label UFW_MANAGED=TRUE et crée les règles UFW correspondantes automatiquement. Il supporte aussi UFW_ALLOW_FROM pour restreindre l'accès à des IP ou plages CIDR spécifiques.

Vérification depuis un hôte externe

Après avoir autorisé un port avec ufw-docker, confirmez depuis votre machine locale :

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

Testez un port non listé pour confirmer qu'il est bloqué :

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

Que se passe-t-il si je désactive la gestion iptables de Docker ?

Définir "iptables": false dans la configuration du démon Docker empêche Docker de créer des règles iptables. C'est le correctif le plus simple, mais celui qui a les effets secondaires les plus sévères.

Ce qui casse :

  • Les conteneurs ne peuvent plus accéder à Internet (pas de règles de masquerading)
  • La communication entre conteneurs sur des réseaux différents cesse de fonctionner
  • La publication de ports (-p) cesse de fonctionner entièrement. Vous devez gérer toutes les règles de transfert vous-même.

Cette approche convient uniquement si vous exécutez des conteneurs isolés qui n'ont pas besoin d'accès Internet et que vous gérez tout le réseau manuellement.

Modifiez ou créez le fichier de configuration du démon Docker :

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

Si le fichier contient déjà du contenu, ajoutez la clé "iptables": false à l'objet JSON existant. Ne créez pas un second objet JSON.

Redémarrez Docker :

sudo systemctl restart docker

Vérifiez que Docker ne crée plus de règles iptables :

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

Avec Docker 28.x, la chaîne DOCKER peut encore exister mais ne devrait contenir qu'une règle DROP au lieu des règles de transfert habituelles. Aucune règle ACCEPT signifie que Docker ne route pas le trafic vers les conteneurs.

Vérification depuis un hôte externe

Redémarrez le conteneur de test (il a été arrêté au redémarrage de Docker) :

docker start ufw-test

Depuis votre machine locale :

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

closed (et non filtered) car Docker ne crée plus les règles NAT pour transférer le trafic. Le port n'écoute pas du point de vue externe.

Pour revenir en arrière, supprimez "iptables": false de daemon.json et redémarrez Docker :

sudo systemctl restart docker

Comment lier les ports Docker Compose à localhost uniquement ?

Pour les conteneurs derrière un reverse proxy (Nginx, Traefik, Caddy), le correctif le plus simple est de ne jamais publier les ports sur 0.0.0.0. Liez-les à 127.0.0.1 à la place. Le reverse proxy se connecte au conteneur via localhost. Le trafic externe arrive sur le reverse proxy via les ports 80/443, que UFW contrôle normalement.

Cela ne nécessite aucune modification d'iptables, aucun outil supplémentaire, et fonctionne sur tous les OS.

Dans votre docker-compose.yml, modifiez le binding de port :

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

Le service app est lié à 127.0.0.1:8080 uniquement. Il n'est pas accessible depuis l'extérieur du serveur. Le service nginx est lié à 0.0.0.0:80 et 0.0.0.0:443 (le défaut quand aucune IP n'est spécifiée), que vous contrôlez avec UFW.

Pour docker run, la syntaxe équivalente :

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

Vérifier le binding

Sur le serveur, confirmez que le port est lié à localhost uniquement :

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

Regardez bien : l'adresse d'écoute est 127.0.0.1:8080, pas 0.0.0.0:8080. Cela signifie que seules les connexions locales sont acceptées.

Vérification depuis un hôte externe

Depuis votre machine locale :

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

Le port est fermé au trafic externe. Votre reverse proxy gère l'accès public sur les ports 80 et 443, qu'UFW protège normalement.

C'est l'approche recommandée pour la plupart des configurations VPS où vous exécutez des applications web derrière un reverse proxy.

Dépannage

Les règles disparaissent après un redémarrage de Docker

Si vous avez utilisé l'approche de la chaîne DOCKER-USER et que les règles disparaissent au redémarrage de Docker, vérifiez que vos règles sont dans /etc/ufw/after.rules et non ajoutées manuellement avec des commandes iptables. Les règles iptables manuelles ne survivent pas aux redémarrages de service. Le fichier after.rules est chargé à chaque démarrage d'UFW.

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

Le conteneur ne peut pas résoudre le DNS après application des règles DOCKER-USER

Le bloc after.rules inclut une règle conntrack --ctstate RELATED,ESTABLISHED qui autorise le trafic de réponse DNS à revenir vers les conteneurs. Si la résolution DNS échoue dans les conteneurs, vérifiez que la règle conntrack est chargée :

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

Si elle est absente, vérifiez la syntaxe de votre bloc after.rules et rechargez UFW.

Les commandes ufw-docker échouent avec « container not found »

La commande ufw-docker allow nécessite que le conteneur soit en cours d'exécution. Elle résout le nom du conteneur en adresse IP. Si le conteneur est arrêté, la commande échoue. Démarrez le conteneur d'abord, puis ajoutez la règle.

Compatibilité nftables sur Debian 12

Debian 12 utilise nftables comme backend de pare-feu, mais UFW et Docker utilisent la couche de compatibilité iptables (iptables-nft). Les solutions de ce guide fonctionnent de manière identique sur Debian 12 et Ubuntu 24.04. Vérifiez que vous utilisez le backend nft :

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

Le suffixe (nf_tables) confirme que la couche de compatibilité iptables-nft est active. Toutes les règles de la chaîne DOCKER-USER fonctionnent via cette couche.

Vérifier les logs du trafic bloqué

Si une connexion est bloquée de manière inattendue après application du correctif de la chaîne DOCKER-USER, vérifiez les logs UFW Docker :

sudo journalctl -k | grep "UFW DOCKER BLOCK"

Les entrées de log affichent l'IP source et le port de destination des paquets bloqués.

Nettoyage des ressources de test

Supprimez le conteneur de test quand vous avez terminé :

docker rm -f ufw-test

Si vous avez ajouté une règle UFW deny pendant les tests, supprimez-la :

sudo ufw delete deny 8080

Copyright 2026 Virtua.Cloud. Tous droits réservés. Ce contenu est une création originale de l'équipe Virtua.Cloud. Toute reproduction, republication ou redistribution sans autorisation écrite est interdite.

Prêt à essayer ?

Déployez votre serveur en quelques secondes. Linux, Windows ou FreeBSD.

Voir les offres VPS