Pare-feu Linux : la disposition nftables claire et lisible à 500 règles

Cet article vous a aidé ?

On ne remarque pas un pare-feu tant que ça ne fait pas mal. Généralement à 02:13, quand « un petit changement » transforme SSH en fantôme,
votre téléphone d’astreinte en alarme incendie, et votre PDG en ingénieur réseau à mi-temps.

nftables peut être élégant et rapide — mais seulement si vous cessez de le traiter comme un iptables en plus grand.
Voici une disposition de production qui reste lisible quand l’ensemble de règles dépasse 500 entrées, survit aux audits,
et vous permet de déboguer comme un adulte.

Pourquoi la disposition compte plus que la syntaxe

nftables n’est pas difficile à cause de sa syntaxe. Il est difficile parce que votre ensemble de règles est un système vivant :
il croît par accumulation, il est modifié sous pression, et il est lu par des personnes qui ne l’ont pas écrit
(y compris Vous du Futur, qui est le relecteur le moins indulgent qui soit).

Lorsque vous dépassez ~200 règles, deux choses se produisent :

  • Le risque lié aux changements monte en flèche. Un petit changement peut faire passer le chemin d’un paquet de « accept » à « drop » sans signal de diff évident.
  • Le temps de débogage explose. Vous cessez de raisonner sur la politique et vous commencez à greper des chaînes et prier.

Le remède est la structure. Pas la « structure de commentaires partout ». Une vraie structure :
frontières de chaînes prévisibles, nommage strict, ensembles/maps pour l’identité, et un ruleset qui reflète réellement comment les paquets circulent.

Avis : si votre fichier nftables est un blob unique de 1 200 lignes, vous n’avez pas un pare-feu. Vous avez un futur rapport d’incident.

Quelques faits et contexte historique (pour que vous arrêtiez de répéter les erreurs de 2012)

Points de contexte courts et concrets qui comptent en production :

  1. nftables est entré dans le noyau Linux en 3.13 (2014). Ce n’est plus « nouveau » ; vos mauvaises habitudes le sont.
  2. iptables est réellement une interface vers netfilter. nftables est une interface plus récente, conçue pour remplacer la famille iptables et réduire la duplication.
  3. nftables utilise une approche de bytecode de type VM. C’est pourquoi les règles peuvent être exprimées de manière compacte, et pourquoi les ensembles/maps peuvent réduire radicalement le nombre de règles.
  4. iptables avait historiquement des outils séparés pour IPv4/IPv6/arptables/ebtables. nftables les unifie, ce qui est excellent — jusqu’à ce que vous « unifiiez » accidentellement deux politiques qui devraient rester séparées.
  5. Conntrack (suivi d’état) prédate largement nftables. nftables n’a pas inventé l’état ; il a rendu son application plus simple et plus cohérente.
  6. nftables prend en charge le remplacement atomique du ruleset. C’est de l’or opérationnel : vous pouvez charger un nouveau ruleset comme une transaction, pas une séquence d’éditions risquée.
  7. Les ensembles ont été une percée en performance et lisibilité. Au lieu de 200 lignes « ip saddr X accept », vous obtenez une règle et une définition d’ensemble.
  8. nftables offre de meilleurs primitives d’introspection. Compteurs, handles et listings structurés sont conçus pour le débogage et les outils — pas seulement pour les humains qui regardent du texte.

Une citation que les équipes opérations devraient tatouer sur leur processus de changement :
L’espoir n’est pas une stratégie. — General Gordon R. Sullivan

Principes de conception qui montent au-delà de 500 règles

1) Un chemin de paquet, une seule histoire

Un ruleset lisible raconte l’histoire d’un paquet. Par exemple : paquet entrant touche input → contrôles de sanity →
established/related → « services autorisés » → logs limités → drop.

Si votre chaîne input alterne entre « autoriser nginx » et « bloquer bogons » et « autoriser monitoring » et « drop fragments » sans ordre,
vous forcez le lecteur à simuler toute la chaîne dans sa tête. Ça ne s’échelonne pas.

2) Refus par défaut aux frontières de chaîne, pas éparpillé partout

« Refus par défaut » n’est pas « drop des paquets en 90 endroits ». C’est une décision politique unique et délibérée à la fin d’un chemin
(ou comme politique de chaîne), avec les exceptions placées en avant.

Éparpiller des règles drop au hasard rend l’audit impossible et crée des « politiques fantômes » que personne ne se rappelle.

3) Utilisez des sets/maps pour l’identité ; utilisez des chaînes pour le comportement

Les sets et maps sont votre budget de lisibilité. Vous les dépensez pour garder les règles courtes.
Les chaînes expriment le comportement et l’ordre. Si vous faites l’inverse (chaînes pour l’identité, règles pour le comportement),
vous finirez par un désordre combinatoire.

4) Séparez « edge » de « host de service » de « transit »

Le moyen le plus rapide de créer un ruleset cassé est de mélanger la logique de routeur (forwarding/NAT), la logique d’hôte (services locaux),
et la logique « cette machine est aussi un endpoint VPN » dans une seule chaîne.

Utilisez des tables séparées ou au moins des fichiers include séparés. Rendez impossible de changer accidentalement le NAT en « ouvrant juste un port ».

5) Soyez explicite sur ce qui est autorisé à parler à la machine elle-même

La plupart des douleurs en production viennent du trafic plane de contrôle : SSH, gestion de configuration, monitoring, synchronisation horaire, découverte de services.
Traitez-le comme une politique de première classe, pas comme une pensée après coup.

6) La journalisation doit être utile ou ne pas exister

Les tempêtes de logs n’« aident » pas au débogage. Elles gonflent votre facture SIEM.
Loggez uniquement aux points de décision, limitez le débit, et incluez un préfixe que vous pouvez greper.

Blague #1 : Si votre pare-feu logge tout, félicitations — vous avez inventé un générateur de nombres aléatoires très coûteux.

7) Préférez les rechargements atomiques, et testez sérieusement

Les changements en production devraient suivre : valider → dry-run (ou au moins parser) → appliquer de façon atomique → vérifier compteurs/comportement.
Pas « éditer en direct dans un terminal et espérer que la session TCP reste ».

8) N’allez pas après les micro-optimisations avant de savoir expliquer votre ruleset

nftables est rapide. Votre vrai goulot n’est généralement pas « deux comparaisons en plus », c’est « personne ne sait quelle règle est active ».
Optimisez d’abord pour l’opérabilité. Vos incidents futurs vous remercieront.

Disposition de référence : fichiers, tables, chaînes et nommage

Organisation des fichiers (la partie que les auditeurs adorent)

Gardez /etc/nftables.conf minuscule. Il doit charger vos vraies règles depuis un répertoire.
Cela facilite les revues, permet la propriété partielle (l’équipe réseau possède le fichier NAT, l’équipe plateforme possède le fichier services),
et évite des conflits de merge qui ressemblent à de la soupe de spaghettis.

  • /etc/nftables.conf — point d’entrée
  • /etc/nftables.d/00-defs.nft — constantes, sets, maps, noms d’interfaces
  • /etc/nftables.d/10-filter-base.nft — chaînes de base : squelette input/output/forward
  • /etc/nftables.d/20-filter-services.nft — autorisations de services (ingress vers services locaux)
  • /etc/nftables.d/30-filter-management.nft — SSH/monitoring/gestion de configuration
  • /etc/nftables.d/40-nat.nft — NAT (uniquement si nécessaire)
  • /etc/nftables.d/90-debug.nft — règles de debug optionnelles (désactivées par défaut)

Conventions de nommage qui survivent aux équipes

Les noms ne sont pas de la vanité. Ce sont le seul moyen de déboguer rapidement sans appeler la personne qui a écrit les règles il y a trois ans.
Utilisez des préfixes prévisibles :

  • Tables : inet filter, ip nat (et ip6 nat seulement si nécessaire)
  • Chaînes de base : input, output, forward
  • Chaînes utilisateur : in_sanity, in_established, in_allow_mgmt, in_allow_services, in_log_drop
  • Sets : préfixer par set_ et décrire le contenu : set_mgmt_v4, set_bogon_v4, set_allowed_tcp_services
  • Maps : préfixer par map_ et décrire la correspondance : map_if_trust, map_service_ports

Pourquoi « inet filter » est la table par défaut

Utilisez table inet filter pour le filtrage d’hôte quand c’est possible. Vous obtenez une politique unique pour IPv4 et IPv6,
moins de règles dupliquées, et moins d’incidents « on a oublié qu’IPv6 existait ».

Mais ne soyez pas dogmatique : le NAT reste typiquement dans les familles ip/ip6.
Gardez le NAT séparé du filtrage. Les mélanger, c’est comme ranger une tronçonneuse dans le tiroir à couverts.

Structure des chaînes : petites, ciblées et ennuyeuses

Les chaînes de base doivent être fines et sauter principalement vers des sous-chaînes. Les sous-chaînes doivent chacune avoir un seul rôle.
Quand vous faites cela, les compteurs et traces deviennent significatifs : vous pouvez voir à quel stade le trafic est abandonné.

Un exemple de ruleset propre (avec includes)

Voici une organisation qui reste lisible à 500 règles parce que le nombre de règles vit dans des sets, pas dans des lignes répétées.
Elle garde aussi le débogage prévisible parce que le flux de paquets est structuré en étapes.

cr0x@server:~$ sudo sed -n '1,160p' /etc/nftables.conf
#!/usr/sbin/nft -f

flush ruleset

include "/etc/nftables.d/*.nft"
cr0x@server:~$ sudo sed -n '1,260p' /etc/nftables.d/00-defs.nft
define IF_WAN = "eth0"
define IF_LAN = "eth1"
define IF_LOOP = "lo"

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

  set set_mgmt_v6 {
    type ipv6_addr
    flags interval
    elements = { 2001:db8:100::/64 }
  }

  set set_bogon_v4 {
    type ipv4_addr
    flags interval
    elements = { 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16,
                 172.16.0.0/12, 192.0.2.0/24, 192.168.0.0/16, 198.18.0.0/15, 203.0.113.0/24, 224.0.0.0/4, 240.0.0.0/4 }
  }

  set set_allowed_tcp_services {
    type inet_service
    elements = { 22, 80, 443, 9100 }
  }

  set set_allowed_udp_services {
    type inet_service
    elements = { 123 }
  }
}
cr0x@server:~$ sudo sed -n '1,260p' /etc/nftables.d/10-filter-base.nft
table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;

    iifname $IF_LOOP accept

    jump in_sanity
    jump in_established
    jump in_allow_mgmt
    jump in_allow_services
    jump in_log_drop
  }

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

    jump fwd_sanity
    jump fwd_established
    jump fwd_allow
    jump fwd_log_drop
  }

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

    jump out_sanity
  }

  chain in_sanity {
    ct state invalid drop

    ip saddr @set_bogon_v4 drop
    ip6 saddr ::/128 drop

    meta l4proto { tcp, udp, icmp, icmpv6 } accept
    drop
  }

  chain in_established {
    ct state { established, related } accept
  }

  chain in_log_drop {
    limit rate 10/second burst 20 packets log prefix "nft in drop " flags all counter
    drop
  }

  chain out_sanity {
    ct state invalid drop
  }

  chain fwd_sanity {
    ct state invalid drop
  }

  chain fwd_established {
    ct state { established, related } accept
  }

  chain fwd_log_drop {
    limit rate 10/second burst 20 packets log prefix "nft fwd drop " flags all counter
    drop
  }
}
cr0x@server:~$ sudo sed -n '1,260p' /etc/nftables.d/30-filter-management.nft
table inet filter {
  chain in_allow_mgmt {
    tcp dport 22 ip saddr @set_mgmt_v4 accept
    tcp dport 22 ip6 saddr @set_mgmt_v6 accept
  }
}
cr0x@server:~$ sudo sed -n '1,300p' /etc/nftables.d/20-filter-services.nft
table inet filter {
  chain in_allow_services {
    tcp dport { 80, 443 } accept
    tcp dport 9100 accept
    udp dport 123 accept
  }
}

Remarques sur l’exemple :

  • Les chaînes de base sont courtes. Elles sautent vers des chaînes mises en scène, ce qui rend le flux de paquets facile à narrer.
  • Politique drop sur input/forward. Output est accept par défaut pour la plupart des serveurs ; verrouillez-le seulement si vous en avez une raison.
  • La chaîne de sanity est stricte. Elle droppe les invalides tôt et gère les bogons. Elle a aussi une « allowlist » de protocoles pour accepter rapidement les primitives L4, donc les absurdités sont rejetées vite.
  • Le trafic de management est isolé. Les règles SSH sont dans une chaîne dédiée avec des restrictions de source.
  • La journalisation est limitée en débit et tardive. Vous loggez seulement les drops en fin de chemin input/forward.

Blague #2 : Les règles de pare-feu « temporaires » ont le même cycle de vie que les tables « temporaires » dans la base de données : elles survivent à votre équipe.

Tâches pratiques : commandes, sorties et décisions

Ce sont de vraies tâches que vous ferez sur de vraies machines. Chacune inclut la commande, une sortie réaliste,
ce que signifie la sortie, et la décision que vous prenez.

Task 1: Confirm nftables is active and which loader your distro uses

cr0x@server:~$ systemctl status nftables
● nftables.service - nftables
     Loaded: loaded (/lib/systemd/system/nftables.service; enabled; preset: enabled)
     Active: active (exited) since Tue 2026-02-04 09:11:22 UTC; 2h 13min ago
       Docs: man:nft(8)
    Process: 612 ExecStart=/usr/sbin/nft -f /etc/nftables.conf (code=exited, status=0/SUCCESS)
   Main PID: 612 (code=exited, status=0/SUCCESS)

Signification : Le service a appliqué le ruleset et s’est terminé proprement (commun pour nftables).

Décision : Si le service est inactive/failed, réparez la gestion du service d’abord ; déboguer des règles sans loader cohérent, c’est le chaos.

Task 2: Dump the active ruleset exactly as the kernel sees it

cr0x@server:~$ sudo nft list ruleset
table inet filter {
	chain input {
		type filter hook input priority filter; policy drop;
		iifname "lo" accept
		jump in_sanity
		jump in_established
		jump in_allow_mgmt
		jump in_allow_services
		jump in_log_drop
	}
	chain in_allow_mgmt {
		tcp dport 22 ip saddr @set_mgmt_v4 accept
		tcp dport 22 ip6 saddr @set_mgmt_v6 accept
	}
	chain in_log_drop {
		limit rate 10/second burst 20 packets log prefix "nft in drop " flags all counter
		drop
	}
}

Signification : C’est la vérité. Pas votre fichier, pas votre repo git — le ruleset actif dans le noyau.

Décision : Si la sortie ne correspond pas à vos includes attendus, vous avez un mismatch de loader ou un déploiement de configuration obsolète.

Task 3: Validate syntax before you apply it (prevent self-lockout)

cr0x@server:~$ sudo nft -c -f /etc/nftables.conf

Signification : Pas de sortie et code de sortie 0 veut dire que le parse/check a réussi.

Décision : Si la validation échoue, corrigez cela d’abord ; ne « testez pas en direct ». Si vous devez appliquer à distance, la validation est non négociable.

Task 4: Apply changes atomically and verify success

cr0x@server:~$ sudo nft -f /etc/nftables.conf

Signification : Là encore, l’absence de sortie signifie généralement succès.

Décision : Suivez immédiatement par des vérifications de compteurs (Task 7) et des tests d’accès aux services. « Chargé » n’est pas « correct ».

Task 5: Show chain handles (for precise deletions, audits, and tooling)

cr0x@server:~$ sudo nft -a list chain inet filter input
table inet filter {
	chain input { # handle 1
		type filter hook input priority filter; policy drop;
		iifname "lo" accept # handle 5
		jump in_sanity # handle 6
		jump in_established # handle 7
		jump in_allow_mgmt # handle 8
		jump in_allow_services # handle 9
		jump in_log_drop # handle 10
	}
}

Signification : Les handles sont des identifiants stables pour les règles dans le ruleset actif.

Décision : Si vous devez supprimer une règle chirurgicalement pendant un incident, utilisez les handles, pas le comptage de lignes approximatif.

Task 6: List sets and confirm they loaded as intended

cr0x@server:~$ sudo nft list set inet filter set_mgmt_v4
table inet filter {
	set set_mgmt_v4 {
		type ipv4_addr
		flags interval
		elements = { 198.51.100.0/24, 198.51.100.10 }
	}
}

Signification : Vos données d’identité sont présentes. Les sets d’intervalle peuvent coalescer des entrées.

Décision : Si une autorisation de management dépend de ce set, confirmez-le avant d’accuser le réseau d’un problème SSH.

Task 7: Check counters to see what’s actually being hit

cr0x@server:~$ sudo nft list chain inet filter in_log_drop
table inet filter {
	chain in_log_drop {
		limit rate 10/second burst 20 packets log prefix "nft in drop " flags all counter packets 41 bytes 2870
		drop
	}
}

Signification : 41 paquets ont frappé le logger de drop. Ce n’est pas théorique — quelque chose est refusé.

Décision : Si les compteurs augmentent après un déploiement, isolez le changement de politique. Si les compteurs sont à zéro mais les utilisateurs se plaignent, le problème est ailleurs (routage, appli, ACL en amont).

Task 8: Verify conntrack state behavior (common source of “it works once” bugs)

cr0x@server:~$ sudo conntrack -S
cpu=0 found=18231 invalid=12 ignore=0 insert=40121 insert_failed=0 drop=0 early_drop=0 error=0 search_restart=0

Signification : Vous avez des paquets invalides (12). Un peu d’invalides est normal sur Internet ; des nombres énormes suggèrent un routage asymétrique ou un offload cassé.

Décision : Si invalid augmente rapidement, enquêtez sur la symétrie du routage et les réglages d’offload ; n’acceptez pas simplement les invalides pour faire joli sur les graphiques.

Task 9: Use nft monitor during a change window

cr0x@server:~$ sudo nft monitor
add rule inet filter in_allow_services tcp dport 8443 counter accept

Signification : Vue live des règles ajoutées/supprimées. Idéal pour vérifier que l’automatisation a fait ce qu’elle prétend.

Décision : Si vous voyez un churn inattendu, arrêtez et revoyez les outils de déploiement ; les boucles de configuration peuvent thrash votre ruleset.

Task 10: Trace a packet path (the fastest way to find “which rule ate it”)

cr0x@server:~$ sudo nft add rule inet filter input meta nftrace set 1
cr0x@server:~$ sudo nft monitor trace
trace id 3c2a inet filter input packet: iif "eth0" ip saddr 203.0.113.55 ip daddr 198.51.100.20 tcp sport 51234 tcp dport 22
trace id 3c2a inet filter input rule jump in_sanity
trace id 3c2a inet filter in_sanity verdict accept
trace id 3c2a inet filter input rule jump in_established
trace id 3c2a inet filter input rule jump in_allow_mgmt
trace id 3c2a inet filter in_allow_mgmt verdict drop

Signification : Cette trace montre le chemin d’évaluation. Le paquet n’est pas dans votre set de sources de management, donc il ne correspond pas aux règles d’accept et tombe ensuite vers le drop.

Décision : Vous ajoutez la source à set_mgmt_v4 (si légitime) ou vous dites à l’utilisateur « non, c’est voulu ».

Task 11: Watch kernel logs for rate-limited firewall drops

cr0x@server:~$ sudo journalctl -k -n 5
Feb 04 11:22:19 server kernel: nft in drop IN=eth0 OUT= MAC=52:54:00:aa:bb:cc SRC=203.0.113.55 DST=198.51.100.20 LEN=60 TOS=0x00 PREC=0x00 TTL=49 ID=53113 DF PROTO=TCP SPT=51234 DPT=22 WINDOW=64240 RES=0x00 SYN URGP=0
Feb 04 11:22:20 server kernel: nft in drop IN=eth0 OUT= MAC=52:54:00:aa:bb:cc SRC=203.0.113.8 DST=198.51.100.20 LEN=52 TOS=0x00 PREC=0x00 TTL=51 ID=0 DF PROTO=TCP SPT=60211 DPT=443 WINDOW=1024 RES=0x00 SYN URGP=0

Signification : Votre préfixe de journal fonctionne, et vous pouvez corréler avec les plaintes de service.

Décision : Si vous voyez du trafic légitime droppé, corrigez les règles d’autorisation. Si vous voyez de la garbage, gardez la journalisation minimale et envisagez un filtrage en amont.

Task 12: Confirm you didn’t forget IPv6

cr0x@server:~$ ip -6 addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
    inet6 2001:db8:200::20/64 scope global
       valid_lft forever preferred_lft forever

Signification : L’hôte a une adresse IPv6 globale. Si vous n’avez écrit que des règles IPv4, vous pouvez avoir exposé des services via v6 — ou les avoir bloqués par accident.

Décision : Utilisez la table inet pour le filtrage, confirmez les sources v6 dans les sets de management, et autorisez/denyez explicitement les services sur IPv6 selon l’intention.

Task 13: Confirm that a port is actually listening before blaming the firewall

cr0x@server:~$ sudo ss -lntp | head
State  Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
LISTEN 0      4096   0.0.0.0:22         0.0.0.0:*     users:(("sshd",pid=811,fd=3))
LISTEN 0      4096   0.0.0.0:80         0.0.0.0:*     users:(("nginx",pid=1002,fd=6))
LISTEN 0      4096   0.0.0.0:443        0.0.0.0:*     users:(("nginx",pid=1002,fd=7))

Signification : Les services écoutent sur IPv4. Si les utilisateurs ne peuvent pas se connecter, le pare-feu peut être en cause — ou le routage/groupes de sécurité/ACL en amont.

Décision : Si le service n’écoute pas, corrigez le service. S’il écoute, continuez avec les compteurs/trace nft et les vérifications en amont.

Task 14: Spot rule bloat by counting rules and hunting duplicates

cr0x@server:~$ sudo nft list ruleset | wc -l
892

Signification : Le comptage de lignes est une métrique grossière, mais c’est un test olfactif. Si vous attendiez 250 lignes et que vous avez 892, vous avez probablement des règles dupliquées ou du contenu généré expansé.

Décision : Convertissez les littéraux répétés en sets/maps, segmentez les includes par domaine, et arrêtez de générer des quasi-doublons dans l’automatisation.

Task 15: Confirm offload/fast path isn’t undermining visibility

cr0x@server:~$ ethtool -k eth0 | egrep 'gro|gso|tso|rx-checksumming|tx-checksumming'
rx-checksumming: on
tx-checksumming: on
tcp-segmentation-offload: on
generic-segmentation-offload: on
generic-receive-offload: on

Signification : Les offloads sont activés. C’est acceptable, mais certains environnements ont des comportements étranges avec conntrack et certaines encapsulations.

Décision : Si vous voyez beaucoup d’invalides dans conntrack ou des traces bizarres, testez la désactivation d’offloads spécifiques pendant une fenêtre de maintenance — pas comme une superstition aléatoire.

Playbook de diagnostic rapide

Quand vous êtes sous pression, vous ne « relisez pas le ruleset ». Vous lancez une boucle courte qui trouve rapidement le goulot d’étranglement.
Voici une séquence rapide qui fonctionne pour la plupart des rapports « le trafic est bloqué » ou « le trafic est lent ».

First: prove the service and the socket

  1. Vérifiez si le processus écoute sur l’IP/port attendu (ss -lntp).
  2. Confirmez la connectivité locale (curl vers localhost ou nc -vz 127.0.0.1 PORT si applicable).
  3. Confirmez qu’il écoute sur IPv6 si les clients utilisent v6 (ss -lntp | grep '\[::\]:' patterns).

Si le service n’écoute pas, le pare-feu est innocent. Traitez l’innocence comme un actif précieux.

Second: prove packets arrive at the host

  1. Capturez sur l’interface pour une courte fenêtre (tcpdump -ni eth0 port 443).
  2. Si rien n’arrive, enquêtez sur le routage, le pare-feu en amont, la santé du load balancer, ou les security groups.

Third: prove nftables is the decision point

  1. Vérifiez les compteurs sur votre(s) chaîne(s) de drop/log (nft list chain avec compteurs).
  2. Activez une trace courte avec nftrace et nft monitor trace pour localiser l’étape exacte.
  3. Confirmez que les sets/maps contiennent les éléments attendus (IP de management, ports de service).

Fourth: isolate the specific policy break

  1. Le trafic est-il ancien ou nouveau ? Si les flux existants fonctionnent mais que les nouveaux échouent, l’état conntrack ou un nouvel ordre de règle est en cause.
  2. Est-ce IPv4 seulement, IPv6 seulement, ou les deux ? « Les deux » signifie souvent une politique de chaîne ; « un seul » signifie souvent des règles manquantes par famille ou un mauvais usage de inet.
  3. Y a-t-il du NAT ? Si oui, vérifiez les tables NAT séparément ; ne cherchez pas un bug NAT dans les règles filter.

Fifth: fix with minimal blast radius

  1. Privilégiez l’ajout à un set/map existant plutôt que d’ajouter une règle ponctuelle.
  2. Privilégiez l’ajout d’une règle allow dans la chaîne dédiée correcte (mgmt vs services) plutôt que dans la chaîne de base.
  3. Rechargez atomiquement et vérifiez avec des compteurs et un test synthétique.

Erreurs courantes : symptômes → cause racine → correction

1) Symptom: SSH works from the office but not from VPN

Cause racine : L’autorisation management est liée à un set de sources qui n’inclut pas les plages de sortie VPN, ou le VPN utilise IPv6 et vous n’avez autorisé que v4.

Correction : Ajoutez le subnet d’egress VPN à set_mgmt_v4/set_mgmt_v6. Validez avec une trace, puis rechargez atomiquement.

2) Symptom: “It worked after restart, then broke later”

Cause racine : L’état conntrack permettait des flux établis, mais de nouveaux flux tombent sur une nouvelle règle de refus, ou des changements basés sur le temps (DHCP, IP dynamiques) ont déplacé des clients hors des sets autorisés.

Correction : Vérifiez le placement de ct state ; gardez established,related tôt. Pour les IP clients dynamiques, utilisez une identité stable (pool VPN, bastion) plutôt que des plages ISP aléatoires.

3) Symptom: IPv6 exposure or IPv6 outage “out of nowhere”

Cause racine : Vous avez écrit des règles v4 seulement alors que l’hôte a du v6 global, ou vous avez utilisé la table ip pour le filtrage en pensant qu’elle couvre les deux familles.

Correction : Utilisez table inet filter pour le pare-feu d’hôte. Autorisez/denyez explicitement les services sur v6. Vérifiez avec ip -6 addr et nft list ruleset.

4) Symptom: High CPU on a busy edge box after “improving logging”

Cause racine : Journalisation trop tôt dans la chaîne, log des accepts, ou absence de rate limit cause une surcharge de logs kernel et userspace.

Correction : Loggez seulement les drops finaux, limitez le débit, et utilisez des préfixes concis. Si vous avez besoin d’observabilité, utilisez des compteurs et des traces de façon ciblée.

5) Symptom: Packet drops appear random during high traffic

Cause racine : Pression sur la table conntrack, pics d’invalides, ou routage asymétrique rendant des flux établis invalides.

Correction : Vérifiez les stats conntrack (conntrack -S), assurez la symétrie du routage, et n’« accept » pas les invalides comme pansement. Enquêtez sur offload et interactions d’encapsulation.

6) Symptom: Automation keeps “fixing” your manual emergency change

Cause racine : La gestion de configuration réapplique un état désiré sans reconnaître les éditions d’incident.

Correction : Pendant l’incident : mettez à jour la source de vérité repo rapidement (même une branche hotfix), ou mettez temporairement en pause le rôle nftables sur cet hôte. Après l’incident : codifiez le changement d’urgence proprement (idéalement comme éléments de set, pas des règles ad-hoc).

7) Symptom: NAT works for some hosts but not others

Cause racine : NAT et filtrage imbriqués de façon confuse ; forward autorise le trafic mais le masquerade postrouting manque ou est trop restreint.

Correction : Séparez le NAT dans ip nat (et ip6 nat si nécessaire). Vérifiez avec captures et compteurs. Alignez les allows de forward et les règles NAT par interface/subnet d’identité.

Trois mini-histoires d’entreprise (parce que vous reconnaîtrez l’odeur)

Mini-story 1: The incident caused by a wrong assumption

Une entreprise SaaS de taille moyenne est passée des scripts iptables legacy à nftables via une « simple traduction ».
L’équipe a supposé que l’ordre des règles de l’ancien système n’importait pas parce que « ce ne sont que des listes d’autorisation ».
Ils ont aussi supposé qu’IPv6 était sans importance parce que leur load balancer terminait les connexions en IPv4.

Le nouveau ruleset nftables est passé en production pendant un après-midi calme. Il a passé le test de fumée basique :
le service web était joignable, et SSH depuis le bastion fonctionnait. Tout le monde est rentré chez soi rassuré.
Deux heures plus tard, le monitoring a commencé à montrer des échecs sporadiques depuis un sous-ensemble de nœuds dans une région.

Le vrai problème : ces nœuds préféraient IPv6 pour la découverte de services interne. Le pare-feu avait une table filter de famille ip,
et le trafic IPv6 tombait sur un chemin quasi-vide. Certains services étaient exposés involontairement ; d’autres étaient silencieusement bloqués,
selon quel processus était lié à [::].

Le débogage a pris plus de temps qu’il n’aurait dû parce que le ruleset n’était pas mis en scène.
Il n’y avait pas de « chaîne management », pas de « chaîne services », juste une longue liste de règles mélangées. La trace aurait montré la rupture en minutes,
mais personne n’avait un workflow de trace ou un include de debug sûr.

La correction a été ennuyeuse : consolider le filtrage d’hôte dans table inet filter, structurer le flux des chaînes,
et faire d’IPv6 une décision explicite plutôt qu’un effet secondaire accidentel.
La phrase la plus utile du postmortem : « IPv6 n’est pas une option. C’est quelque chose qui existe déjà. »

Mini-story 2: The optimization that backfired

Une équipe de services financiers gérait une tier API à fort trafic et voulait gratter la latence.
Quelqu’un a remarqué « trop de règles » et a décidé de compresser le pare-feu en déplaçant beaucoup de logique dans des maps astucieuses,
faisant des drops agressifs tôt, et loggant chaque rejet pour « meilleure visibilité sécurité ».

Sur le papier, c’était séduisant : moins de lignes et des correspondances structurées.
En réalité, ils ont créé un ruleset que personne ne pouvait lire pendant un incident.
La « map astucieuse » codait plusieurs comportements (allow, drop, log) d’une façon qui exigeait un compilateur mental pour comprendre.

Le retour de bâton est venu sous charge : le volume de logs a grimpé, le temps noyau a augmenté, et la pipeline SIEM a commencé à prendre du retard.
Sous pression, quelqu’un a désactivé la journalisation en entier — en supprimant une chaîne partagée utilisée à trois endroits — parce que la disposition ne séparait pas les préoccupations.
Cela a coupé la visibilité au pire moment.

La récupération a été une refonte : les maps servent uniquement à l’identité (groupes de ports et de sources),
le comportement reste dans des chaînes explicites, et la journalisation est déplacée vers des chaînes de drop en fin de chemin avec des rate limits stricts.
La latence s’est un peu améliorée, mais le vrai gain a été opérationnel : le prochain incident a duré 15 minutes au lieu d’une demi-journée.

Mini-story 3: The boring but correct practice that saved the day

Une grande entreprise gérait des milliers de VMs Linux avec nftables piloté par gestion de configuration.
Leur politique de sécurité changeait trimestriellement, et chaque trimestre la crainte revenait : « ce changement nous verrouillera dehors ».
Une équipe a mis en place une pratique discrète : chaque changement nftables devait être validé avec nft -c,
déployé atomiquement, et vérifié avec des compteurs plus un test de connexion synthétique depuis une sonde connue.

Des mois plus tard, un changement précipité a atterri tard un vendredi (parce que bien sûr).
La nouvelle politique a accidentellement supprimé un port UDP requis pour la synchronisation horaire sur un sous-ensemble d’hôtes.
Le changement a été déployé, et en quelques minutes leur pipeline de vérification a signalé une explosion de compteurs dans la chaîne de drop et des checks synthétiques en échec.

L’astreinte n’a pas eu besoin de deviner. Ils avaient un ruleset mis en scène, donc les compteurs pointaient directement vers in_allow_services manquant UDP 123.
Ils ont mis à jour le set de ports de service, re-validé, et rechargé atomiquement. Le temps d’arrêt a été limité et contenu.
Personne n’a écrit un fil Slack héroïque. C’est comme ça qu’on sait que c’était du bon engineering.

La leçon : la fonctionnalité la plus précieuse d’un pare-feu n’est pas une expression de match. C’est un processus de changement discipliné qui suppose que les humains seront fatigués.

Listes de contrôle / plan étape par étape

Step-by-step: build a readable ruleset that scales

  1. Choisissez les familles de tables : Utilisez inet pour le filtrage, séparez ip/ip6 pour le NAT si nécessaire.
  2. Définissez les interfaces une fois : Utilisez define IF_WAN, IF_LAN, etc. Évitez les chaînes magiques dans les règles.
  3. Créez des sets d’identité : sources de management, sources de monitoring, subnets internes, ports de services autorisés.
  4. Créez des chaînes mises en scène : sanity → established → management → services → log/drop.
  5. Gardez les chaînes de base fines : Les chaînes de base doivent principalement sauter ; ne cachez pas la politique dedans.
  6. Loggez seulement en fin de chemin : Un logger de drop par chemin (input/forward), limité en débit, avec des préfixes cohérents.
  7. Faites respecter l’ordre : Assurez-vous que ct state established,related est tôt. Les invalides sont aussi en début.
  8. Utilisez des rechargements atomiques : Déployez toujours via nft -f après nft -c.
  9. Vérifiez avec des compteurs : Confirmez que les chaînes attendues s’incrémentent sous trafic ; vérifiez que les compteurs de drop restent raisonnables.
  10. Gardez des outils de debug prêts : Maintenez un include de debug désactivé par défaut que vous pouvez activer en urgence (trace, journalisation temporaire).

Checklist: pre-change safety for remote hosts

  • L’accès hors-bande existe (console/ILO/IPMI/série) ou un chemin bastion connu est testé.
  • nft -c -f /etc/nftables.conf réussit.
  • Vous avez une méthode de rollback : le dernier config connu bon disponible localement.
  • Vous ne mélangez pas « refactor NAT » avec « ouvrir un port ».
  • Vous avez une commande de vérification live prête (curl, nc, ou une sonde de monitoring).

Checklist: ongoing hygiene (the stuff that keeps 500 rules readable)

  • Chaque nouvelle règle allow doit atterrir dans la chaîne correcte (mgmt vs services vs transit).
  • Chaque littéral répété (même subnet/groupe de ports) devient un set en deux itérations au plus.
  • Chaque règle de log est limitée en débit et a un préfixe convenu.
  • Les changements de ruleset sont revus avec la « histoire du paquet » en tête : un lecteur peut-il narrer le chemin en 60 secondes ?
  • Auditez trimestriellement : taillez les ports morts, supprimez les plages sources obsolètes, regroupez les doublons en sets.

FAQ

1) Should I use chain policy drop or an explicit final drop rule?

Pour les chaînes de base comme input et forward, une politique de chaîne drop est claire et évidente.
Gardez quand même une chaîne explicite in_log_drop qui journalise et droppe — parce qu’une politique drop ne journalise pas d’elle-même.

2) Why not log every drop in multiple places?

Parce que vous allez vous noyer. Centralisez la journalisation en fin de chemin, limitez le débit, et utilisez des compteurs ailleurs.
Si vous avez besoin de détails, activez une trace temporaire pour une fenêtre courte.

3) Are sets always faster?

La plupart du temps, oui — surtout pour de longues listes d’IPs/ports. Mais le gain le plus important est la maintenabilité.
Les sets vous permettent aussi de mettre à jour l’appartenance sans réécrire la logique des règles.

4) Where do I put “bogon” filtering?

Mettez-le dans une chaîne de sanity tôt dans input (et forward si vous routez).
Gardez la liste sous forme de set, et révisez-la. Ne bloquez pas RFC1918 sur des interfaces internes à moins d’aimer les pannes auto-infligées.

5) How do I avoid accidentally blocking DNS/NTP/monitoring egress?

Commencez par une output policy accept pour la plupart des serveurs.
Si vous devez restreindre l’egress, faites-en un projet séparé avec cartographie complète des dépendances et bonne observabilité.
Verrouiller l’egress sans inventaire est un excellent moyen d’apprendre combien de dépendances « optionnelles » vous avez réellement.

6) What’s the safest way to migrate from iptables to nftables?

Ne faites pas une traduction aveugle. Ré-exprimez l’intention en utilisant des sets et des chaînes mises en scène.
Faites tourner côte à côte dans un environnement contrôlé quand c’est possible, validez avec des tests de trafic, puis basculez avec un chargement atomique.

7) Should I put management access and service access in the same chain?

Non. Séparez-les. Le management est plane de contrôle, les services sont plane de données.
Ils ont des restrictions de source différentes, des exigences d’audit différentes, et des réponses d’incident différentes.

8) How do I debug “some clients can connect, others can’t”?

Vérifiez si les clients en échec partagent une plage source non incluse dans vos sets.
Utilisez nft monitor trace pour voir le mismatch exact. Confirmez si les clients utilisent IPv6.

9) Is nftables stateful by default?

Non. Vous décidez comment utiliser conntrack avec les matches ct state.
La plupart des pare-feux d’hôte en production autorisent established,related tôt et traitent invalid comme drop.

10) How do I keep the ruleset readable when product teams demand “just one more exception”?

Forcez les exceptions dans des données (éléments de set) plutôt que dans la logique (nouvelles règles ad-hoc).
Si l’exception change le comportement, elle doit avoir sa propre chaîne avec un nom qui dit ce que c’est.
La honte est un outil de gouvernance sous-estimé.

Prochaines étapes qui ne gâcheront pas votre week-end

Un ruleset nftables lisible n’est pas une préférence de style. C’est une fonctionnalité de fiabilité.
À 500 règles, vous ne combattez pas des paquets — vous combattez l’entropie.

Faites ceci ensuite :

  1. Séparez votre monolithe en includes : defs, base, mgmt, services, nat, debug.
  2. Convertissez les listes IP/ports répétées en sets. Gardez l’identité dans les sets ; le comportement dans les chaînes.
  3. Structurez le chemin des paquets : sanity → established → allow mgmt → allow services → log/drop.
  4. Adoptez la boucle de changement : nft -c → chargement atomique → vérification compteurs → trace uniquement si nécessaire.
  5. Notez vos conventions de nommage et faites-les respecter en revue. Vous construisez un outil d’astreinte, pas un poème.

Si vous ne faites rien d’autre : rendez l’histoire du paquet évidente. Quand le pager sonne, la clarté est le seul indicateur de performance qui compte.

← Précédent
WSL : Le moyen le plus rapide pour obtenir un véritable environnement de développement sur Windows (sans drame de VM)
Suivant →
Pics CPU toutes les quelques minutes : la tâche planifiée à vérifier en premier

Laisser un commentaire