Network

Comparatif firewalls Linux 2026 : ufw, nftables, iptables

Sur cette page
  1. Pourquoi quatre outils de firewall en 2026
  2. Le noyau en dessous : netfilter vs nf_tables
  3. iptables : l'interface historique
  4. nftables : l'ABI moderne du noyau
  5. firewalld : le daemon d'état dynamique
  6. ufw : le front-end lisible par un humain
  7. Les réglages par défaut, distrib par distrib
  8. Recette : la même règle dans les quatre syntaxes
  9. Comparatif performances et fonctionnalités
  10. Schémas de migration
  11. Arbre de décision pour une nouvelle installation
  12. Les pièges classiques
  13. Sources et lectures complémentaires

Quatre outils de firewall, une seule machine Linux, tous en production en 2026, et un comparatif des firewalls Linux reste le seul moyen de ne pas les confondre. Il y a iptables le vieux cheval de trait, nftables le vrai remplaçant côté noyau, firewalld le daemon à zones sur chaque machine RHEL, et ufw le front-end Debian et Ubuntu qui se lit comme de l'anglais courant. Personne ne le dit tout haut, mais ils tapent tous exactement sur le même noyau. Chacun enrobe netfilter ou nf_tables, sans exception. Du coup je les aligne, j'écris le même firewall quatre fois en quatre dialectes, j'explique comment lâcher les anciens, puis je te dis lequel je dégaine sur une machine fraîche. Le verdict : nftables gagne pour presque tout ce qui n'est pas RHEL.

The short answer

iptables, nftables, firewalld et ufw sont quatre portes d'entrée vers le même filtre de paquets du noyau. Sur tout ce qui est récent, ils écrivent tous des règles nf_tables au final, même quand tu tapes la vieille syntaxe iptables à travers le shim iptables-nft. Choisis selon la distrib et le boulot : ufw pour une machine Debian ou Ubuntu simple, firewalld là où Red Hat le fait déjà tourner, nftables pour tout le reste.

4 outilsun seul noyau en dessous
nf_tablesle seul backend moderne
nftablesmon choix par défaut
Carte réponse : iptables, nftables, firewalld et ufw sont quatre front-ends qui écrivent tous vers un seul backend noyau nf_tables sur une machine Linux moderne.
Quatre portes d'entrée, un seul noyau en dessous. Quoi que tu tapes, c'est nf_tables qui filtre sur tout ce qui est récent. PNG

Quatre outils de firewall. Une seule machine Linux. Tous actifs, tous en production, en 2026. Voilà le bazar dans lequel tu débarques. Il y a iptables, le vieux cheval de trait que mes doigts tapent encore sans demander la permission. Il y a nftables, le vrai remplaçant que les gens du noyau ont construit, par défaut depuis quelque part entre 2018 et 2020 selon la distrib à qui tu poses la question. Sur chaque machine RHEL tu croiseras firewalld, le daemon à base de zones. Ah, et ufw, le front-end Debian/Ubuntu sympa qui se lit comme de l'anglais courant. Personne ne le dit tout haut, mais ils tapent tous exactement sur le même noyau. Chacun d'entre eux est un wrapper autour de netfilter ou de nf_tables, sans exception. Du coup je vais les aligner, écrire le même firewall quatre fois dans quatre dialectes, expliquer comment lâcher les anciens, puis te dire lequel je dégaine vraiment sur une machine fraîche. Et comme je déteste enterrer le verdict : je pense que nftables gagne pour presque tout ce qui n'est pas RHEL. Les raisons plus loin.

Pourquoi quatre outils de firewall en 2026

C'est l'histoire qui veut ça. Toute la réponse tient là-dedans, en vrai. iptables est arrivé avec Linux 2.4 en 2001 comme le visage de netfilter, et puis il a juste... gagné. Vingt ans en monopole. Ce qui veut dire vingt ans de tutos, de runbooks, de rôles Ansible et de réponses Stack Overflow à moitié justes empilés derrière lui comme des couches de sédiments. Quiconque a commencé avant 2018 le tape les yeux fermés. Moi le premier, et je n'en suis pas fier. Puis l'équipe netfilter en a eu marre de tout ce qu'iptables ne savait pas faire et a commencé à construire nftables en 2014 comme le véritable héritier. Le noyau a basculé vers le backend nf_tables au fil de la série 4.x, sans tambour ni trompette, et les distribs ont suivi vers 2018-2020 (Debian 10, RHEL 8, Ubuntu 20.04, openSUSE Tumbleweed). Et voilà le passage qui embrouille tout le monde. La commande iptables marche encore sur quasiment toutes les machines. Mais sur la plupart d'entre elles, c'est le shim iptables-nft, qui réécrit en douce ta vieille syntaxe en règles nftables dans ton dos. Tape iptables -L sur Rocky 9 et tu regardes des règles nftables déguisées en iptables. Tu t'es fait avoir.

firewalld et ufw sont arrivés plus tard, pensés pour les gens qui préfèrent ne pas écrire leurs chaînes à la main. firewalld (2011, Red Hat) pose un daemon par-dessus le noyau et te file des zones, des services nommés, ce fameux découpage runtime/permanent, plus une API D-Bus. C'est le seul des quatre qui garde réellement un processus tournant en arrière-plan, aux aguets. ufw (2008, Ubuntu) a pris la direction inverse, complètement. C'est un mince wrapper shell par-dessus iptables qui ramène le quotidien à des one-liners. Publics différents. Même objectif, à peu près : un firewall sans la paperasse des chaînes.

Le noyau en dessous : netfilter vs nf_tables

Décolle les quatre front-ends et il te reste une seule chose dans le noyau qui fait le vrai boulot : le filtre de paquets. Deux générations cohabitent là-dedans, côte à côte.

  • x_tables : le vieux framework netfilter derrière iptables, ip6tables, ebtables et arptables. Un module par famille de protocoles. Un jeu de tables figé. Et ça devient pataud dès que tu veux exprimer quoi que ce soit de composé.
  • nf_tables : la nouvelle mouture. Un seul framework qui couvre v4, v6, bridge et arp, avec une petite VM bytecode qui tourne dans le noyau et des swaps atomiques. Les performances tiennent quand ton nombre de règles devient vraiment délirant.

Sur n'importe quoi de récent, nf_tables est le seul backend que le noyau fait réellement tourner. Peu importe ce que tu as tapé. iptables, iptables-legacy, iptables-nft, firewall-cmd, ufw, nft, tous sont des outils en espace utilisateur qui finissent par écrire des règles nf_tables. Une exception. Installe iptables-legacy volontairement, à côté du backend nft, et te revoilà à programmer pour de vrai la vieille structure x_tables. Elle est toujours là. Elle est aussi en train de couler doucement, donc honnêtement, je ne miserais pas dessus pour quoi que ce soit de neuf.

iptables : l'interface historique

Le modèle mental, c'est tables et chaînes. iptables range les règles dans des tables (filter, nat, mangle, raw) puis dans des chaînes à l'intérieur : INPUT, OUTPUT, FORWARD pour la table filter, plus toutes les chaînes custom que tu bricoles toi-même. Une règle regarde un paquet. Puis elle l'accepte, le drop, le rejette, ou le renvoie vers une autre chaîne. C'est tout le jeu, en gros.

# List current rules
sudo iptables -L -n -v --line-numbers

# Allow SSH from anywhere
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Set default policy to drop
sudo iptables -P INPUT DROP

# Save rules persistently (Debian/Ubuntu via iptables-persistent package)
sudo netfilter-persistent save

# RHEL 7 style
sudo service iptables save

Ce qu'iptables a pour lui, c'est la mémoire musculaire et une montagne de docs. Quel que soit le truc bizarre que tu essaies de faire, quelqu'un l'a documenté en 2014 et le post marche encore. Ce qu'il n'a pas, c'est les éditions atomiques. Chaque -I ou -D modifie le ruleset vivant sur place, donc il y a toujours cette demi-seconde où tu es coincé quelque part entre l'ancienne policy et la nouvelle. Rate une étape et tu t'es verrouillé dehors. La syntaxe devient verbeuse à toute vitesse dès qu'une règle a besoin de plus d'une condition. Et pousse le nombre de règles au-delà de quelques milliers ? La courbe de performance se plie dans le mauvais sens, et pas en douceur.

nftables : l'ABI moderne du noyau

nftables garde tables et chaînes, mais les assouplit et y greffe les morceaux qu'iptables n'a jamais eus. Tu nommes tes propres tables et tu les rattaches à une famille de protocoles. Tu y déposes des chaînes en disant exactement à quel hook chacune s'accroche. Les règles vont dans les chaînes, comme toujours. Le truc que j'adore vraiment, par contre ? Les sets et les maps sont des citoyens de première classe. Tu regroupes une pile d'adresses, de ports ou d'interfaces sous un seul nom et tu y fais simplement référence. Cette seule fonctionnalité change la façon d'écrire les règles, point.

# /etc/nftables.conf, full example
#!/usr/sbin/nft -f
flush ruleset

table inet filter {
  set allowed_tcp {
    type inet_service
    elements = { 22, 80, 443 }
  }

  chain input {
    type filter hook input priority 0;
    policy drop;

    iif lo accept
    ct state established,related accept
    tcp dport @allowed_tcp accept
    ip protocol icmp accept
    ip6 nexthdr icmpv6 accept
  }
}

# Apply
sudo nft -f /etc/nftables.conf

# List
sudo nft list ruleset

Alors, pourquoi basculer ? Le remplacement atomique, déjà. Tout le ruleset est échangé d'un seul coup, aucun état intermédiaire dans lequel se faire piéger. Les sets transforment « autorise ces 17 ports » en une seule ligne au lieu de dix-sept règles séparées. La famille inet permet à une seule règle de couvrir IPv4 et IPv6 d'un coup, donc tu arrêtes de maintenir deux firewalls parallèles qui finissent par diverger avec le temps. Ça monte à des dizaines de milliers de règles sans s'effondrer comme le fait iptables. Il y a un hic, et il est légitime. La syntaxe est plus récente, donc la pile de runbooks et de tutos derrière elle est encore plus mince que celle d'iptables. Certains jours, c'est toi qui écriras la doc au lieu de la lire.

firewalld : le daemon d'état dynamique

firewalld apporte deux ou trois idées que les autres n'ont tout simplement pas. Les zones, d'abord : chaque interface ou connexion appartient à l'une d'elles, et la zone porte la policy par défaut, donc « home » et « public » peuvent vouloir dire des choses très différentes sur le même portable. Ensuite les services, des paquets nommés de port plus protocole, donc tu dis ssh au lieu de mémoriser tcp/22. Et le gros morceau, runtime versus permanent, qui mord littéralement tout le monde au moins une fois. Tout ce que tu ajoutes sans --permanent prend effet tout de suite puis s'évapore en silence au prochain reload ou reboot. On reparlera de cette mine précise plus loin.

# Inspect current state
sudo firewall-cmd --get-default-zone
sudo firewall-cmd --list-all

# Allow SSH, HTTP, HTTPS persistently
sudo firewall-cmd --zone=public --add-service=ssh --permanent
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --zone=public --add-service=https --permanent

# Custom port range
sudo firewall-cmd --zone=public --add-port=8080-8090/tcp --permanent

# Apply
sudo firewall-cmd --reload

Là où firewalld justifie son salaire : il change les règles à la volée sans tout vider comme le fait iptables au moment d'un save. Le modèle de zones a pile la bonne forme pour les portables nomades et les machines à plusieurs cartes réseau. L'API D-Bus fait que des outils graphiques peuvent le piloter aussi. Là où il me frustre, c'est l'abstraction. Il cache ce qui s'écrit réellement, donc dès que quelque chose cloche, tu débogues sur deux couches à la fois, les zones firewalld en haut et le ruleset nft en bas. Et sur un serveur dépouillé ? Ce daemon en arrière-plan, c'est juste une chose de plus qui peut tomber à 3 h du mat pendant que tu dors.

ufw : le front-end lisible par un humain

Si tout ce que tu veux c'est « SSH et HTTPS ouverts, le reste fermé », ufw t'y amène en six lignes environ et après tu n'y penses plus :

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
sudo ufw enable

# Verify
sudo ufw status verbose

# Rate-limit (built-in)
sudo ufw limit 22/tcp

ufw a des opinions, et c'est tout l'intérêt. Il arrange les choses pour que tu n'aies jamais à penser aux chaînes, aux tables, au NAT ou aux cibles de saut. Le bénéfice, c'est la courbe d'apprentissage la plus douce des quatre, plus le workflow « ouvre ce port et que ça reste » le plus rapide que tu trouveras où que ce soit. Le prix, c'est le vocabulaire. Dès que tu veux quelque chose au-delà du simple allow ou deny sur un port, ufw hausse juste les épaules et tu redescends de toute façon vers iptables ou nft bruts. Il refuse aussi de partager un hôte avec firewalld sans se battre, donc n'essaie même pas de faire tourner les deux. Choisis-en un. Désactive l'autre.

Les réglages par défaut, distrib par distrib

Voilà ce que chaque distrib de notre Distro Reference te file vraiment à l'installation. Et « à l'installation » porte beaucoup de sous-entendus dans cette phrase, donc lis le tableau attentivement :

DistroDefault firewall front-endKernel backend
Ubuntu 24.04 LTSufw (not enabled by default)nf_tables
Debian 12 BookwormNone pre-installed (nftables.service available)nf_tables
Fedora 40firewalld (enabled)nf_tables
Rocky 9 / Alma 9 / RHEL 9firewalld (enabled)nf_tables
Arch LinuxNone pre-installednf_tables (available via nftables package)
openSUSE Leap 15.5firewalld (enabled, since Leap 15.0)nf_tables
Alpine Linux 3.19None pre-installednf_tables (via nftables package)

Deux choses dans ce tableau ont brûlé des gens que je connais personnellement. Ubuntu livre ufw mais le laisse éteint. Un serveur frais n'a aucun firewall qui tourne tant que tu n'as pas réellement tapé ufw enable, et plein de monde suppose que « ufw est installé » veut dire « ufw fait quelque chose ». Ce n'est pas le cas. Il est juste posé là. Arch et Alpine vont plus loin et ne livrent rien du tout, donc une machine que tu viens de lancer a chaque port en écoute grand ouvert sur tout internet tant que tu n'as pas installé et activé un firewall toi-même.

Critique : une machine Arch ou Alpine fraîche est exposée sur chaque port en écoute à l'instant où elle boote. Installe et active un firewall avant que cet hôte ne touche un réseau public. Pas après. Pas « dans une minute ».

Recette : la même règle dans les quatre syntaxes

Le même boulot, quatre fois de suite. La base que je vise : claquer la porte sur tout le trafic entrant sauf SSH (22), HTTP (80), HTTPS (443), plus les réponses aux connexions qu'on a ouvertes nous-mêmes. Ce dernier point fait constamment trébucher les gens, donc il est intégré aux quatre versions exprès.

iptables

sudo iptables -P INPUT DROP
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
sudo iptables -A INPUT -p icmp -j ACCEPT
sudo netfilter-persistent save

nftables

cat | sudo tee /etc/nftables.conf <<'EOF'
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
  chain input {
    type filter hook input priority 0;
    policy drop;
    iif lo accept
    ct state established,related accept
    tcp dport { 22, 80, 443 } accept
    ip protocol icmp accept
    ip6 nexthdr icmpv6 accept
  }
}
EOF
sudo systemctl enable --now nftables
sudo nft -f /etc/nftables.conf

firewalld

sudo firewall-cmd --zone=public --set-target=DROP --permanent
sudo firewall-cmd --zone=public --add-service=ssh --permanent
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --zone=public --add-service=https --permanent
sudo firewall-cmd --reload

ufw

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Résultat identique. Quatre ressentis complètement différents. La version nft est la plus serrée du lot, grâce entièrement à ce set { 22, 80, 443 } posé sur une seule ligne. firewalld se lit comme si tu décrivais la config à voix haute à un collègue. ufw, c'est celui que tes doigts terminent en premier, sans débat. Et iptables ? C'est le plus honnête sur ce qui arrive réellement à chaque paquet, chaque étape épelée, rien de planqué. Lequel est « le meilleur » dépend entièrement de ce qui te tient à cœur. Ma réponse attend tout en bas, et honnêtement je ne pense pas que ce soit si serré.

La même règle de firewall en deny par défaut (SSH, HTTP, HTTPS plus les réponses établies) écrite quatre fois, en syntaxe iptables, nftables, firewalld et ufw.
Un boulot, quatre dialectes. Même résultat, ressenti très différent : nft est le plus serré, ufw le plus rapide, iptables le plus explicite. PNG

Comparatif performances et fonctionnalités

Featureiptablesnftablesfirewalldufw
Atomic rule replacementNoYesYes (via daemon)No (uses iptables under hood)
Sets and mapsNoYesLimited (ipset)No
IPv4 + IPv6 in one ruleNo (two tools)Yes (inet family)Yes (transparent)Yes (parallel rules)
Dynamic API (state daemon)NoNo (rules are static config)Yes (D-Bus)No
Zones / location awarenessNoNoYesNo
Performance at 10k+ rulesSlow (linear)FastDepends on backend (now nft)Inherits from iptables
Learning curveSteep (deep)MediumMedium (concepts)Lowest
Ecosystem (docs, runbooks)HugeGrowingSolid in RHEL worldSolid in Debian/Ubuntu

Schémas de migration

iptables vers nftables

Il existe un convertisseur officiel, iptables-restore-translate. File-lui ton dump iptables-save et il en ressort le ruleset nftables correspondant. Traite cette sortie comme un brouillon grossier, par contre, pas comme un produit fini. Ça marche, mais ça traduit règle par règle, bêtement, donc tu voudras repasser dessus pour replier les choses dans des sets et des maps à la main si tu veux le vrai bénéfice :

sudo iptables-save > /tmp/rules-v4.txt
sudo ip6tables-save > /tmp/rules-v6.txt
iptables-restore-translate -f /tmp/rules-v4.txt > /tmp/rules.nft
ip6tables-restore-translate -f /tmp/rules-v6.txt >> /tmp/rules.nft

# Review the output, edit as needed, then load
sudo nft -f /tmp/rules.nft
sudo systemctl disable --now iptables  # remove the old service
sudo systemctl enable --now nftables

ufw vers firewalld (ou l'inverse)

Ces deux-là ne cohabiteront pas, donc il n'y a pas d'astuce ici. Désolé. Tu désactives l'un, tu installes l'autre, tu reconstruis les règles de zéro. Personne n'a écrit de convertisseur de syntaxe et personne ne le fera jamais, parce que les deux pensent les firewalls dans des formes complètement différentes : des règles de port à plat d'un côté, des zones de l'autre. Donc recrée juste ce que tu avais en t'appuyant sur l'antisèche de la section recette plus haut. Fastidieux, oui, mais pas difficile.

Ancien format iptables-save vers config nftables permanente sur Debian/Ubuntu

Tu trimballes encore un vieux /etc/iptables/rules.v4 de l'époque iptables-persistent ? Sur Debian 12 ou Ubuntu 24.04, la façon la plus propre de t'en débarrasser, c'est ça :

sudo apt install nftables iptables-nftables-compat
sudo iptables-restore-translate -f /etc/iptables/rules.v4 > /etc/nftables.conf
sudo systemctl enable --now nftables
sudo systemctl disable netfilter-persistent

Arbre de décision pour une nouvelle installation

  • Un seul serveur Debian ou Ubuntu, quelques ports, une équipe qui vit sur ses antisèches choisit ufw. Le moins à retenir. La façon la plus rapide d'ouvrir un port et de repartir sans y repenser.
  • N'importe quoi dans la famille RHEL (Rocky, Alma, RHEL, Fedora) choisit firewalld, et je le dis un peu à contrecoeur. Il est déjà là, déjà en train de tourner. Nager à contre-courant du défaut ne paie quasiment jamais, et j'ai vu de mes propres yeux quelqu'un cramer une journée entière à le démontrer.
  • Hôte de conteneurs, noeud K8s, partout où les règles bougent tout le temps choisit nftables, sans hésiter. Les swaps atomiques et les performances à l'échelle sont précisément ce dont ce boulot a besoin.
  • Un portable nomade (VPN, captive portals, un réseau différent chaque jour) choisit firewalld avec ses zones. C'est le seul scénario pour lequel il a réellement été conçu, donc recule et laisse-le faire son travail.
  • Gestion de parc, des runbooks partout, infrastructure as code choisit nftables, facile. Un nftables.conf déclaratif tombe d'un template tout propre et se diffe comme n'importe quel fichier normal dans git.
  • Une vieille machine ancestrale avec des centaines de règles iptables que personne ne veut toucher reçoit iptables-restore-translate passé dessus, puis maintiens le résultat en nftables à partir de là. Arrache le pansement une bonne fois et n'en parlons plus.

Les pièges classiques

  • Policy par défaut à DROP sans règle allow sur le loopback : ça verrouille l'hôte hors de lui-même, ce qui est son propre genre de misère. Toujours -A INPUT -i lo -j ACCEPT (iptables) ou iif lo accept (nft) avant de toucher à la policy par défaut.
  • Oublier la règle established/related : les connexions sortantes marchent, mais les réponses ne reviennent jamais. Toujours autoriser state established,related sur la chaîne input. Toujours.
  • Conflit ufw et Docker : Docker écrit ses propres règles iptables et nft qui passent tout droit à côté d'ufw. Attrape l'utilitaire ufw-docker, ou configure Docker avec --iptables=false si tu tiens vraiment à ce qu'ufw mène la barque pour le trafic des conteneurs.
  • firewalld muet sans --permanent : les règles ajoutées sans --permanent disparaissent après un reload ou un reboot, et il n'y a aucun avertissement. Aucun. Prends l'habitude de toujours coupler --permanent avec --reload.
  • Plusieurs outils de firewall sur un même hôte : ufw et firewalld et iptables brut qui se donnent tous des coups de coude. Gardes-en un et coupe les autres pour de bon avec sudo systemctl mask.
  • IPv6 oublié : une règle qui ouvre TCP/22 en v4 peut laisser SSH grand ouvert en v6 si tu n'as jamais écrit la règle v6 correspondante. La famille inet de nftables règle juste le problème. iptables t'oblige à maintenir iptables et ip6tables à la main, et c'est exactement comme ça que les gens oublient.
  • Persistance non configurée : les règles que tu as appliquées avec iptables -A disparaissent à la seconde où tu reboot, sauf si tu les as réellement sauvegardées. Chaque distrib a son propre mécanisme de save (iptables-persistent, nftables.service, firewall-cmd --reload), donc vérifie le tien.

Sources et lectures complémentaires

Questions fréquentes

Faut-il apprendre nftables en 2026 ?

Oui. Le noyau a tourné la page, et nftables est ce qui tourne en dessous de tout maintenant. Tu peux continuer à t'appuyer sur iptables-nft pour tes vieilles règles, d'accord, mais connaître la syntaxe nft native paie vraiment dès que tu lis de la doc moderne ou que tu dois déboguer pourquoi un ruleset refuse de se charger. Et une fois que les sets et les maps font tilt, iptables arrête de te manquer.

Est-ce qu'iptables est en train de disparaître ?

La commande iptables reste là encore des années, simplement à cause du nombre de systèmes qui la font encore tourner. Le backend x_tables en dessous est techniquement déprécié mais toujours maintenu pour la compatibilité. Mon pronostic honnête (et c'est un pronostic) : iptables-nft reste utilisable jusqu'en 2030, tandis que iptables-legacy se fait virer de la plupart des distribs autour de 2028. Je peux me tromper d'un an dans un sens ou dans l'autre.

Puis-je utiliser ufw sur RHEL ou firewalld sur Ubuntu ?

Techniquement, oui. ufw vit dans EPEL pour RHEL, et firewalld est dans le dépôt universe d'Ubuntu. Ni l'un ni l'autre n'est le chemin de moindre résistance, par contre. Le vrai coût, c'est de faire tourner un outil que le reste de l'écosystème ne s'attend pas à trouver là. Les docs supposent que tu as pris l'autre voie. Les paquets aussi, et la mémoire musculaire de tes collègues encore plus.

Et pf, dans tout ça ?

pf est réservé à BSD (OpenBSD, FreeBSD, macOS). Il n'existe tout simplement pas sur Linux. Si tu viens de BSD et que tes doigts continuent de chercher pfctl, le modèle mental le plus proche par ici, c'est nftables : déclaratif, à base de tables et de chaînes, avec le remplacement atomique dont tu as l'habitude. Commande différente, même état d'esprit.

Ai-je encore besoin de fail2ban avec un vrai firewall ?

Oui, tu en as besoin. Le firewall bloque sur le port et l'IP source. Il ne regarde jamais à l'intérieur du contenu des connexions déjà établies. fail2ban (et ses successeurs) surveille plutôt les logs applicatifs, puis injecte des règles de blocage temporaires dès qu'un motif d'échecs apparaît. fail2ban lit les logs, le firewall lit les paquets. Il te faut les deux.