SSD Nodes Learn 🎉 VPS dès $5.50/mois
Guides Matt ConnorPar Matt Connor

iptables ou nftables sur Ubuntu : lequel choisir ?

Sur Ubuntu, iptables écrit souvent des règles nftables. Vérifiez le backend, lisez le ruleset natif et évitez les conflits entre ufw et Docker.

iptables et nftables sur Ubuntu : lequel fonctionne sur votre serveur ?

Sur Ubuntu 20.04 et les versions ultérieures, la commande iptables est une interface qui écrit des règles nftables. Un seul filtre de paquets s’exécute dans le kernel, nftables, et deux commandes en espace utilisateur le configurent. Une ligne iptables -A INPUT fonctionne toujours exactement comme avant, et la règle qu’elle crée est une règle nftables que nft peut afficher.

Vérifiez ce point sur votre propre serveur avant de vous y fier.

iptables -V
sudo update-alternatives --display iptables
sudo nft list ruleset

Sur Ubuntu 24.04 (iptables 1.8.10, en août 2026), iptables -V affiche iptables v1.8.10 (nf_tables). Le nom entre crochets correspond au backend. (nf_tables) signifie que la commande communique avec nftables. (legacy) signifie qu’elle utilise l’ancien backend x_tables, qu’Ubuntu fournit toujours sous le nom iptables-legacy et que le kernel conserve dans un ruleset complètement distinct. update-alternatives affiche le lien symbolique correspondant à ce choix : link currently points to /usr/sbin/iptables-nft.

Sur un VPS neuf sans pare-feu configuré, sudo nft list ruleset n’affiche rien. Cette sortie vide constitue votre référence. Ajoutez une règle avec l’ancienne méthode, puis vérifiez à nouveau.

sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo nft list ruleset
# Warning: table ip filter is managed by iptables-nft, do not touch!
table ip filter {
  chain INPUT {
    type filter hook input priority filter; policy accept;
    tcp dport 22 counter packets 0 bytes 0 accept
  }
  chain FORWARD {
    type filter hook forward priority filter; policy accept;
  }
  chain OUTPUT {
    type filter hook output priority filter; policy accept;
  }
}

Votre règle iptables est une règle nftables. iptables-nft indique les tables qu’elle crée et nft affiche cet avertissement lorsqu’il détecte cette indication, car modifier une telle table avec nft confie la gestion des mêmes règles à deux outils. Regardez ce qu’une seule commande a produit : une table que vous n’avez pas nommée et des chaînes que vous n’avez pas demandées. C’est l’ancien modèle. C’est aussi le premier point qui change lorsque vous écrivez directement des règles nftables.

Ce que iptables -L vous cache

iptables -L n’affiche que la table filter. Les règles NAT (network address translation) nécessitent iptables -t nat -L, et les règles mangle nécessitent -t mangle. IPv6 utilise une commande distincte, ip6tables, avec sa propre copie de chaque règle. Un serveur peut donc sembler propre dans une liste alors qu’une règle d’une table que vous n’avez jamais vérifiée supprime ou réécrit vos paquets.

sudo nft list ruleset affiche toutes les familles, toutes les tables, toutes les chaînes et toutes les règles dans une seule sortie. Sur un serveur que vous n’avez pas construit vous-même, cette commande est le moyen le plus rapide de voir ce qui est réellement chargé. Ajoutez -a pour afficher les handles des règles. Vous en avez besoin pour supprimer une seule règle au lieu de supprimer toute la chaîne.

Deux habitudes méritent d’être corrigées. iptables -L résout les adresses et les ports en noms. Sur un serveur dont le resolver ne fonctionne pas, la commande semble donc bloquée : utilisez iptables -nvL. Vérifiez également que le backend historique est vide avec sudo iptables-legacy -nvL. Si des règles existent dans les deux backends, le kernel évalue les deux, et aucune des deux listes ne présente la situation complète.

Tables et chaînes que vous créez, et non que vous héritez

nftables ne contient aucune règle au départ. Il n’existe aucune table filter tant que vous n’en avez pas créé une, et le terme filter est simplement le nom que vous avez choisi. Une chaîne ne voit les paquets que si vous lui attribuez un type, un hook et une priorité, ce qui en fait une chaîne de base. Sans ces paramètres, une chaîne n’est atteinte que par un jump ou un goto explicite. Elle ne consomme donc aucune ressource tant qu’un saut ne l’appelle pas.

L’autre changement majeur concerne la famille inet. Une table inet gère IPv4 et IPv6 avec les mêmes règles. Cela élimine toute une catégorie de bugs dans lesquels un port est fermé dans iptables, mais complètement ouvert dans ip6tables. Cette incohérence est suffisamment fréquente pour avoir son propre mode de défaillance sur les serveurs utilisant ufw.

Voici un jeu de règles complet pour un serveur. Il va dans /etc/nftables.conf.

#!/usr/sbin/nft -f
flush ruleset

table inet filter {
  set admin_ips {
    type ipv4_addr
    flags interval
    elements = { 203.0.113.5, 198.51.100.0/24 }
  }

  chain input {
    type filter hook input priority filter; policy drop;
    ct state established,related accept
    ct state invalid drop
    iif lo accept
    ip protocol icmp accept
    meta l4proto ipv6-icmp accept
    tcp dport 22 ip saddr @admin_ips counter accept
    tcp dport { 80, 443 } counter accept
  }

  chain forward {
    type filter hook forward priority filter; policy drop;
  }

  chain output {
    type filter hook output priority filter; policy accept;
  }
}

Lisez deux fois la ligne 2. flush ruleset supprime toutes les tables du serveur, y compris celles que ufw et Docker ont créées pour leur propre usage. Continuez votre lecture avant d’exécuter cette commande sur un serveur en production.

La première règle de la chaîne input fait l’essentiel du travail. ct state established,related accept laisse entrer les réponses aux connexions que vous avez ouvertes. Le reste de la chaîne doit donc seulement décider du sort des nouvelles connexions. ct state invalid drop supprime les paquets qui ne correspondent à aucune connexion connue et ne constituent pas un début de connexion valide. Tout ce qui suit correspond à une autorisation explicite, et policy drop traite le reste.

Vérifiez le fichier avant de le charger et gardez une deuxième session SSH ouverte pendant l’opération. policy drop, combiné à une seule faute de frappe dans la règle SSH, peut vous bloquer l’accès à votre propre serveur.

sudo nft -c -f /etc/nftables.conf
sudo nft -f /etc/nftables.conf
sudo nft list ruleset

nft -c -f analyse le fichier et signale les erreurs sans rien charger. Une analyse réussie n’affiche aucune sortie.

Les sets remplacent les longues listes de règles

tcp dport { 80, 443 } est un set anonyme : une seule règle et un seul lookup, au lieu d’une règle par port. Un set nommé comme admin_ips va plus loin, car vous pouvez le modifier pendant que le firewall fonctionne.

sudo nft add element inet filter admin_ips { 203.0.113.9 }
sudo nft list set inet filter admin_ips
sudo nft delete element inet filter admin_ips { 203.0.113.9 }

Aucun reload ni renumérotation des règles. Le match reste un seul lookup, que le set contienne cinq adresses ou cinquante mille. flags interval permet à un set de contenir des plages et des préfixes CIDR (classless inter-domain routing) comme 198.51.100.0/24. Sans ce flag, le set n’accepte que des adresses individuelles et le chargement du préfixe échoue.

Les sets peuvent également faire expirer automatiquement leurs éléments.

set banned {
  type ipv4_addr
  flags timeout
  timeout 1h
}

Avec une règle ip saddr @banned drop, chaque élément se supprime une heure après son ajout. C’est ainsi que l’action nftables de fail2ban sur Ubuntu 24.04 bloque une adresse : elle ajoute un élément à un set, au lieu d’ajouter une règle. Si les ports sont encore un sujet nouveau pour vous, commencez par ce qu’est réellement un port sous Linux.

Une différence pose souvent problème lors d’une migration. nftables ne compte pas les paquets si vous ne le lui demandez pas. iptables -nvL affiche toujours des compteurs pour chaque règle. Avec nftables, seules les règles qui contiennent le mot-clé counter ont des compteurs. Ajoutez donc counter à toute règle que vous devrez probablement déboguer ultérieurement.

Fonctionnement des hooks et des priorités

Une chaîne de base définit un hook, c’est-à-dire le point du parcours du paquet où elle s’exécute. prerouting s’exécute avant la décision de routage. input s’exécute pour les paquets destinés à cette machine. forward s’exécute pour les paquets routés par cette machine. output s’exécute pour les paquets générés par des processus locaux. postrouting s’exécute en dernier, juste avant que le paquet ne quitte la machine.

La priorité détermine l’ordre des chaînes au sein d’un même hook, en commençant par le nombre le plus faible. nftables donne des noms aux valeurs classiques : raw correspond à -300, mangle à -150, dstnat à -100, filter à 0 et srcnat à 100. Écrire priority filter; revient à écrire priority 0;.

Voici maintenant ce qui détermine si le mélange des outils fonctionne. Toutes les chaînes de base enregistrées sur un hook s’exécutent dans l’ordre des priorités. Un paquet accepté par votre chaîne n’est pas encore traité : accept termine uniquement cette chaîne, puis le paquet continue vers la chaîne de base suivante du même hook. drop est définitif partout et arrête immédiatement le paquet. Ainsi, une règle permissive dans votre table ne peut pas annuler un drop dans la table d’ufw, quelle que soit la chaîne qui s’exécute en premier. Votre accept ne vous protège donc pas contre une chaîne qui s’exécute plus tard.

Deux chaînes de base utilisant le même hook et la même priorité s’exécutent dans l’ordre de leur enregistrement. Cet ordre dépend du service qui a démarré en premier. Il peut changer après un redémarrage. Si vous devez utiliser votre propre table à côté d’ufw, attribuez-lui une priorité distincte. L’ordre est ainsi défini explicitement au lieu de dépendre d’une compétition au démarrage.

Pourquoi n’y a-t-il aucune règle de reverse NAT à écrire ?

C’est la question sur laquelle les erreurs sont les plus fréquentes. Voici la réponse directe : le suivi des connexions écrit la traduction inverse pour vous. Il n’y a pas de seconde règle à ajouter.

Une table nat qui assure les deux fonctions habituelles d’un VPS se présente ainsi.

table inet nat {
  chain prerouting {
    type nat hook prerouting priority dstnat; policy accept;
    iifname "enp1s0" tcp dport 8080 dnat ip to 10.0.0.5:80
  }

  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;
    ip saddr 10.0.0.0/24 oifname "enp1s0" masquerade
  }
}

Seul le premier paquet d’une connexion est évalué par une chaîne nat. Lorsqu’une règle correspond, le noyau enregistre cette traduction dans la table de suivi des connexions, avec l’entrée de la connexion. Chaque paquet suivant, dans les deux directions, est réécrit à partir de cette entrée enregistrée. Aucune règle n’est relue. Installez l’outil conntrack et consultez une entrée en temps réel.

sudo apt install -y conntrack
sudo conntrack -L -p tcp
tcp 6 431999 ESTABLISHED src=198.51.100.20 dst=203.0.113.10 sport=54321 dport=8080 src=10.0.0.5 dst=198.51.100.20 sport=80 dport=54321 [ASSURED] mark=0 use=1

Lisez cette entrée comme deux tuples. Les quatre premiers champs représentent la connexion telle que le client l’a envoyée, à destination de 203.0.113.10:8080, votre adresse publique. Les quatre suivants représentent la réponse attendue par le noyau : elle est déjà inversée et déjà traduite, et provient de 10.0.0.5:80, le backend réel. Ce deuxième tuple est la règle inverse. Le noyau l’a écrite lorsque le premier paquet a correspondu.

N’écrivez donc pas de règle pour le sens retour. Elle ne pourrait pas correspondre, car les paquets retour appartiennent à une connexion établie et n’atteignent jamais une chaîne nat. Et si elle était malgré tout évaluée, vous traduiriez un paquet que le noyau a déjà corrigé.

L’emplacement d’une réécriture découle du même mécanisme. La translation de destination doit s’exécuter dans prerouting, avant la décision de routage, car le routage doit voir la nouvelle destination. Sinon, le paquet est envoyé au mauvais endroit. Le trafic généré par la machine elle-même est traité dans le hook output pour la même raison. La translation de source, y compris la réécriture du port source, doit s’exécuter dans postrouting, après que le routage a sélectionné l’interface de sortie. masquerade prend son adresse sur cette interface, qui n’est pas connue avant l’exécution du routage.

C’est pourquoi une règle comme celle-ci doit se trouver à la fin du chemin, et nulle part ailleurs.

ip saddr 10.0.0.0/24 oifname "enp1s0" snat ip to 203.0.113.10:20000-30000

La plage de ports réécrit le port source en même temps que l’adresse source. C’est ce qu’il faut lorsque plusieurs clients internes partagent une même adresse publique et que leurs ports source entrent en collision. Une réponse arrive à destination d’un port de cette plage. conntrack la fait correspondre à l’entrée, puis le port source d’origine est rétabli avant la remise du paquet. Là encore, aucune seconde règle n’est nécessaire.

Une conséquence pratique est que la modification d’une règle NAT ne déplace pas les connexions déjà établies, car leur traduction est déjà enregistrée. Elles conservent l’ancien comportement jusqu’à l’expiration de leurs entrées. sudo conntrack -D -p tcp --dport 8080 supprime les entrées correspondantes et sudo conntrack -F les supprime toutes. Utilisez la seconde commande avec précaution sur un équipement NAT, car ces traductions enregistrées maintiennent les connexions actuelles. Les vider interrompt donc simultanément toutes les connexions qui traversent l’équipement.

ufw et Docker écrivent chacun leurs propres règles

ufw est une interface pour iptables, qui est lui-même, sur Ubuntu, une interface pour nftables. Un serveur utilisant ufw possède donc une table ip filter contenant des chaînes nommées ufw-before-input, ufw-user-input, etc., ainsi qu’une copie ip6 filter de la même structure. Affichez-les avec sudo nft list ruleset | grep ufw. Ces chaînes sont générées à partir des fichiers de /etc/ufw, et ufw reload les réécrit entièrement. C’est pourquoi une règle iptables ajoutée manuellement disparaît au prochain rechargement. Les bases d’ufw pour un VPS présentent cette organisation des fichiers.

Docker configure lui-même le pare-feu et ne consulte pas ufw. La publication d’un port avec -p 80:80 écrit une règle DNAT dans la table nat et une règle d’acceptation dans le chemin de forwarding. Ces deux règles s’exécutent avant les chaînes utilisateur d’ufw. Le résultat surprend toujours la première fois : ufw deny 80 est chargé, mais le conteneur reste accessible depuis Internet. La solution consiste à utiliser la chaîne DOCKER-USER que Docker réserve à vos règles. L’article Pourquoi les conteneurs Docker ignorent ufw l’explique en détail. Vérifiez la configuration actuelle avec sudo nft list ruleset | grep -i docker.

Relisez maintenant la ligne flush ruleset de la configuration précédente. Elle supprime toutes les tables, y compris celles gérées par ces deux outils. Sur un serveur Docker, les ports publiés cessent de fonctionner jusqu’à ce que sudo systemctl restart docker reconstruise les chaînes. Cette ligne est la cause la plus fréquente d’une interruption des propres services lors du nettoyage de la configuration du pare-feu.

Règles conservées après un redémarrage

Aucun des deux rule sets n’est persistant par lui-même. Le kernel oublie tout à l’arrêt, et chaque solution utilise un package distinct.

Pour nftables, /etc/nftables.conf est lu par nftables.service. Ubuntu fournit ce service désactivé. Vérifiez donc son état avant de lui faire confiance.

systemctl is-enabled nftables
sudo nft -c -f /etc/nftables.conf
sudo systemctl enable --now nftables

Pour iptables, le package s’appelle iptables-persistent. Il installe netfilter-persistent et enregistre les règles dans /etc/iptables/rules.v4 et /etc/iptables/rules.v6.

sudo apt install -y iptables-persistent
sudo netfilter-persistent save

N’exécutez pas les deux. Deux fichiers qui prétendent chacun contenir le firewall vont diverger. Celui qui se charge en dernier prend effet, sans que vous puissiez le déterminer en lisant l’un ou l’autre fichier.

Le dump d’un ruleset actif présente un piège similaire. sudo nft -s list ruleset > /etc/nftables.conf capture tout ce qui est chargé à cet instant, y compris les tables de ufw et de Docker. Si vous restaurez ce dump au démarrage, vous obtenez une copie figée des règles que ces outils sont censés construire eux-mêmes, puis une seconde copie lorsqu’ils démarrent. Exportez uniquement votre propre table avec sudo nft -s list table inet filter. Le flag -s exclut les compteurs, qui n’ont pas leur place dans un fichier de configuration.

Faut-il activer votre VPS ?

Laissez ufw tranquille tant que vous n’avez pas besoin d’une fonction qu’il ne permet pas d’exprimer. ufw couvre le cas courant d’un VPS : une politique de refus par défaut avec quelques ports ouverts. Remplacer cette configuration par un ruleset écrit à la main, sans autre raison, vous donne le même firewall avec un élément supplémentaire à maintenir.

Passez aux outils natifs lorsque vos besoins sortent du modèle d’ufw : NAT et redirection de ports, sets mis à jour à chaud, une seule règle couvrant les deux familles d’adresses, ou priorités de chaînes que vous définissez vous-même. Ce sont de vraies raisons, et ufw ne permet pas de les exprimer.

Si vous passez aux outils natifs, faites-le complètement. Exécutez sudo ufw disable et sudo systemctl disable --now ufw, vérifiez avec sudo nft list ruleset que ses tables ont disparu, puis chargez votre propre fichier. Un serveur qui exécute ufw et une table écrite à la main continue de laisser passer le trafic, mais la policy active est désormais l’union de deux rulesets évalués dans un ordre déterminé par le démarrage des services, et personne ne peut déduire du contenu de l’un ou l’autre fichier ce que fait réellement le serveur.

Migration d’un jeu de règles iptables existant

iptables-translate convertit une règle et affiche sa forme nftables. Il ne modifie rien sur le serveur.

iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT
nft add rule ip filter INPUT tcp dport 22 counter accept

iptables-restore-translate -f /etc/iptables/rules.v4 fait la même chose pour un jeu de règles enregistré complet. Considérez sa sortie comme une première version. La conversion est mécanique et s’effectue règle par règle. Vous récupérez donc les anciens noms de tables et de chaînes, deux jeux de règles distincts pour IPv4 et IPv6, ainsi qu’aucun des sets qui justifiaient la migration. Réécrivez-le manuellement sous la forme d’une seule table inet, puis vérifiez-la avec nft -c -f avant de l’utiliser sur un serveur en production.

Les adresses de ces exemples proviennent des plages réservées à la documentation 203.0.113.0/24 et 198.51.100.0/24, et enp1s0 est un nom d’interface. Utilisez les valeurs obtenues avec ip route show default et ip -br addr au lieu de copier les miennes, car les images Ubuntu récentes nomment rarement une interface eth0.

FAQ

iptables est-il obsolète sur Ubuntu ?

La commande ne disparaît pas et fonctionne toujours sur Ubuntu 24.04. Ce qui a changé, c’est ce qui se passe en dessous : iptables est une interface qui écrit des règles nftables via le backend iptables-nft. Vérifiez le vôtre avec iptables -V, qui affiche iptables v1.8.10 (nf_tables) sur 24.04. L’ancien backend x_tables est toujours fourni sous le nom iptables-legacy et utilise un jeu de règles complètement distinct. Ajoutez donc les règles dans un seul backend, pas dans les deux.

Dois-je ajouter une deuxième règle pour annuler le NAT au retour ?

Non. Le suivi des connexions enregistre la traduction lorsque le premier paquet d’une connexion correspond à une règle nat. Tous les paquets suivants, dans les deux directions, sont réécrits à partir de cette entrée enregistrée. sudo conntrack -L l’affiche sous la forme de deux tuples par connexion : la direction d’origine, puis la réponse déjà inversée. Une règle écrite pour le sens retour ne peut pas aider, car les paquets de retour n’atteignent jamais une chaîne nat.

Puis-je utiliser ufw et mes propres règles nftables en même temps ?

Cela fonctionne, mais vous vous exposez à un problème. Chaque chaîne de base attachée à un hook est exécutée. La politique active combine donc les deux jeux de règles, selon leur priorité et, à priorité égale, selon le service qui a démarré en premier. Un drop dans l’un ou l’autre est définitif. Un accept dans vos règles n’empêche pas l’autre jeu de règles de supprimer le même paquet. Choisissez un seul outil. Si vous utilisez nftables, désactivez d’abord ufw et vérifiez que ses tables ont disparu avec sudo nft list ruleset.

Comment faire pour que les règles nftables soient conservées après un redémarrage sur Ubuntu ?

Placez le jeu de règles dans /etc/nftables.conf, vérifiez-le avec sudo nft -c -f /etc/nftables.conf, puis exécutez sudo systemctl enable --now nftables. Le service n’est pas activé par défaut. Il est donc utile d’exécuter systemctl is-enabled nftables une fois. Lorsque vous générez ce fichier, exportez uniquement votre propre table avec sudo nft -s list table inet filter, car un export complet avec list ruleset inclut également les tables que ufw et Docker gèrent eux-mêmes.