Gérer des centaines de règles nftables sans perdre la tête en 2026
Dès que les règles nftables dépassent la centaine, l'administration du firewall devient un cauchemar : règles orphelines, priorités qui se marchent dessus, scripts shell qui écrasent la configuration lors d'une mise à jour. Cette situation est pourtant courante dans les infrastructures hébergeant plusieurs services, clusters Kubernetes, ou serveurs de jeu multi-port. Cet article vous montre comment organiser proprement 100+ règles nftables sans perdre le contrôle.
Pourquoi les règles nftables deviennent chaotiques à grande échelle
Contrairement à iptables, nftables ne dispose pas de chaînes par défaut ni de priorités implicites. Chaque administrateur doit construire sa propre architecture de tables, chaînes, priorités et hooks. À petite échelle, un fichier unique `/etc/nftables.conf` suffit ; à grande échelle, ce modèle implose pour trois raisons principales :
- Aucun découpage logique : règles métier (services publics), règles de protection (anti-DDoS), règles temporaires (blocages IP) et règles d'intégration tiers (Docker, fail2ban) cohabitent dans le même bloc, rendant toute modification risquée.
- Conflits de priorités : netfilter exécute les chaînes dans l'ordre des hooks et des priorités numériques. Sans convention claire, deux chaînes de priorité identique provoquent un ordre non déterministe ou écrasent mutuellement leurs verdicts.
- Explosion combinatoire : dès que plusieurs dimensions (port, IP source, état de connexion, rate-limit, payload matching) se croisent, le nombre de lignes explose. Un fichier monolithique de 300 lignes est illisible et non-versionnable proprement.
Le chaos monte silencieusement : on commente une ligne « temporaire » qui reste 18 mois, on duplique une règle pour tester, on oublie de supprimer l'ancienne. Le firewall tourne, mais personne ne sait exactement pourquoi ni ce qui arriverait si on supprimait telle ligne.
Stratégie d'organisation : séparer table métier, table de protection et table temporaire
La première brique d'une architecture scalable consiste à isoler les responsabilités dans des tables différentes. Nftables autorise plusieurs tables dans la même famille (inet, ip, ip6), chacune avec ses propres chaînes et priorités. Voici une trame type :
Table métier (famille inet)
table inet services {
chain input {
type filter hook input priority filter; policy drop;
# Règles de base (loopback, established)
iif lo accept
ct state established,related accept
# Services applicatifs
tcp dport 22 accept # SSH
tcp dport {80, 443} accept # HTTP/HTTPS
tcp dport 25565 accept # Minecraft
tcp dport 30000-30100 accept # Cluster serveurs de jeu
udp dport 27015 accept # Source Engine query
# Drop par défaut
counter drop
}
}
Cette table concentre les décisions métier : quels ports ouvrir, quels services publier. Aucune logique de rate-limit ou de blocage IP ici. La priorité `filter` (0) signifie qu'elle s'exécute après les hooks de prérouting et après les chaînes de priorité plus basse (négative).
Table de protection (famille inet, priorité raw)
table inet pakkt {
set blacklist_v4 {
type ipv4_addr; flags interval;
}
set blacklist_v6 {
type ipv6_addr; flags interval;
}
chain prerouting {
type filter hook prerouting priority raw; policy accept;
# Blacklist niveau IP
ip saddr @blacklist_v4 counter drop
ip6 saddr @blacklist_v6 counter drop
# Anti-SYN flood global
tcp flags syn tcp dport {25565, 30000-30100} limit rate over 200/second counter drop
# Anti-UDP flood par port
udp dport 27015 limit rate over 5000/second counter drop
}
chain input {
type filter hook input priority filter + 10; policy accept;
# Rate-limit par connexion (meter + burst)
tcp flags syn meter syn_meter { ip saddr limit rate 10/second burst 20 packets } accept
tcp flags syn counter drop comment "SYN flood par IP source"
# Invalid packets
ct state invalid counter drop
}
}
La priorité `raw` (-300) fait exécuter la blacklist avant le suivi de connexion (conntrack), économisant CPU et mémoire. La chaîne input de la table `pakkt` a une priorité `filter + 10`, donc elle s'exécute après la table services : ce qui a déjà été accepté passe, ce qui reste subit le rate-limit strict. L'usage de meter permet un rate-limit par clé (ici IP source), avec burst pour éviter les faux positifs.
Table temporaire (pour intégrations, blocages manuels)
table inet temp {
set manual_block {
type ipv4_addr; flags timeout;
}
chain input {
type filter hook input priority filter - 10; policy accept;
# IPs bloquées manuellement (timeout 24h par défaut)
ip saddr @manual_block counter drop
}
}
Priorité `filter - 10` pour que ces blocages s'appliquent avant les règles métier, mais après la table de protection. Le flag `timeout` permet d'ajouter une IP avec expiration automatique :
nft add element inet temp manual_block { 203.0.113.42 timeout 1d }
Résultat : aucune modification du fichier `/etc/nftables.conf`, le blocage expire seul. Idéal pour les intégrations fail2ban ou les commandes d'urgence depuis un panel comme PAKKT.io.
Modulariser la configuration avec include et snippets
Une fois les tables séparées, le fichier principal `/etc/nftables.conf` devient un squelette :
#!/usr/sbin/nft -f
flush ruleset
include "/etc/nftables.d/table-services.nft"
include "/etc/nftables.d/table-pakkt.nft"
include "/etc/nftables.d/table-temp.nft"
Chaque fichier `.nft` contient la définition complète d'une table. Avantages concrets :
- Versionnement propre : un diff Git montre exactement quelle table a changé. Les merge conflicts se résolvent plus facilement.
- Rechargement partiel :
nft -f /etc/nftables.d/table-temp.nftrecharge uniquement cette table (attention : cela écrase la table existante, pensez à flush avant ou utiliseznft delete tablepuisnft -f). - Génération dynamique : un script peut construire
table-services.nftdepuis une base de données, un inventaire Ansible, ou un panel d'hébergement. PAKKT, par exemple, génère automatiquement les règles nftables depuis son agent Go lorsqu'un utilisateur active un port protégé via le dashboard. - Documentation inline : chaque fichier commence par un commentaire expliquant le périmètre, les priorités, les dépendances. Exemple :
# Table pakkt : protection kernel-level, priorité raw + filter+10, dépend de XDP pour le premier étage de filtrage.
Poussez la modularité plus loin en découpant les services par application :
# /etc/nftables.d/services/minecraft.nft
add rule inet services input tcp dport 25565 ct state new limit rate 50/second accept
add rule inet services input tcp dport 25565 ct state new counter drop comment "Rate-limit Minecraft conn/s"
# /etc/nftables.d/services/web.nft
add rule inet services input tcp dport {80, 443} accept
Puis incluez-les tous depuis table-services.nft :
table inet services {
chain input {
type filter hook input priority filter; policy drop;
iif lo accept
ct state established,related accept
}
}
include "/etc/nftables.d/services/*.nft"
⚠️ Attention : l'ordre d'inclusion des wildcards n'est pas déterministe. Si l'ordre des règles compte (verdict terminal avant règle générique), nommez vos fichiers avec préfixe numérique : 10-minecraft.nft, 20-web.nft.
Gérer les priorités et les verdicts pour éviter les conflits
Nftables exécute les chaînes dans l'ordre : hook (prerouting, input, forward, output, postrouting), puis priorité numérique croissante, puis ordre de création à priorité égale (non fiable). Voici les priorités standard :
| Nom | Valeur | Usage typique |
| raw | -300 | Avant conntrack, blacklist/whitelist IP |
| mangle | -150 | Modification de paquets (DSCP, TTL) |
| dstnat | -100 | DNAT (prerouting) ou SNAT (postrouting) |
| filter | 0 | Règles de filtrage classiques |
| security | 50 | SELinux, AppArmor |
| srcnat | 100 | SNAT en postrouting |
Convention recommandée pour 100+ règles :
- Priorité raw : table de protection (blacklist IP, anti-flood brutal). Verdict
dropouacceptterminal pour court-circuiter le reste. - Priorité filter - 50 : whitelist IP ou allow-only rules (par exemple, admin SSH depuis un range fixe). Verdict
acceptprotège contre les erreurs dans les chaînes suivantes. - Priorité filter : table métier services. Policy
drop, chaque service accepte son port. Verdictsacceptoudropterminaux. - Priorité filter + 10 : rate-limit fin par connexion, state invalid drop, logging. Priorité plus haute pour intercepter ce qui a échappé aux règles métier (cas des wildcards ou erreurs).
Évitez d'utiliser return dans les chaînes de type filter : return revient à la chaîne appelante, mais dans un hook de base il équivaut à continue (passe à la chaîne suivante de même priorité ou de priorité supérieure). Préférez accept ou drop explicites.
Exemple de conflit et résolution
Imaginons deux chaînes de priorité filter :
# Chaîne A (table services)
chain input {
type filter hook input priority filter; policy drop;
tcp dport 25565 accept
}
# Chaîne B (table temp)
chain input {
type filter hook input priority filter; policy accept;
ip saddr 203.0.113.42 drop
}
Ordre d'exécution indéterminé à priorité égale. Si A s'exécute en premier, le paquet TCP/25565 depuis 203.0.113.42 sera accepté, la chaîne B ne verra jamais le paquet (verdict terminal). Résolution : donner à B une priorité filter - 10 pour qu'elle s'exécute avant A. Ainsi, l'IP est bloquée avant même d'atteindre les règles métier.
Automatisation : synchroniser nftables avec une source de vérité externe
Au-delà de 100 règles, la configuration manuelle devient impossible. Deux approches cohabitent :
Approche déclarative : Ansible, Terraform, scripts CI/CD
Stockez la configuration souhaitée dans un dépôt Git (YAML, JSON, ou HCL). Un playbook Ansible génère les fichiers .nft puis recharge le firewall :
- name: Generate nftables services config
template:
src: table-services.nft.j2
dest: /etc/nftables.d/table-services.nft
notify: reload nftables
- name: Reload nftables
command: systemctl reload nftables.service
Avantages : audit trail complet (chaque commit = une modification), rollback facile (revert Git + redeploy), validation pre-commit (linter nftables, tests de syntaxe). Inconvénient : latence entre la décision (créer un port) et l'application (pipeline CI/CD de 2-5 minutes).
Approche impérative : agent en temps réel
Un daemon local surveille une source externe (API, base de données) et modifie nftables à la volée via nft add rule ou nft add element. C'est le modèle de PAKKT.io : l'agent Go reçoit les règles depuis le panel central via WebSocket, les injecte dans les BPF maps XDP et dans les sets nftables en moins d'une seconde. Exemple concret :
// Agent Go : ajout d'une IP en blacklist
cmd := exec.Command("nft", "add", "element", "inet", "pakkt", "blacklist_v4", fmt.Sprintf("{ %s }", ip))
if err := cmd.Run(); err != nil {
log.Printf("Failed to blacklist %s: %v", ip, err)
}
L'agent PAKKT maintient également un fichier de backup /etc/pakkt/nftables-pakkt-backup.nft rechargé au boot, garantissant la persistance même si le panel est indisponible. La synchronisation bidirectionnelle (agent → panel, panel → agent) évite les conflits : toute modification manuelle via nft est détectée au prochain heartbeat (30s) et soit écrasée soit remontée dans le dashboard selon la politique configurée.
Validation avant application
Testez toujours la syntaxe avant de recharger en production :
nft -c -f /etc/nftables.d/table-services.nft
Le flag -c (check) valide sans appliquer. Intégrez ce test dans votre pipeline CI ou dans le pré-commit Git. Pour les règles dynamiques (sets, meters), testez aussi la cohérence des types :
nft add element inet temp manual_block { 2001:db8::1 }
Si le set manual_block est de type ipv4_addr, la commande échoue. Prévoyez un set dual-stack ou deux sets distincts manual_block_v4 / manual_block_v6.
Surveillance et debugging : meters, counters, logs structurés
Une fois les règles déployées, encore faut-il savoir ce qu'elles font réellement. Nftables offre trois primitives de monitoring :
Counters
tcp dport 25565 counter name "minecraft_in" accept
Inspectez le compteur :
nft list counter inet services minecraft_in
Sortie : counter packets 142384 bytes 8934272. Attention, les counters nftables ne sont pas réinitialisés automatiquement. Pour des métriques time-series, exportez-les vers Prometheus (exporter nftables) ou TimescaleDB (approche PAKKT).
Meters (rate-limit dynamique)
tcp flags syn meter syn_meter { ip saddr limit rate 10/second burst 20 packets } accept
Le meter stocke l'état par clé (ici IP source). Listez les entrées actives :
nft list meter inet pakkt syn_meter
Purge manuelle :
nft flush meter inet pakkt syn_meter
Utile après une attaque pour réinitialiser les quotas sans reboot.
Logs structurés
tcp dport 25565 limit rate 10/minute log prefix "[nft-minecraft] " drop
Les logs apparaissent dans /var/log/kern.log ou le journal systemd. Pour éviter l'inondation (log storm), encadrez log par un limit. Préférez un prefixe unique par règle pour faciliter le parsing (rsyslog, Loki). PAKKT collecte ces logs via son agent, les agrège par port/règle et les affiche dans le dashboard avec filtrage temps réel.
Outils de debugging avancés
nft monitor: affiche en temps réel les ajouts/suppressions de règles et éléments. Utile pour débugger un script qui modifie nftables.nft -a list table inet services: affiche les handles (identifiants uniques de règles). Indispensable pour supprimer une règle précise :nft delete rule inet services input handle 42.nftrace=1: active le tracing noyau pour un paquet. Ajoutez une meta nftrace dans une règle, puisnft monitor tracepour voir le parcours complet du paquet dans les chaînes. Gourmand en CPU, réservé au debug ponctuel.
Conclusion
Organiser 100+ règles nftables sans chaos repose sur trois piliers : isoler les responsabilités dans des tables distinctes avec priorités claires, modulariser la configuration en fichiers versionnés, et automatiser la synchronisation avec un agent ou un pipeline CI/CD. L'intégration de counters, meters et logs structurés transforme le firewall en source de métriques exploitables, offrant visibilité et réactivité face aux attaques. Une architecture bien pensée aujourd'hui évite la réécriture complète demain.
FAQ
Peut-on mélanger règles nftables manuelles et règles générées par un agent automatique sans conflit ?
Oui, à condition de séparer les responsabilités dans des tables différentes. Par exemple, placez les règles manuelles dans une table inet custom avec une priorité bien définie (par exemple filter - 20), et laissez l'agent gérer une table dédiée (comme inet pakkt). L'agent PAKKT, par exemple, n'interfère jamais avec les tables existantes : il crée sa propre table inet pakkt et ne flush que celle-ci lors des mises à jour. Documentez clairement les priorités dans un fichier README à la racine de /etc/nftables.d/.
Comment éviter qu'un rechargement nftables casse les connexions établies (SSH, sessions TCP longues) ?
Utilisez systemctl reload nftables.service plutôt que restart. Le reload fait un nft -f /etc/nftables.conf qui remplace atomiquement le ruleset, mais le module conntrack du noyau préserve les états de connexion. Assurez-vous que vos chaînes incluent ct state established,related accept en premier, avant toute règle spécifique. Ainsi, les connexions en cours passent immédiatement, seules les nouvelles connexions subissent les nouvelles règles. Testez toujours la nouvelle configuration sur un serveur de staging avant de la déployer en production.
Quelle est la limite pratique du nombre de règles nftables sur un serveur moderne avant dégradation de performance ?
Nftables est linéaire : chaque paquet traverse les règles une par une jusqu'à un verdict terminal. En pratique, 500-1000 règles restent gérables (latence ajoutée ~10-50 µs par paquet sur un Xeon récent). Au-delà, privilégiez les sets (lookup O(log n) via rbtree ou O(1) via hash) plutôt que des dizaines de règles individuelles. Par exemple, remplacez 200 lignes ip saddr X drop par un set @blacklist de 200 entrées + une seule règle ip saddr @blacklist drop. Pour la première ligne de défense (filtrage brut par IP/port), XDP surpasse nftables : l'intégration XDP + nftables de PAKKT gère plusieurs millions de pps en combinant filtrage stateless XDP (sub-microseconde) et filtrage stateful nftables sur le trafic légitime uniquement.
Déployez PAKKT en 30 secondes
Protection kernel double couche XDP + nftables, pilotable depuis un panel centralisé. Essai gratuit 7 jours.