Vai al contenuto
GLGiuseppe Lanza Scrivimi

Firewall completo su Linux con nftables

Costruire su Ubuntu 24.04 un firewall nftables completo: un server con policy drop, SSH limitato e rate limiting, poi un gateway WAN/LAN/DMZ con masquerade, port forwarding, MSS clamping e anti-spoofing.

Sistemi: Ubuntu 24.04 LTS · nftablesLivello: IntermedioTempo: 60 minutiAggiornata: novembre 2026

Perché nftables

nftables è il sottosistema di filtraggio dei pacchetti del kernel Linux che ha sostituito iptables. Su Ubuntu 24.04 è già tutto pronto: il pacchetto nftables è installato, il comando iptables stesso è solo un traduttore verso nftables (iptables-nft) e il servizio nftables.service carica all’avvio il file /etc/nftables.conf.

Rispetto a iptables hai alcuni vantaggi concreti:

  • un solo comando, nft, per IPv4, IPv6, ARP e bridge;
  • la famiglia inet: una sola tabella filtra insieme IPv4 e IPv6, quindi non devi mantenere due firewall paralleli che prima o poi divergono;
  • variabili (define) e set nominati: elenchi di indirizzi o porte che consulti in una sola regola e che puoi modificare a caldo;
  • caricamento atomico: nft -f applica tutto il file o niente, senza stati intermedi.

In questa guida costruiamo due firewall completi: quello di un server esposto su Internet e quello di un gateway che separa WAN, LAN e DMZ.

I concetti in cinque minuti

In nftables non esistono tabelle e catene predefinite: le crei tu. Una tabella appartiene a una famiglia (ip, ip6, inet…) e contiene catene e set. Una catena base si aggancia a un punto del percorso del pacchetto nel kernel (l’hook) con una certa priorità e una policy, cioè il verdetto applicato se nessuna regola decide.

Ingresso dalla rete prerouting DNAT, anti-spoof Instradamento per me o no? forward solo router postrouting masquerade input policy drop Processo locale sshd, nginx… output policy accept uscita verso la rete destinato al server da inoltrare
Gli hook di netfilter. Un pacchetto destinato al server passa da prerouting e input; uno da inoltrare (gateway) da prerouting, forward e postrouting; uno generato dal server da output e postrouting.

Tre regole che eviteranno molti mal di testa:

  • Il primo verdetto vince, dentro una catena. Le regole si leggono dall’alto: il primo accept o drop chiude la valutazione di quella catena.
  • drop è definitivo, accept no. Se allo stesso hook ci sono più catene base (la tua, quella di Docker, quella di fail2ban), un accept fa passare il pacchetto alla catena successiva; un drop in una qualsiasi lo scarta per sempre. Le catene si eseguono in ordine di priorità, dal numero più basso.
  • iifname invece di iif per le interfacce fisiche e virtuali: iif usa l’indice dell’interfaccia, che deve esistere quando carichi le regole; iifname confronta il nome e funziona anche con interfacce create dopo (wg0, ppp0, gre1). Per lo, che esiste sempre, iif va benissimo ed è più veloce.

Le priorità hanno nomi simbolici: raw (−300), mangle (−150), dstnat (−100), filter (0), srcnat (100). Li userai nelle definizioni delle catene.

Preparazione

1. Spegnere UFW

Su Ubuntu è installato anche UFW, normalmente inattivo. UFW genera le sue regole tramite iptables-nft: se lo usi insieme a un /etc/nftables.conf scritto a mano ottieni due firewall che si sovrappongono, e un drop dell’uno vanifica gli accept dell’altro. Scegline uno solo. Per questa guida:

sudo ufw status          # deve rispondere "Status: inactive"
sudo ufw disable         # se era attivo
sudo systemctl disable --now ufw

2. Avere un piano di emergenza

Prima di toccare il firewall di una macchina remota assicurati di avere un accesso fuori banda: la console della macchina virtuale, l’iDRAC/iLO, la console seriale o la console web del provider. Se non ce l’hai, segui alla lettera la procedura con il rollback automatico descritta più avanti, in Applicare le regole senza chiudersi fuori.

3. Salvare le regole attuali

sudo sh -c '{ echo "flush ruleset"; nft list ruleset; } > /root/nft-prima.nft'
sudo cp /etc/nftables.conf /root/nftables.conf.orig

Il primo file è una fotografia delle regole caricate in questo momento, pronta per essere ricaricata così com’è.

Parte 1: il firewall di un server

Scenario: un server web Ubuntu con un’interfaccia ens18 su Internet. Vogliamo:

  • tutto chiuso in ingresso, salvo quello che apriamo esplicitamente;
  • SSH raggiungibile solo dalla rete di amministrazione (192.168.50.0/24, un IP fisso dell’ufficio 198.51.100.7 e la rete IPv6 2001:db8:50::/64), con un limite alle nuove connessioni;
  • HTTP e HTTPS aperti a tutti;
  • i pacchetti scartati registrati nel log, senza intasarlo;
  • il traffico in uscita libero.
/etc/nftables.conf – server
#!/usr/sbin/nft -f
# Firewall del server web – Ubuntu 24.04
# Verifica: nft -c -f /etc/nftables.conf   Applica: nft -f /etc/nftables.conf

flush ruleset

define PORTA_SSH = 22
define PORTE_WEB = { 80, 443 }

table inet filtro {

    # --- Reti da cui è consentito SSH -------------------------------
    set admin4 {
        type ipv4_addr
        flags interval
        elements = { 192.168.50.0/24, 198.51.100.7 }
    }
    set admin6 {
        type ipv6_addr
        flags interval
        elements = { 2001:db8:50::/64 }
    }

    # --- Set dinamici per limitare le nuove connessioni SSH ---------
    # Ogni sorgente resta nel set per 2 minuti dall'ultimo tentativo
    set ssh_lim4 {
        type ipv4_addr
        flags dynamic
        timeout 2m
    }
    set ssh_lim6 {
        type ipv6_addr
        flags dynamic
        timeout 2m
    }

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

        # Loopback e connessioni già stabilite
        iif "lo" accept
        ct state established,related accept
        ct state invalid drop

        # ICMPv6: neighbor discovery e router advertisement sono
        # indispensabili, senza IPv6 smette di funzionare
        icmpv6 type { nd-neighbor-solicit, nd-neighbor-advert, nd-router-solicit, nd-router-advert } ip6 hoplimit 255 accept
        icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem } accept
        icmpv6 type echo-request limit rate 10/second accept

        # ICMP IPv4: errori (servono alla scoperta dell'MTU) e ping limitato
        icmp type { destination-unreachable, time-exceeded, parameter-problem } accept
        icmp type echo-request limit rate 10/second accept

        # DHCPv6: serve solo se il server prende l'indirizzo IPv6 via DHCPv6
        # ip6 saddr fe80::/10 udp sport 547 udp dport 546 accept

        # SSH: solo dalle reti di amministrazione, massimo 4 nuove
        # connessioni al minuto per indirizzo sorgente
        tcp dport $PORTA_SSH ct state new ip saddr @admin4 update @ssh_lim4 { ip saddr limit rate over 4/minute } counter drop
        tcp dport $PORTA_SSH ct state new ip saddr @admin4 accept
        tcp dport $PORTA_SSH ct state new ip6 saddr @admin6 update @ssh_lim6 { ip6 saddr limit rate over 4/minute } counter drop
        tcp dport $PORTA_SSH ct state new ip6 saddr @admin6 accept

        # Servizi pubblici
        tcp dport $PORTE_WEB accept
        # udp dport 443 accept     # solo se servi HTTP/3 (QUIC)

        # Tutto il resto finisce nella policy drop: registriamo
        # al massimo 5 righe al minuto per non riempire il log
        counter limit rate 5/minute burst 10 packets log prefix "nft-in-drop: " level info
    }

    chain forward {
        # Il server non instrada: niente passa
        type filter hook forward priority filter; policy drop;
    }

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

Perché la tabella inet

Con table inet filtro la stessa catena input vede sia i pacchetti IPv4 sia quelli IPv6. Le regole che non citano la versione del protocollo (tcp dport 443, ct state) valgono per entrambe; quelle che usano ip saddr valgono solo per IPv4, quelle con ip6 saddr solo per IPv6. Con le famiglie separate ip e ip6 dovresti scrivere e tenere allineati due file: il classico errore è chiudere bene IPv4 e lasciare IPv6 spalancato.

Stato delle connessioni e loopback

ct state established,related accept sta in alto perché riguarda la stragrande maggioranza dei pacchetti: le risposte alle connessioni già accettate, comprese quelle aperte dal server verso l’esterno (aggiornamenti, DNS). related copre i pacchetti collegati a una connessione esistente, come i messaggi ICMP di errore. I pacchetti invalid non appartengono a nessuna connessione valida e si possono scartare subito.

ICMP e ICMPv6: non chiuderli

Senza ICMPv6 IPv6 non funziona

In IPv6 non c’è ARP: la risoluzione degli indirizzi dei vicini (neighbor solicitation/advertisement) e la configurazione automatica (router advertisement) viaggiano in ICMPv6. Se le blocchi, IPv6 funziona per qualche minuto grazie alla cache e poi il server diventa irraggiungibile. Il controllo ip6 hoplimit 255 garantisce che questi messaggi arrivino dal collegamento locale e non da un router remoto.

Anche in IPv4 i messaggi di errore servono: destination-unreachable trasporta il “fragmentation needed” usato dalla scoperta dell’MTU del percorso. Il ping è utile per il monitoraggio: lo lasciamo passare con un limite.

SSH: set nominati e rate limiting

I set admin4 e admin6 contengono reti e singoli indirizzi (flags interval permette di usare i prefissi). Aggiungere una sede significa modificare una riga, non duplicare regole.

I set ssh_lim4 e ssh_lim6 sono dinamici: li riempie il firewall stesso. L’istruzione update @ssh_lim4 { ip saddr limit rate over 4/minute } inserisce l’indirizzo sorgente nel set (o ne aggiorna la scadenza) e associa a ognuno un proprio contatore. Se una sorgente supera le 4 nuove connessioni al minuto, la regola è vera e il pacchetto viene scartato; altrimenti si passa alla regola successiva, che accetta. È la versione moderna dei meter, ora deprecati.

Il limite riguarda solo i pacchetti ct state new: le sessioni già aperte non ne risentono. Per un blocco basato sui tentativi di login falliti, e non sul semplice numero di connessioni, affianca fail2ban.

Il log dei pacchetti scartati

L’ultima regola della catena input non dà un verdetto: conta e registra, poi il pacchetto cade nella policy drop. Il limit è fondamentale: senza, una scansione di porte scriverebbe migliaia di righe al secondo. Il prefisso nft-in-drop: ti permette di filtrare il log del kernel.

Applicare le regole senza chiudersi fuori

Se lavori in SSH, un errore nella regola di SSH ti chiude fuori. La procedura sicura ha quattro passi.

1. Controllare la sintassi

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

Con -c nft analizza il file e lo valida con il kernel, ma non lo applica. Se non stampa nulla, il file è corretto. Attenzione: il controllo trova gli errori di sintassi, non quelli di logica come una rete di amministrazione sbagliata.

2. Programmare il ritorno alle regole precedenti

Prima di applicare, prepara un rollback che scatta da solo dopo 3 minuti:

sudo systemd-run --on-active=3min --unit=nft-rollback /usr/sbin/nft -f /root/nft-prima.nft

In alternativa, con il pacchetto at (da installare) o con un semplice sleep lasciato in esecuzione in una sessione tmux:

echo "/usr/sbin/nft flush ruleset" | sudo at now + 3 minutes
sudo sh -c 'sleep 180 && nft flush ruleset'      # dentro tmux: annulli con Ctrl+C

nft flush ruleset cancella tutte le regole e lascia la macchina completamente aperta: va bene come paracadute, ma solo per pochi minuti.

3. Applicare e verificare da una nuova sessione

sudo nft -f /etc/nftables.conf

La sessione SSH già aperta sopravvive grazie a ct state established: non è una prova. Apri una seconda sessione SSH da un indirizzo della rete di amministrazione. Se entra, annulla il rollback:

sudo systemctl stop nft-rollback.timer
# oppure, con at:
atq
sudo atrm <numero-del-job>

Se la seconda sessione non entra, non fare niente: entro 3 minuti tornano le regole di prima.

4. Rendere le regole permanenti

sudo systemctl enable --now nftables
systemctl status nftables

Il servizio carica /etc/nftables.conf all’avvio, prima che la rete salga. Dopo ogni modifica al file usa sudo systemctl reload nftables (o direttamente sudo nft -f /etc/nftables.conf), sempre preceduto da nft -c.

Non fermare il servizio per “disattivare” il firewall

systemctl stop nftables esegue nft flush ruleset: cancella le tue regole ma anche quelle di Docker, fail2ban e di chiunque altro. Vedi Convivere con Docker, UFW e fail2ban.

Parte 2: il firewall di un gateway

Secondo scenario: un server Ubuntu con tre schede di rete fa da router e firewall per una piccola sede.

Internet Gateway Ubuntu 24.04 WAN ens18 203.0.113.2 nftables · ip_forward=1 DMZ · server web 172.16.0.10 172.16.0.0/24 · ens20 .1 LAN uffici 192.168.1.0/24 ens19 .1 (gateway) Port forwarding 80/443 (DNAT) verso 172.16.0.10 uscita in masquerade
Il gateway separa tre zone. Da Internet si raggiunge solo il server web in DMZ, sulle porte pubblicate; la LAN esce su Internet con l’indirizzo pubblico del gateway.

Prima di scrivere le regole, decidi la matrice dei flussi: chi può parlare con chi. Tutto ciò che non è nella tabella è vietato.

Da → AInternet (WAN)DMZLANGateway stesso
Internet–solo 80/443 verso 172.16.0.10nono
DMZHTTP/HTTPS, DNS, NTP–nono
LANsìsì–DNS, DHCP, SSH

La regola d’oro della DMZ: la DMZ non apre mai connessioni verso la LAN. Se il server web viene compromesso, l’attaccante resta confinato lì.

Abilitare l’instradamento

/etc/sysctl.d/99-gateway.conf
net.ipv4.ip_forward = 1
sudo sysctl --system
sysctl net.ipv4.ip_forward      # deve valere 1

La configurazione del gateway

/etc/nftables.conf – gateway WAN/LAN/DMZ
#!/usr/sbin/nft -f
# Firewall del gateway – Ubuntu 24.04

flush ruleset

define WAN      = "ens18"
define LAN      = "ens19"
define DMZ      = "ens20"
define RETE_LAN = 192.168.1.0/24
define RETE_DMZ = 172.16.0.0/24
define WEB_DMZ  = 172.16.0.10

table inet filtro {

    # Indirizzi che non possono arrivare legittimamente da Internet
    set bogon4 {
        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.168.0.0/16,
                     224.0.0.0/3 }
    }

    # --- Anti-spoofing, prima di NAT e filtro -----------------------
    chain antispoof {
        type filter hook prerouting priority mangle; policy accept;

        iifname $WAN ip saddr @bogon4 counter drop
        # Le richieste DHCP dei client hanno sorgente 0.0.0.0:
        # le escludiamo dal controllo successivo
        udp sport 68 udp dport 67 accept
        # Reverse path filter: la sorgente deve essere raggiungibile
        # attraverso l'interfaccia da cui il pacchetto è entrato
        meta nfproto ipv4 fib saddr . iif oif missing counter drop
    }

    # --- Traffico diretto al gateway --------------------------------
    chain input {
        type filter hook input priority filter; policy drop;

        iif "lo" accept
        ct state established,related accept
        ct state invalid drop

        icmpv6 type { nd-neighbor-solicit, nd-neighbor-advert, nd-router-solicit, nd-router-advert } ip6 hoplimit 255 accept
        icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem } accept
        icmp type { destination-unreachable, time-exceeded, parameter-problem } accept
        icmp type echo-request limit rate 10/second accept

        # Servizi del gateway per la LAN: DNS, DHCP, SSH di gestione
        iifname $LAN udp dport { 53, 67 } accept
        iifname $LAN tcp dport { 22, 53 } accept

        counter limit rate 5/minute burst 10 packets log prefix "nft-in-drop: " level info
    }

    # --- Traffico che attraversa il gateway -------------------------
    chain forward {
        type filter hook forward priority filter; policy drop;

        # MSS clamping: adegua l'MSS dei SYN all'MTU del percorso
        tcp flags syn tcp option maxseg size set rt mtu

        ct state established,related accept
        ct state invalid drop

        # LAN: verso Internet e verso la DMZ
        iifname $LAN oifname { $WAN, $DMZ } accept

        # DMZ: verso Internet solo aggiornamenti, DNS e NTP.
        # Verso la LAN niente: lo decide la policy drop
        iifname $DMZ oifname $WAN tcp dport { 80, 443 } accept
        iifname $DMZ oifname $WAN udp dport { 53, 123 } accept

        # Internet: solo le connessioni pubblicate con DNAT
        iifname $WAN oifname $DMZ ip daddr $WEB_DMZ tcp dport { 80, 443 } ct status dnat accept

        counter limit rate 5/minute burst 10 packets log prefix "nft-fwd-drop: " level info
    }

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

# --- NAT (solo IPv4) ------------------------------------------------
table ip natgw {

    chain prerouting {
        type nat hook prerouting priority dstnat; policy accept;
        # Port forwarding verso il server web in DMZ
        iifname $WAN tcp dport { 80, 443 } dnat to $WEB_DMZ
    }

    chain postrouting {
        type nat hook postrouting priority srcnat; policy accept;
        # LAN e DMZ escono con l'indirizzo pubblico del gateway
        oifname $WAN ip saddr { $RETE_LAN, $RETE_DMZ } masquerade
    }
}

Forward con policy drop

Tutto il lavoro di un gateway sta nella catena forward. Con la policy drop ogni flusso che non hai previsto viene bloccato, compreso il traffico IPv6 tra le zone: se in futuro instraderai anche IPv6, aggiungi le regole corrispondenti. Nota come le regole seguono riga per riga la matrice dei flussi: è il modo più semplice per rileggerle a distanza di mesi.

Masquerade e port forwarding

Il NAT ha una sua tabella, di famiglia ip perché riguarda solo IPv4. La chiamo natgw e non nat di proposito: ip nat è il nome della tabella che crea iptables-nft (e quindi Docker), e conviene non mescolarle.

  • masquerade in postrouting sostituisce l’indirizzo sorgente con quello dell’interfaccia WAN. Rispetto a snat to ha il vantaggio di seguire da solo l’indirizzo anche se cambia (DHCP, PPPoE).
  • dnat in prerouting riscrive la destinazione prima della decisione di instradamento: il pacchetto entra per 203.0.113.2 e la catena forward lo vede già diretto a 172.16.0.10. Per questo la regola in forward usa l’indirizzo interno. ct status dnat aggiunge una garanzia: passa solo il traffico che è davvero stato traslato.

MSS clamping

Se un tratto del percorso ha MTU inferiore a 1500 (PPPoE, tunnel) e qualcuno filtra i messaggi ICMP, le connessioni TCP si bloccano sui pacchetti grandi: il sito si apre a metà, i trasferimenti si fermano. La regola tcp option maxseg size set rt mtu corregge al volo l’MSS dei pacchetti SYN in base all’MTU della rotta di uscita. Sta prima di ct state established perché deve vedere anche il SYN/ACK di risposta, che appartiene già a una connessione nota.

Anti-spoofing

La catena antispoof si aggancia a prerouting con priorità mangle (−150), cioè prima del DNAT (−100). Fa due controlli:

  • scarta i pacchetti che arrivano dalla WAN con sorgente privata o riservata (il set bogon4);
  • con fib saddr . iif oif missing chiede alla tabella di instradamento se la sorgente è raggiungibile attraverso l’interfaccia di ingresso. Un pacchetto che entra dalla WAN con sorgente 192.168.1.50 non supera il controllo.
Se il gateway è dietro il modem del provider

Quando l’interfaccia WAN ha un indirizzo privato (per esempio 192.168.0.2 dietro il router dell’operatore), il set bogon4 scarterebbe anche le risposte DHCP e il traffico del modem: togli dal set la rete del collegamento verso il modem. Il controllo fib resta valido. In alternativa al fib puoi usare il filtro del kernel con net.ipv4.conf.all.rp_filter = 1; Ubuntu lo imposta di default a 2, la modalità “loose”, più permissiva.

Convivere con Docker, UFW e fail2ban

Più programmi possono scrivere regole nel kernel nello stesso momento. Ognuno usa le proprie tabelle, ma tutti condividono gli stessi hook.

UFW

Non usare UFW e un /etc/nftables.conf scritto a mano sulla stessa macchina. UFW crea catene con policy propria agli stessi hook: un pacchetto deve essere accettato da entrambi i firewall, e il risultato è difficile da prevedere e da diagnosticare. UFW va benissimo su un server semplice; per un gateway o per regole come quelle di questa guida, usa solo nftables.

fail2ban

fail2ban, con l’azione nftables predefinita su Ubuntu 24.04, crea la sua tabella inet f2b-table con una catena in input a priorità −1, quindi valutata prima della tua: un indirizzo bannato viene respinto anche se le tue regole lo accetterebbero. Il problema è il flush ruleset in testa al tuo file: ogni volta che ricarichi nftables cancelli anche la tabella di fail2ban, e i ban spariscono. Dopo ogni ricarica:

sudo nft -f /etc/nftables.conf && sudo systemctl restart fail2ban

Tutti i dettagli nella guida Fail2ban su Linux.

Docker

Docker gestisce la rete dei container con iptables-nft: crea le tabelle ip filter e ip nat con le sue catene. Due conseguenze:

  • Il flush ruleset cancella le regole di Docker. Dopo aver ricaricato il firewall, esegui sudo systemctl restart docker, altrimenti i container perdono la rete e le porte pubblicate.
  • La tua policy drop in forward vale anche per i container. Il traffico verso i container attraversa forward: se la tua catena lo scarta, l’accept di Docker non basta. Su un host Docker aggiungi in forward, dopo la riga ct state established,related accept, per esempio iifname "docker0" accept (i container escono) e oifname "docker0" ct status dnat accept (le porte pubblicate), adattandole alle reti dei tuoi container: le reti definite con docker network create usano interfacce br-…, non docker0.
Ricaricare senza toccare le tabelle degli altri

Se sulla macchina convivono più programmi, al posto di flush ruleset puoi azzerare solo le tue tabelle. All’inizio del file scrivi, per ogni tabella:

table inet filtro
delete table inet filtro

La prima riga crea la tabella se non esiste, la seconda la elimina: così il file si ricarica sempre senza errori e senza toccare f2b-table o le tabelle di Docker. Ricorda però che systemctl stop nftables esegue comunque un flush ruleset completo.

Verifiche: regole, contatori e log

Leggere le regole caricate

sudo nft list ruleset                       # tutto, comprese le tabelle di Docker e fail2ban
sudo nft list table inet filtro
sudo nft -a list chain inet filtro input    # con -a mostra l'handle di ogni regola

Le regole con counter mostrano pacchetti e byte: counter packets 1532 bytes 91920. È il modo più rapido per capire se una regola “lavora”.

Set a caldo

# vedere chi è nel set del rate limiting SSH, con la scadenza
sudo nft list set inet filtro ssh_lim4

# aggiungere al volo un IP di amministrazione (si perde al riavvio:
# riportalo anche in /etc/nftables.conf)
sudo nft add element inet filtro admin4 { 203.0.113.50 }
sudo nft delete element inet filtro admin4 { 203.0.113.50 }

Il log del kernel

sudo journalctl -k -g "nft-in-drop" --since "1 hour ago"
sudo journalctl -k -f                        # in tempo reale

Ogni riga riporta interfaccia, sorgente, destinazione, protocollo e porte. Con rsyslog installato le stesse righe finiscono anche in /var/log/kern.log.

Seguire un pacchetto passo per passo

Quando non capisci quale regola scarta un pacchetto, usa il tracciamento. Inserisci in testa a una catena una regola che marca il traffico che ti interessa e osserva:

sudo nft insert rule inet filtro input ip saddr 192.168.50.20 meta nftrace set 1
sudo nft monitor trace
# finito: cancella la regola usando il suo handle
sudo nft -a list chain inet filtro input
sudo nft delete rule inet filtro input handle <numero>

nft monitor trace mostra ogni catena e ogni regola attraversata, fino al verdetto finale.

Problemi comuni

SintomoCausa probabileCosa fare
Dopo aver applicato le regole non entri più in SSHRete di amministrazione sbagliata, porta SSH diversa da 22, IP di uscita diverso da quello che pensaviAspetta il rollback o usa la console; controlla il tuo IP reale con ss -tn sul server prima di applicare
IPv6 funziona per qualche minuto e poi smetteICMPv6 neighbor discovery bloccatoAccetta nd-neighbor-solicit, nd-neighbor-advert e nd-router-advert
Ping OK, ma alcuni siti o trasferimenti si bloccanoMTU/MSS, ICMP “packet too big” o “fragmentation needed” scartatiRegola di MSS clamping in forward; non bloccare gli ICMP di errore
La LAN non navigaip_forward a 0 o masquerade mancantesysctl net.ipv4.ip_forward; controlla i contatori in forward e postrouting
Il port forwarding funziona da Internet ma non dalla LANIl DNAT vale solo per il traffico che entra dalla WAN (manca l’hairpin NAT)Dalla LAN usa l’indirizzo interno, con un DNS interno che risolve il nome in 172.16.0.10
Dopo reload i container Docker non hanno reteflush ruleset ha cancellato le regole di Dockersudo systemctl restart docker; valuta di azzerare solo le tue tabelle
fail2ban non banna più dopo una ricaricaLa tabella f2b-table è stata cancellatasudo systemctl restart fail2ban
Regole corrette ma il traffico viene scartato lo stessoUFW o un altro programma ha catene con policy drop sullo stesso hooksudo nft list ruleset: cerca altre catene base su input e forward
Error: Could not process rule al caricamentoSet o variabile non definiti, errore di battitura, regola IPv4 in una tabella ip6Leggi la riga indicata dall’errore; nft -c -f segnala il punto esatto
Nessuna riga nel logLa regola di log non viene raggiunta o il limite è troppo bassoControlla il suo contatore; usa journalctl -k, non il log di sistema generico

Il passo successivo

Il firewall decide chi può raggiungere un servizio; non sa distinguere un login riuscito da mille tentativi falliti. Per bloccare gli attacchi a forza bruta in base ai log, aggiungi Fail2ban su Linux, che usa proprio nftables per i ban. Per proteggere il servizio più esposto, vedi SSH sicuro su Linux. Se il gateway deve collegare altre sedi, i tunnel sono in Tunnel GRE tra Cisco e Ubuntu e VPN IPsec tra Cisco e Ubuntu.

Prima di andare in produzione

Prova sempre la configurazione in laboratorio, salva quella attuale prima di modificarla e tieni un accesso di emergenza (console o accesso fuori banda) quando lavori su firewall e VPN da remoto.