ufw ne bloque pas les ports publiés par Docker : cinq étapes pour fermer l’accès

sudo ufw status répond Status: active, aucune ligne ne mentionne le port 8080, et depuis une autre machine curl http://203.0.113.10:8080 renvoie 200. Docker Engine publie un port en écrivant une règle DNAT dans la table nat d’iptables, évaluée avant les chaînes INPUT et FORWARD d’ufw 0.36.2 : docker run -p 8080:80 ouvre 8080 à toutes les adresses source, et ufw deny 8080 ne le referme pas. La documentation Docker sur le filtrage de paquets reconnaît que les deux outils utilisent iptables de façon incompatible. Commandes relevées sous Debian 12, ufw 0.36.2-1 et Docker Engine 28.0.
Sommaire
- Étape 1 : vérifier que le port publié échappe à ufw
- Étape 2 : télécharger le script ufw-docker dans /usr/local/bin
- Étape 3 : ufw-docker install insère son bloc dans /etc/ufw/after.rules
- Étape 4 : autoriser un port avec le numéro de port du conteneur
- Étape 5 : rendre les règles durables avec ufw-docker.service
- Le piège : ufw allow 8080 ne rouvre rien, et les sources privées passent sans règle
- Quatre messages du script et leur cause
- Docker Engine 28.0.0 a fermé les ports non publiés, pas les ports publiés
- Trois méthodes comparées sur des critères mesurables
- Questions fréquentes
Étape 1 : vérifier que le port publié échappe à ufw
Relevez l’état réel du serveur. La dernière commande se lance depuis une autre machine, 203.0.113.10 représentant l’adresse du serveur.
sudo ufw status
sudo docker ps --format 'table {{.Names}}\t{{.Ports}}'
sudo iptables -t nat -L DOCKER -n
curl -s -o /dev/null -w '%{http_code}\n' http://203.0.113.10:8080
Ce qui doit apparaître : ufw status affiche Status: active avec une seule règle, 22/tcp ALLOW Anywhere, et rien pour 8080. docker ps affiche le conteneur httpd avec 0.0.0.0:8080->80/tcp. La chaîne DOCKER contient une ligne de cette forme :
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80
Le 200 de curl confirme l’accès depuis l’extérieur malgré l’absence de règle ufw ; 172.17.0.2 est l’adresse du conteneur sur le pont Docker.
Étape 2 : télécharger le script ufw-docker dans /usr/local/bin
Le script du projet chaifeng/ufw-docker s’installe hors des dépôts APT. Il refuse de démarrer si ufw status ne contient pas Status: active ou si docker est absent.
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
ls -l /usr/local/bin/ufw-docker
ufw-docker help
Ce qui doit apparaître : ls -l renvoie une ligne commençant par -rwxr-xr-x, et ufw-docker help affiche le bloc Usage: avec les sous-commandes allow, delete allow, install, install-service, reload, list et status.
Étape 3 : ufw-docker install insère son bloc dans /etc/ufw/after.rules
ufw-docker install ajoute à la fin de /etc/ufw/after.rules un bloc délimité par # BEGIN UFW AND DOCKER et # END UFW AND DOCKER, après copie du fichier d’origine. La première règle du bloc, -A DOCKER-USER -j ufw-user-forward, renvoie le trafic transféré vers la chaîne qu’ufw utilise pour ses règles de routage. Le script ne recharge pas le pare-feu, d’où la deuxième commande.
sudo ufw-docker install
sudo systemctl restart ufw
sudo ufw-docker check
Ce qui doit apparaître : le diff du fichier, puis Backing up /etc/ufw/after.rules to /etc/ufw/after.rules-ufw-docker~AAAA-MM-JJ-HHMMSS~, puis Please restart UFW service manually by using the following command: suivi de sudo systemctl restart ufw. ufw-docker check affiche l’en-tête ########## iptables -n -L DOCKER-USER ##########, où la première règle de la chaîne est le renvoi vers ufw-user-forward, et se termine par Check IPv4 firewall rules done.
Sur un hôte où Docker gère IPv6 (fixed-cidr-v6 dans /etc/docker/daemon.json), le bloc est aussi écrit dans /etc/ufw/after6.rules.
Étape 4 : autoriser un port avec le numéro de port du conteneur
ufw-docker allow attend le nom du conteneur et le port tel qu’il est exposé à l’intérieur du conteneur, pas le port publié sur le serveur. Le script interroge docker inspect et applique une règle de routage de la forme ufw route allow proto tcp from any to 172.17.0.2 port 80 comment "allow httpd 80/tcp".
sudo ufw-docker allow httpd 80
sudo ufw-docker list httpd
Ce qui doit apparaître : la ligne allow httpd 80/tcp puis la commande ufw correspondante. ufw-docker list httpd renvoie la ligne de ufw status numbered qui porte le commentaire # allow httpd 80/tcp, avec la mention ALLOW FWD dans la colonne Action, car la règle agit sur le trafic transféré et non sur le trafic entrant du serveur. Le port 8080 répond de nouveau depuis l’extérieur, les autres ports publiés restent fermés. Pour retirer l’autorisation, la commande est sudo ufw-docker delete allow httpd 80/tcp.
Étape 5 : rendre les règles durables avec ufw-docker.service
L’adresse d’un conteneur est attribuée au démarrage et la règle de l’étape 4 vise une adresse précise : un conteneur recréé perd son autorisation. La sous-commande install-service écrit une unité systemd /etc/systemd/system/ufw-docker.service qui exécute ufw-docker reload après docker.service.
sudo ufw-docker install-service
systemctl status ufw-docker
sudo ufw-docker reload
Ce qui doit apparaître : Service file created at /etc/systemd/system/ufw-docker.service, puis Service enabled and started. ; systemctl status ufw-docker affiche active (exited), ce qui est normal pour une unité Type=oneshot. ufw-docker reload affiche une ligne Reloading rule for httpd 80/tcp par règle existante et réécrit celles dont le conteneur a changé d’adresse. Les journaux se lisent avec journalctl -u ufw-docker, la méthode générale étant décrite dans services et journaux systemd.
Le piège : ufw allow 8080 ne rouvre rien, et les sources privées passent sans règle
ufw allow 8080 ajoute une règle entrante sur le serveur et laisse le port du conteneur fermé : le trafic d’un conteneur traverse la chaîne FORWARD, et seule une règle ufw route agit sur cette chaîne. Le bloc écrit dans /etc/ufw/after.rules n’apparaît pas dans ufw status : le manuel ufw 0.36.2 précise que cette commande ignore les règles des fichiers de /etc/ufw. ufw show after-rules montre le contenu du fichier, ufw show raw le pare-feu complet.
Deuxième piège, tout paquet dont l’adresse source est privée est laissé passer. La chaîne installée contient ces trois règles :
-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
Un poste du réseau local en 192.168.1.0/24 joint donc un port publié sans qu’aucune commande ufw-docker allow n’ait été lancée, alors qu’un poste sur Internet est bloqué. Le dépôt assume ce choix, les réseaux privés étant jugés plus fiables ; c’est la limite à connaître avant d’exposer un service à tout un réseau d’entreprise. Pour réduire la liste des réseaux de confiance, ufw-docker install --docker-subnets accepte des préfixes CIDR explicites, et la commande se relance après chaque création de réseau Docker.
Le dépôt signale enfin un cas connu : « There may be some unknown reasons cause the UFW rules will not take effect after restart UFW, please reboot servers. » Si le trafic passe toujours après systemctl restart ufw, un redémarrage complet de la machine est la première chose à tenter. Pour les autres services exposés de l’hôte, la démarche est celle de durcissement minimal ; les sujets serveur voisins sont dans Linux.
Quatre messages du script et leur cause
Messages relevés tels quels sur la sortie d’erreur :
ERROR: UFW is disabled or you are not root user, or mismatched iptables legacy/nf_tables, current <sortie de iptables --version>
ERROR: Docker instance "httpd" doesn't exist.
Fail to add rule(s), cannot find the published port 80/tcp of instance "httpd" or cannot update outdated rule(s).
ERROR: invalid port syntax: "8080:80".
- Le premier tombe quand ufw n’est pas actif, quand la commande n’est pas lancée en root, ou quand
ufwetiptablesne visent pas le même moteur, legacy contre nftables. - Le deuxième signifie que
docker inspect httpdne trouve rien : conteneur arrêté, renommé, ou lancé sur une autre machine. - Le troisième indique que le port demandé n’est pas publié par ce conteneur ; le script attend le port interne et le protocole, par exemple
ufw-docker allow httpd 80/tcp. - Le quatrième refuse la syntaxe
hôte:conteneurdedocker run: écrivez80, pas8080:80.
Docker Engine 28.0.0 a fermé les ports non publiés, pas les ports publiés
Docker Engine 28.0.0, publié en février 2025, supprime désormais les paquets vers une adresse de conteneur dont le port n’est pas publié ; un hôte en FORWARD ACCEPT laissait auparavant un voisin du réseau local joindre cette adresse. L’option "ip-forward-no-drop": true dans /etc/docker/daemon.json rétablit l’ancienne politique ; un réseau nat-unprotected reste joignable sans publication.
Ce durcissement ne concerne pas les ports publiés : docker run -p 8080:80 continue d’ouvrir 8080 à toutes les adresses source. Le contournement d’ufw reste donc entier après la mise à jour, et c’est la chaîne DOCKER-USER qu’il faut corriger.
La documentation Docker déconseille également "iptables": false dans /etc/docker/daemon.json : les conteneurs du réseau bridge perdent alors l’accès à Internet, et leurs ports restent joignables depuis le réseau local. Le dépôt ufw-docker demande de revenir en arrière si cette modification a déjà été faite.
Trois méthodes comparées sur des critères mesurables
| Méthode | Port publié joignable depuis Internet | Depuis le réseau local | Après redémarrage du conteneur | Où lire la règle |
|---|---|---|---|---|
Publication sur 127.0.0.1 avec -p 127.0.0.1:8080:80 |
non | non | conservée, option du conteneur | docker ps, aucune règle de pare-feu |
| Règle iptables manuelle dans DOCKER-USER | non, si la règle correspond exactement | à régler au cas par cas | à rejouer, iptables n’est pas persistant | iptables -n -L DOCKER-USER |
| ufw-docker | non, par défaut | oui, sauf --docker-subnets restreint |
conservée avec ufw-docker.service |
ufw status numbered, commentaire # allow httpd 80/tcp |
Questions fréquentes
ufw est actif et le port publié répond depuis Internet, est-ce normal ?
Oui, c’est le comportement documenté de Docker Engine sous Debian 12 : la redirection de port est écrite dans la table nat, avant les chaînes qu’ufw 0.36.2 contrôle, donc ufw deny 8080 n’a aucun effet sur ce trafic. La correction passe par la chaîne DOCKER-USER, installée aux étapes 3 et 4.
Pourquoi ufw allow 8080 ne rouvre-t-il rien après ufw-docker install ?
Parce que le trafic arrive par la chaîne FORWARD. La commande est sudo ufw-docker allow httpd 80, ou à la main sudo ufw route allow proto tcp from any to 172.17.0.2 port 80. Une règle ufw allow ne concerne que les services du serveur lui-même.
Comment n’autoriser qu’une seule adresse IP source ?
Relevez l’adresse du conteneur, puis posez la règle de routage avec cette source. La commande ufw-docker reload ne réécrit pas cette règle manuelle, posée et retirée à la main.
sudo docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' httpd
sudo ufw route allow proto tcp from 203.0.113.7 to 172.17.0.2 port 80
sudo ufw route delete allow proto tcp from 203.0.113.7 to 172.17.0.2 port 80
Une source située dans 10.0.0.0/8, 172.16.0.0/12 ou 192.168.0.0/16 reste acceptée par les règles de l’étape 3, quelle que soit cette restriction.
Faut-il mettre “iptables”: false dans /etc/docker/daemon.json ?
Non. La documentation Docker écrit que cette option n’est pas adaptée à la plupart des usages et qu’elle casse le réseau des conteneurs : sans les règles de masquerade, un conteneur du réseau bridge ne joint plus Internet, et ses ports deviennent joignables depuis le réseau local.
Quelle commande unique vérifie que le filtrage est en place ?
sudo iptables -n -L DOCKER-USER affiche la chaîne active ; la première ligne doit être le renvoi vers ufw-user-forward. ufw-docker check fait la même vérification et se termine par Check IPv4 firewall rules done.