nftables esiste dal kernel 3.13 (2014), ma è solo nell'ultimo biennio che il consenso si è cristallizzato. Tre ragioni molto concrete spiegano la transizione:
- Una sola utility per tutte le famiglie di protocollo. Dove prima servivano
iptables, ip6tables, arptables ed ebtables, oggi basta nft con la famiglia inet, arp o bridge. Meno tool da ricordare, meno errori di sintassi incrociata.
- Performance nettamente superiori su set di regole grandi. Le strutture dati di nftables (
set, map, verdict map) permettono lookup in O(log n) o O(1), contro la valutazione lineare di iptables. Su un firewall con 50.000 IP bloccati la differenza è di ordini di grandezza — l'ho misurata personalmente e non c'è storia.
- Aggiornamenti atomici. Il comando
nft -f applica un intero set di regole in un'unica transazione: se una riga è invalida, nessuna modifica viene applicata. Con iptables si rischiavano stati intermedi pericolosi (il classico firewall "vuoto" durante il reload, con tutti i servizi improvvisamente esposti). Brutto ricordo.
Anche il progetto netfilter ha ormai ufficializzato la fine vita di iptables-legacy: dal kernel 6.10 (giugno 2025) i moduli x_tables non ricevono più feature, solo bugfix critici. Nuove funzionalità come il connection tracking ottimizzato, il flowtable offload hardware e l'integrazione nativa con nftables-eBPF sono esclusive di nftables. In pratica, restare su iptables oggi significa rinunciare volontariamente a metà del motore.
Installazione e verifica del backend attivo
Sulle distribuzioni recenti, nftables è già installato. Verifica veloce con:
nft --version
# nftables v1.1.5 (Commodore Bullmoose)
systemctl status nftables.service
Se manca (raro, ma può capitare su immagini minimali), installalo e abilita il servizio:
# Debian / Ubuntu
sudo apt update && sudo apt install nftables
sudo systemctl enable --now nftables
# RHEL / Rocky / AlmaLinux
sudo dnf install nftables
sudo systemctl enable --now nftables
# Arch
sudo pacman -S nftables
sudo systemctl enable --now nftables
A questo punto conviene verificare quale backend è effettivamente in uso — passaggio cruciale se stai migrando da una configurazione preesistente:
sudo update-alternatives --display iptables
# /usr/sbin/iptables - status is auto.
# link currently points to /usr/sbin/iptables-nft
# oppure
sudo iptables -V
# iptables v1.8.10 (nf_tables)
Se vedi (nf_tables) nell'output, il sistema sta già usando il backend nftables tramite il wrapper di compatibilità. Questo è il punto di partenza ideale per una migrazione "soft", senza strappi.
Anatomia di una configurazione nftables
nftables ha una gerarchia chiara e prevedibile: tabelle → catene → regole. Una tabella appartiene a una famiglia (ip, ip6, inet, arp, bridge, netdev) e contiene catene; ogni catena è agganciata a un hook netfilter (input, output, forward, prerouting, postrouting) con una priority e una policy di default.
Ecco un esempio minimale di una configurazione "deny all, allow needed" per un server stand-alone — il tipo di base che uso come punto di partenza in quasi tutti i progetti:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Loopback e connessioni stabilite
iif "lo" accept
ct state established,related accept
ct state invalid drop
# ICMP utile (no flood)
ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded } limit rate 5/second accept
ip6 nexthdr icmpv6 icmpv6 type { echo-request, destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
# SSH solo da rete admin
ip saddr 10.0.0.0/24 tcp dport 22 accept
# Web pubblico
tcp dport { 80, 443 } accept
# Logging dei drop (limitato)
limit rate 3/minute log prefix "nft-drop: " level info
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Salva il file in /etc/nftables.conf e applicalo:
sudo nft -c -f /etc/nftables.conf # check syntax
sudo systemctl reload nftables
L'opzione -c (check) è la tua rete di sicurezza: convalida la sintassi senza applicare nulla. Usala sempre prima di un reload remoto, specialmente via SSH. Una regola persa in produzione può chiuderti fuori dal server in meno tempo di quanto tu impieghi a dire "oops".
Sintassi essenziale: matcher, statement, verdict
Una regola nftables, nella sua forma generale, è così:
<matcher> <matcher> ... <statement> <verdict>
I matcher filtrano il pacchetto (tcp dport 443, ip saddr 10.0.0.0/8, ct state new); gli statement compiono azioni intermedie (log, counter, limit, meta mark set); il verdict decide il destino finale (accept, drop, reject, jump, queue). Semplice da leggere una volta interiorizzato.
Ecco alcuni matcher utili che in iptables proprio non c'erano:
| Matcher | Esempio | A cosa serve |
meta l4proto | meta l4proto { tcp, udp } th dport 53 | Match su TCP+UDP in una sola regola |
tcp flags | tcp flags & (fin|syn|rst|ack) == syn | Solo SYN iniziali (anti scan) |
ct count | ct count over 20 reject | Limite connessioni concorrenti per IP |
numgen | numgen inc mod 3 | Load balancing puro su L3/L4 |
jhash | jhash ip saddr mod 16 | Sharding deterministico |
Set, map e verdict map: il vero superpotere
Il principale collo di bottiglia di iptables è sempre stato la valutazione lineare delle regole. nftables introduce invece strutture dati native: set (insiemi), map (chiave→valore) e verdict map (chiave→azione). Possono essere statici o dynamic, cioè modificabili a runtime senza alcun reload. Ed è qui che la magia succede.
Set statici
table inet filter {
set ssh_admins {
type ipv4_addr
elements = { 10.0.0.5, 10.0.0.6, 192.168.50.10 }
}
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
tcp dport 22 ip saddr @ssh_admins accept
}
}
Set dinamici per blocklist anti-bruteforce
Questo pattern, onestamente, sostituisce in modo elegante fail2ban per scenari semplici — senza processo in user-space da monitorare:
table inet filter {
set blackhole {
type ipv4_addr
flags dynamic, timeout
timeout 1h
size 65536
}
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
# Drop immediato se già in blocklist
ip saddr @blackhole drop
# Aggiungi alla blocklist se supera 5 tentativi SSH in 1 minuto
tcp dport 22 ct state new \
meter ssh_meter { ip saddr timeout 1m limit rate over 5/minute } \
add @blackhole { ip saddr } \
log prefix "ssh-bruteforce: " drop
tcp dport 22 accept
}
}
Puoi ispezionare e manipolare il set direttamente a runtime, senza ricaricare nulla:
sudo nft list set inet filter blackhole
sudo nft add element inet filter blackhole { 1.2.3.4 timeout 24h }
sudo nft delete element inet filter blackhole { 1.2.3.4 }
Verdict map per dispatching
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
tcp dport vmap {
22 : jump ssh_chain,
80 : accept,
443 : accept,
25 : drop
}
}
Le vmap sono O(log n): che tu abbia 5 o 5000 porte, il lookup ha sempre lo stesso costo. È una differenza sottile su piccola scala, brutale su scala seria.
NAT con nftables: SNAT, DNAT e masquerade
Il NAT richiede una tabella separata (per convenzione nat) con catene agganciate agli hook prerouting e postrouting:
table ip nat {
chain prerouting {
type nat hook prerouting priority dstnat;
# Port forwarding: 80 esterno -> 8080 interno
iif "eth0" tcp dport 80 dnat to 192.168.1.10:8080
}
chain postrouting {
type nat hook postrouting priority srcnat;
# Masquerade per la LAN che esce su eth0
oif "eth0" ip saddr 192.168.1.0/24 masquerade
}
}
Per scenari ad alte prestazioni (router, gateway con >1 Gbit/s), vale la pena abilitare anche un flowtable per offloadare il fast-path al kernel o addirittura all'hardware:
table inet filter {
flowtable ft {
hook ingress priority 0;
devices = { eth0, eth1 };
flags offload; # solo se la NIC supporta hw offload
}
chain forward {
type filter hook forward priority 0; policy accept;
ip protocol { tcp, udp } flow add @ft
ct state established,related accept
}
}
Migrazione pratica da iptables (senza downtime)
La via più sicura è una migrazione in 4 passi. Funziona anche su server in produzione, a patto di non bruciare le tappe.
1. Esporta le regole esistenti
sudo iptables-save > /root/iptables.bkp
sudo ip6tables-save > /root/ip6tables.bkp
2. Traduci automaticamente
iptables-restore-translate -f /root/iptables.bkp > /root/nftables-from-ipt.nft
ip6tables-restore-translate -f /root/ip6tables.bkp >> /root/nftables-from-ipt.nft
Importante: il file generato è solo un punto di partenza, non un risultato finale. Va riletto e refattorizzato a mano per:
- Unificare IPv4 e IPv6 nella famiglia
inet.
- Sostituire blocchi ripetitivi con
set e vmap.
- Usare
ct state al posto di --state.
- Rimuovere catene custom ormai superflue.
3. Applica in dry-run
sudo nft -c -f /root/nftables-from-ipt.nft
4. Switch atomico
Sempre (e sottolineo sempre) da una sessione SSH secondaria, con un timer di rollback attivo:
# Rete di sicurezza: ripristina iptables fra 5 minuti se l'SSH cade
sudo bash -c "sleep 300 && iptables-restore < /root/iptables.bkp" &
ROLLBACK_PID=$!
# Spegni iptables, accendi nftables
sudo systemctl stop iptables 2>/dev/null
sudo iptables -F && sudo iptables -X
sudo cp /root/nftables-from-ipt.nft /etc/nftables.conf
sudo systemctl restart nftables
# Se la nuova SSH funziona, annulla il rollback
kill $ROLLBACK_PID
Questo trucco mi ha salvato le chiappe più di una volta. Non saltarlo, anche se sembra paranoia.
Esempio reale: hardening di un host Docker
Docker manipola pesantemente le tabelle nat e filter della famiglia ip. Per non collidere, conviene definire le proprie regole nella famiglia inet con priorità inferiore (cioè più precoce) rispetto alle catene di Docker.
table inet hardening {
set trusted_admin {
type ipv4_addr
elements = { 10.0.0.0/24, 192.168.50.0/24 }
}
chain prerouting_block {
type filter hook prerouting priority -300;
# Anti-spoofing
iif "eth0" ip saddr { 127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16 } drop
# Drop di pacchetti malformati
ct state invalid drop
tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 drop
tcp flags & (syn|fin) == (syn|fin) drop
}
chain input_filter {
type filter hook input priority -100; policy accept;
ct state established,related accept
iif "lo" accept
tcp dport 22 ip saddr @trusted_admin accept
tcp dport 22 drop
# Lascia passare il traffico verso le porte pubblicate da Docker
# (Docker gestisce le sue catene a priority 0)
}
}
Logging, contatori e troubleshooting
nftables integra counter e log come statement nativi, eliminando la necessità di moduli esterni:
chain input {
type filter hook input priority 0; policy drop;
tcp dport 443 counter accept comment "https-traffic"
}
Visualizza i contatori in tempo reale:
sudo nft list ruleset -a
sudo nft monitor trace # tracing pacchetto-per-pacchetto
sudo nft list counters
Per il logging strutturato verso journald + un SIEM (Wazuh, Splunk, Elastic):
tcp dport 22 ct state new log prefix "[NFT-SSH-NEW] " group 0 level warn accept
I log finiscono in journalctl -k e possono essere raccolti via auditd o rsyslog con un parser dedicato. Il nft monitor trace, in particolare, è una gemma quando devi capire perché diavolo un pacchetto non arriva dove dovrebbe.
Best practice di sicurezza per il 2026
- Default deny in input e forward. Mai
policy accept sugli hook che ricevono traffico.
- Famiglia
inet ovunque possibile: una regola, due protocolli (IPv4 e IPv6). Meno duplicazione, meno errori.
- Limita ICMP e ICMPv6 con
limit rate ma non bloccarli del tutto: icmpv6 è essenziale per Path MTU Discovery e Neighbor Discovery (tanti ci sono caduti, chiedi in giro).
- Usa
set dynamic con timeout al posto di catene "blocklist" statiche aggiornate da cron.
- Versiona
/etc/nftables.conf con git e applica le modifiche via Ansible/Salt con nft -c obbligatorio in fase di lint.
- Non eseguire
nft flush ruleset in produzione senza un timer di rollback attivo. Mai.
- Disabilita IPv6 forwarding se non lo usi:
sysctl -w net.ipv6.conf.all.forwarding=0.
- Integra con eBPF per visibilità: tool come Tetragon o
bpftrace permettono di vedere quali processi triggerano quali regole nftables.
Domande frequenti su nftables
Posso usare iptables e nftables contemporaneamente?
Tecnicamente sì, ma è fortemente sconsigliato. I due framework lavorano su priority differenti dello stesso hook netfilter e l'ordine di valutazione può riservarti sorprese spiacevoli. In più, qualsiasi reload di iptables tramite il wrapper iptables-nft sovrascrive parti del ruleset nftables. Regola d'oro: scegli un framework e mantienilo.
nftables è più lento di iptables su regole semplici?
No. Su set di regole piccoli (< 50 regole) le performance sono praticamente indistinguibili. Su set medio-grandi nftables vince nettamente grazie a set, map e vmap. Il vero collo di bottiglia, oggi, è raramente il firewall: è il conntrack sotto carico DDoS, e anche lì nftables offre flowtable per offloadare il fast-path.
Come posso fare backup e ripristino delle regole nftables?
Usa nft list ruleset > backup.nft per esportare lo stato corrente e nft -f backup.nft per ripristinarlo. Per un ripristino atomico dell'intero ruleset, anteponi flush ruleset al file di backup. Su systemd basta systemctl reload nftables per riapplicare /etc/nftables.conf.
nftables supporta il logging in formato JSON?
Sì, dalla versione 0.9.4. Esegui nft -j list ruleset per ottenere l'intero ruleset in JSON — ideale per pipeline di osservabilità. Anche nft monitor supporta --json, il che lo rende perfetto per integrazioni con Vector, Fluent Bit o Logstash.
Devo migrare anche se uso UFW o firewalld?
Probabilmente no, almeno non subito. Sia UFW (Ubuntu) sia firewalld (RHEL/Fedora) usano già nftables come backend di default dal 2022. La migrazione esplicita ha senso solo se vuoi superare le astrazioni dei frontend e sfruttare feature avanzate come flowtable, vmap dinamici o regole eBPF.
Conclusioni
nftables nel 2026 è la base imprescindibile di qualsiasi strategia seria di hardening Linux: più espressivo, più performante e con un modello transazionale che riduce drasticamente il rischio di errori operativi. Investire qualche ora in una migrazione pulita ripaga in stabilità, leggibilità delle regole e capacità di automatizzare con Ansible o GitOps. Il passo successivo, quello logico, è integrare nftables con un SIEM come Wazuh per chiudere il cerchio su detection e response.