Vai al contenuto
GLGiuseppe Lanza Scrivimi

Rilevare le violazioni su Ubuntu e bloccare gli IP per 120 minuti

Come capire dai log di Ubuntu 24.04 chi sta attaccando il server (SSH, web, scansioni delle porte, accessi riusciti sospetti) e bloccare automaticamente gli indirizzi per 120 minuti con i set a scadenza di nftables, fail2ban o uno script con timer systemd.

Sistemi: Ubuntu 24.04 LTS · nftables · fail2banLivello: IntermedioTempo: 50 minutiAggiornata: novembre 2026

Che cosa vogliamo ottenere

Un server Ubuntu raggiungibile da Internet riceve ogni giorno migliaia di tentativi: password provate su SSH, richieste a pagine che non esistono come /wp-login.php o /.env, scansioni delle porte. Quasi tutto è rumore automatico, ma nel rumore si nascondono i segnali che contano.

Questa guida fa due cose:

  • rilevare: dove guardare su Ubuntu 24.04 e con quali comandi trovare gli indirizzi che stanno violando le regole, compresi i segni di un accesso che è andato a buon fine;
  • bloccare: mettere quegli indirizzi in un elenco del firewall da cui escono da soli dopo 120 minuti, senza lasciare regole da ripulire a mano.
RILEVARE DECIDERE BLOCCARE Log SSH journalctl -u ssh Log web nginx access.log Pacchetti nftables, kernel fail2ban bantime = 120m Script + timer ogni 5 minuti Regola con soglia limit rate over Set a scadenza bloccati4 / 6 timeout 120m Dopo 120 minuti l’indirizzo esce dal set da solo · gli indirizzi fidati non entrano mai
Tre sorgenti, tre modi di decidere, un solo punto di blocco: un set di nftables in cui ogni indirizzo ha la sua scadenza.

Nella guida uso indirizzi di documentazione (203.0.113.0/24, 198.51.100.0/24 per gli attaccanti, 192.0.2.10 per la postazione dell’amministratore). Gli output dei comandi sono d’esempio: i tuoi avranno indirizzi e numeri diversi.

Perché proprio 120 minuti

Un blocco breve, di pochi minuti, viene semplicemente atteso dai programmi di attacco, che riprendono appena scade. Un blocco permanente, invece, riempie il firewall di indirizzi che dopo qualche giorno appartengono a qualcun altro (connessioni domestiche, cloud, CGNAT). Due ore sono un buon compromesso: interrompono la campagna in corso e si risolvono da sole se blocchi un indirizzo per errore.

Dove si vedono le violazioni su Ubuntu

Su Ubuntu 24.04 quasi tutto passa dal journal di systemd. Sulla versione Server è installato anche rsyslog, che copia gli stessi messaggi nei classici file di /var/log; sulle immagini minimal e cloud a volte non c’è, e il journal resta l’unica fonte.

Che cosaDoveComando di partenza
Accessi SSH, riusciti e fallitijournal, unità ssh · /var/log/auth.logjournalctl -u ssh
Accessi falliti in forma compatta/var/log/btmpsudo lastb
Accessi riusciti/var/log/wtmplast -a
Uso di sudojournal · /var/log/auth.logjournalctl _COMM=sudo
Richieste web/var/log/nginx/ o /var/log/apache2/tail -f access.log
Pacchetti registrati dal firewalljournal del kernel · /var/log/kern.logjournalctl -k
Connessioni in corsokernelss -tn

Leggere il journal richiede i permessi giusti: usa sudo oppure aggiungi il tuo utente al gruppo adm (sudo usermod -aG adm $USER, poi esci e rientra).

Tentativi di accesso SSH

Ogni tentativo fallito lascia nel log una riga di questo tipo. Il formato cambia a seconda di come fallisce:

Failed password for root from 203.0.113.45 port 51122 ssh2
Failed password for invalid user admin from 203.0.113.45 port 51130 ssh2
Invalid user oracle from 198.51.100.7 port 40210
Connection closed by authenticating user root 198.51.100.7 port 40222 [preauth]
Connection closed by invalid user test 198.51.100.7 port 40230 [preauth]

Le ultime due righe sono quelle che vedi quando SSH accetta solo le chiavi: l’attaccante non arriva nemmeno a provare una password e la connessione si chiude prima. Non contengono “from”, quindi un filtro che cerca solo Failed password … from le perde tutte. In compenso, in tutti i formati l’indirizzo viene subito prima di port, e su questo si può contare.

Gli indirizzi più insistenti

sudo journalctl -u ssh --since "24 hours ago" -o cat \
  | grep -E 'Failed password|Invalid user|invalid user|authenticating user' \
  | grep -oP '\S+(?= port \d+)' \
  | sort | uniq -c | sort -rn | head -15
    412 203.0.113.45
    187 198.51.100.7
     36 203.0.113.201
      4 192.0.2.10

La prima colonna è il numero di eventi, la seconda l’indirizzo. Lo stesso tentativo può produrre due righe (Invalid user e poi Failed password), quindi il numero va letto come un ordine di grandezza. Qui 192.0.2.10 è l’amministratore che ha sbagliato la password: è il motivo per cui, più avanti, gli indirizzi fidati vanno esclusi dal blocco.

I nomi utente provati

sudo journalctl -u ssh --since "24 hours ago" -o cat \
  | grep -oP '[Ii]nvalid user \K\S+' | sort | uniq -c | sort -rn | head

È un buon indicatore del tipo di attacco: admin, test, oracle, ubuntu sono tentativi a tappeto; il nome di un tuo utente reale, ripetuto da un solo indirizzo, è un attacco mirato e merita più attenzione.

Con lastb

sudo lastb -i | awk '{print $3}' | sort | uniq -c | sort -rn | head

lastb legge /var/log/btmp, dove sshd registra i login falliti con password. Non vede le connessioni chiuse prima dell’autenticazione, quindi su un server con le sole chiavi dice poco. La terza colonna dell’output di lastb -i è l’indirizzo.

L’unità si chiama ssh, non sshd

Su Ubuntu il servizio è ssh.service. Dalla 24.04 viene avviato tramite ssh.socket, ma i messaggi finiscono comunque sotto ssh.service: journalctl -u ssh li trova tutti. journalctl -u sshd invece non restituisce nulla.

Attacchi al server web

Nel formato combined di nginx e Apache il primo campo è l’indirizzo del client e il nono il codice di risposta. Gli scanner automatici si riconoscono dalle tante risposte 404, 401 o 403 in poco tempo e dalle pagine che cercano.

Chi colleziona errori

sudo awk '$9 ~ /^(401|403|404)$/ {print $1}' /var/log/nginx/access.log \
  | sort | uniq -c | sort -rn | head -15

Chi cerca file che non dovrebbero esistere

sudo grep -E '/\.env|/\.git/|wp-login\.php|xmlrpc\.php|phpmyadmin|/cgi-bin/|\.\./' \
  /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head

Se il sito non è un WordPress, chiunque chieda wp-login.php sta solo scansionando. Una richiesta a /.env o /.git/config cerca password e chiavi dimenticate nella cartella pubblica: vale la pena controllare che quelle richieste abbiano ricevuto davvero 404 e non 200.

sudo awk '$7 ~ /\.env|\.git/ && $9 == 200' /var/log/nginx/access.log

Se questo comando stampa qualcosa, un file riservato è stato scaricato: non è più un tentativo ma una violazione. Togli il file dalla cartella pubblica e cambia subito le credenziali che conteneva.

Per vedere anche i giorni precedenti, i log ruotati e compressi si leggono con zcat: sudo zcat -f /var/log/nginx/access.log* al posto del solo file.

Scansioni delle porte e connessioni anomale

Le scansioni non lasciano tracce nei log dei servizi, perché colpiscono porte su cui non ascolta nessuno. Per vederle bisogna far registrare al firewall le nuove connessioni. Lo facciamo per un periodo limitato, con una regola temporanea nella tabella che creeremo nella sezione Il blocco per 120 minuti con nftables:

sudo nft 'add rule inet blocchi input ct state new meta l4proto tcp
  limit rate 50/second log prefix "nft-nuove: " level info'

Il limit evita che una raffica di pacchetti riempia il journal. Dopo un’ora contiamo, per ogni sorgente, quante porte diverse ha provato:

sudo journalctl -k --since "1 hour ago" --grep 'nft-nuove' -o cat \
  | grep -oP 'SRC=\S+ .*DPT=\d+' | awk '{print $1, $NF}' \
  | sort -u | awk '{print $1}' | uniq -c | sort -rn | head
    963 SRC=198.51.100.90
     41 SRC=203.0.113.12
      3 SRC=192.0.2.10

Chi tocca centinaia di porte diverse sta scansionando. Chi ne tocca due o tre è quasi sempre un client normale. Quando hai finito, togli la regola di registrazione: cercane il numero (handle) e cancellala.

sudo nft -a list chain inet blocchi input | grep nft-nuove
sudo nft delete rule inet blocchi input handle 12      # il numero letto sopra

Che cosa succede adesso

# connessioni stabilite, contate per indirizzo remoto
ss -Htn state established | awk '{print $4}' | sed 's/:[0-9]*$//' \
  | sort | uniq -c | sort -rn | head

# connessioni a metà (SYN ricevuto, risposta mai arrivata): tante = SYN flood
ss -Htn state syn-recv | wc -l

Un singolo indirizzo con centinaia di connessioni aperte verso il sito, o migliaia di connessioni in syn-recv, indicano un tentativo di saturare il server. Contro un attacco distribuito vero il blocco degli indirizzi sul server serve a poco: il traffico va fermato prima, dal provider o da una CDN.

I segni di un accesso riuscito

I tentativi falliti sono fastidiosi ma innocui. Quello che conta davvero è accorgersi se uno è riuscito. Sono pochi controlli, da fare regolarmente e sempre dopo un attacco intenso.

# accessi SSH riusciti negli ultimi 7 giorni: riconosci tutti gli indirizzi?
sudo journalctl -u ssh --since "7 days ago" -o cat | grep Accepted

# ultimi accessi con indirizzo
last -a | head -20

# utenti con UID da 1000 in su (le persone) e utenti con UID 0 (root)
awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' /etc/passwd
awk -F: '$3 == 0 {print $1}' /etc/passwd             # deve stampare solo root

# chiavi SSH autorizzate modificate negli ultimi 7 giorni
sudo find /root /home -name authorized_keys -mtime -7 -ls

# comandi eseguiti con sudo oggi
sudo journalctl _COMM=sudo --since today -o cat | grep COMMAND

# servizi in ascolto: c'è qualcosa che non hai installato tu?
sudo ss -tulpn

# attività pianificate
sudo ls -la /etc/cron.d /var/spool/cron/crontabs
systemctl list-timers --all

# file dei pacchetti modificati rispetto all'originale
sudo dpkg --verify

Una riga di Accepted deve sempre avere un indirizzo che riconosci, e il metodo atteso: publickey, non password, se hai disattivato le password. dpkg --verify segnala anche i file di configurazione che hai modificato tu (con una c nella colonna del tipo): conta quello che compare in /usr/bin, /usr/sbin e /usr/lib.

Se trovi un accesso che non riconosci

Bloccare l’indirizzo non basta: chi è entrato può aver lasciato un’altra porta aperta. Isola il server dalla rete (lasciando la console), conserva i log copiandoli altrove, cambia tutte le credenziali che il server conosceva e valuta seriamente una reinstallazione da zero con il ripristino dei soli dati. Un sistema compromesso non si “pulisce” con certezza.

Quando bloccare: le soglie

Prima di automatizzare serve una regola chiara su che cosa merita un blocco. Questi sono valori di partenza ragionevoli per un server con pochi amministratori; guarda i tuoi log per qualche giorno e adattali.

ComportamentoSogliaChi decide
Accessi SSH falliti5 in 10 minutifail2ban o script
Nuove connessioni SSH, riuscite o nooltre 10 al minutoregola nftables
Connessioni verso porte non pubblicateoltre 15 al minutoregola nftables
Autenticazione web fallita (401)5 in 10 minutifail2ban
Richieste di file sensibili (.env, .git)2 in 10 minutifail2ban

La durata del blocco è sempre la stessa: 120 minuti.

Il blocco per 120 minuti con nftables

nftables permette di creare set con scadenza: elenchi di indirizzi in cui ogni elemento ha un proprio conto alla rovescia e, quando arriva a zero, sparisce. È esattamente quello che serve: si aggiunge l’indirizzo e non ci si pensa più.

Mettiamo i blocchi in una tabella separata, blocchi, così non tocchiamo il firewall principale. La sua catena ha priorità filter - 10, quindi viene attraversata prima delle regole normali (che hanno priorità filter, cioè 0).

sudo mkdir -p /etc/nftables.d
/etc/nftables.d/blocchi.nft
# Blocchi temporanei: ogni indirizzo resta nel set per 120 minuti, poi esce da solo.
table inet blocchi {

    # Indirizzi da non bloccare mai: amministrazione, monitoraggio, backup
    set fidati4 {
        type ipv4_addr
        flags interval
        elements = { 127.0.0.0/8, 192.0.2.10 }
    }
    set fidati6 {
        type ipv6_addr
        flags interval
        elements = { ::1 }
    }

    # Indirizzi bloccati: scadenza predefinita 120 minuti
    set bloccati4 {
        type ipv4_addr
        flags dynamic, timeout
        timeout 120m
        size 65536
    }
    set bloccati6 {
        type ipv6_addr
        flags dynamic, timeout
        timeout 120m
        size 65536
    }

    # Contatori per sorgente, usati dalle regole con soglia
    set ssh4  { type ipv4_addr; flags dynamic, timeout; timeout 1m; size 65536; }
    set ssh6  { type ipv6_addr; flags dynamic, timeout; timeout 1m; size 65536; }
    set scan4 { type ipv4_addr; flags dynamic, timeout; timeout 1m; size 65536; }
    set scan6 { type ipv6_addr; flags dynamic, timeout; timeout 1m; size 65536; }

    chain input {
        type filter hook input priority filter - 10; policy accept;

        # 1. il traffico locale e gli indirizzi fidati non vengono controllati
        iif "lo" accept
        ip saddr @fidati4 accept
        ip6 saddr @fidati6 accept

        # 2. chi è già bloccato viene scartato
        ip saddr @bloccati4 counter drop
        ip6 saddr @bloccati6 counter drop

        # 3. troppe nuove connessioni SSH dallo stesso indirizzo: blocco
        tcp dport 22 ct state new update @ssh4 { ip saddr limit rate over 10/minute } \
            add @bloccati4 { ip saddr } log prefix "nft-blocco-ssh: " drop
        tcp dport 22 ct state new update @ssh6 { ip6 saddr limit rate over 10/minute } \
            add @bloccati6 { ip6 saddr } log prefix "nft-blocco-ssh: " drop

        # 4. scansione: troppe connessioni verso porte che non pubblichiamo
        #    (tieni l'elenco allineato con le porte aperte nel firewall principale)
        tcp dport != { 22, 80, 443 } ct state new update @scan4 { ip saddr limit rate over 15/minute } \
            add @bloccati4 { ip saddr } log prefix "nft-blocco-scan: " drop
        tcp dport != { 22, 80, 443 } ct state new update @scan6 { ip6 saddr limit rate over 15/minute } \
            add @bloccati6 { ip6 saddr } log prefix "nft-blocco-scan: " drop
    }
}

Come funzionano le regole

  • flags dynamic, timeout permette al firewall stesso di aggiungere elementi al set mentre passa il traffico, e a ognuno di avere una scadenza. timeout 120m è la scadenza usata quando un elemento viene aggiunto senza indicarne una.
  • update @ssh4 { ip saddr limit rate over 10/minute } tiene un contatore separato per ogni indirizzo sorgente. Finché un indirizzo resta sotto le 10 nuove connessioni al minuto, la regola si ferma lì e il pacchetto prosegue. Quando le supera, la regola continua: add @bloccati4 { ip saddr } inserisce l’indirizzo tra i bloccati per 120 minuti, log lo annota nel journal e drop scarta il pacchetto.
  • Dal pacchetto successivo l’indirizzo viene fermato dalla regola 2, quindi il log scrive una sola riga per ogni blocco.
  • accept in questa catena significa solo “questa tabella non ha obiezioni”: il pacchetto attraversa poi il firewall principale, che decide come sempre. Questa tabella può solo togliere, mai aprire.

Caricare la tabella

Il file /etc/nftables.conf di Ubuntu comincia con flush ruleset. Aggiungi in fondo una riga che includa i file della cartella, così la tabella viene ricaricata insieme al resto e all’avvio:

/etc/nftables.conf – in fondo al file
include "/etc/nftables.d/*.nft"
sudo nft -c -f /etc/nftables.conf      # controlla la sintassi senza applicare
sudo systemctl enable nftables
sudo systemctl reload nftables
sudo nft list table inet blocchi
Prima di applicare, metti il tuo indirizzo tra i fidati

Se gestisci il server da remoto, controlla che l’indirizzo da cui ti colleghi sia in fidati4 o fidati6. Un scp in un ciclo o un programma di monitoraggio che apre molte connessioni SSH supera facilmente le 10 al minuto, e ti ritroveresti chiuso fuori per due ore. Per lavorare tranquillo, prepara un paracadute come spiegato in Applicare le regole senza chiudersi fuori.

Bloccare e sbloccare a mano

# blocco con la scadenza predefinita (120 minuti)
sudo nft add element inet blocchi bloccati4 '{ 203.0.113.45 }'

# blocco con scadenza esplicita; un'intera rete richiede un set con flags interval
sudo nft add element inet blocchi bloccati4 '{ 198.51.100.7 timeout 120m }'
sudo nft add element inet blocchi bloccati6 '{ 2001:db8:bad::1 }'

# chi è bloccato e quanto manca alla scadenza
sudo nft list set inet blocchi bloccati4

# sblocco anticipato di un indirizzo, oppure di tutti
sudo nft delete element inet blocchi bloccati4 '{ 203.0.113.45 }'
sudo nft flush set inet blocchi bloccati4
table inet blocchi {
	set bloccati4 {
		type ipv4_addr
		size 65536
		flags dynamic,timeout
		timeout 2h
		elements = { 198.51.100.7 timeout 2h expires 1h41m12s,
			     203.0.113.45 timeout 2h expires 1h58m03s }
	}
}

nft scrive 120 minuti come 2h; expires è il tempo che resta prima che l’indirizzo venga sbloccato.

I blocchi vivono in memoria

Il contenuto dei set non viene salvato: un riavvio o un systemctl reload nftables li svuota. Con una durata di due ore è accettabile, perché gli attacchi ancora in corso vengono rilevati e bloccati di nuovo in pochi minuti.

Blocco automatico dai log con fail2ban

Le regole di nftables vedono i pacchetti, non sanno se una password era giusta o sbagliata. Per bloccare in base ai log serve fail2ban, che li legge in tempo reale e reagisce in pochi secondi. La configurazione completa è nella guida Fail2ban su Linux; qui conta solo come ottenere blocchi di esattamente 120 minuti.

/etc/fail2ban/jail.local
[DEFAULT]
# blocco fisso di 120 minuti, senza aumenti per i recidivi
bantime  = 120m
bantime.increment = false
findtime = 10m
maxretry = 5

banaction = nftables-multiport
banaction_allports = nftables-allports

# stessi indirizzi fidati della tabella nftables
ignoreip = 127.0.0.1/8 ::1 192.0.2.10

[sshd]
enabled = true
backend = systemd
mode    = aggressive

[nginx-http-auth]
enabled = true
logpath = /var/log/nginx/error.log

[nginx-file-sensibili]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/access.log
maxretry = 2

mode = aggressive fa contare a fail2ban anche le connessioni chiuse prima dell’autenticazione, quelle tipiche dei server con le sole chiavi. La jail nginx-file-sensibili usa un filtro che scriviamo noi:

/etc/fail2ban/filter.d/nginx-file-sensibili.conf
[Definition]
# richieste a file che non devono mai essere pubblici, a prescindere dalla risposta
failregex = ^<HOST> \S+ \S+ \[[^\]]+\] "(GET|POST|HEAD) /(\S*/)?(\.env|\.git/|wp-config\.php|\.aws/|\.ssh/)
ignoreregex =
sudo fail2ban-client -t                                    # verifica la configurazione
sudo fail2ban-regex /var/log/nginx/access.log nginx-file-sensibili   # prova il filtro sui log veri
sudo systemctl restart fail2ban
sudo fail2ban-client get sshd bantime                      # deve rispondere 7200

fail2ban crea una sua tabella, inet f2b-table, separata dalla nostra. Le due convivono senza problemi: un indirizzo può essere fermato dall’una o dall’altra, e in entrambe esce da solo dopo 120 minuti.

Senza fail2ban: uno script con timer systemd

Se preferisci non installare altro, o vuoi capire esattamente che cosa succede, lo stesso lavoro per SSH lo fa uno script di poche righe. Legge i tentativi falliti degli ultimi 10 minuti, conta gli eventi per indirizzo e mette nel set bloccati chi supera la soglia. Un timer di systemd lo esegue ogni 5 minuti.

Rispetto a fail2ban reagisce più lentamente, fino a 5 minuti, ma non ha dipendenze e scrive nel set che abbiamo già creato, quindi tutti i blocchi si vedono e si gestiscono in un solo posto.

/usr/local/sbin/blocca-violazioni
#!/bin/bash
# Blocca per 120 minuti gli indirizzi con troppi accessi SSH falliti.
set -euo pipefail

SOGLIA=5                  # eventi falliti...
FINESTRA="10 min ago"     # ...in questo intervallo
DURATA="120m"             # durata del blocco
TABELLA="inet blocchi"

# Indirizzi da non bloccare mai (espressione regolare)
FIDATI='^(127\.|::1$|192\.0\.2\.10$)'

eventi=$(journalctl -u ssh --since "$FINESTRA" -o cat --no-pager \
  | grep -E 'Failed password|Invalid user|invalid user|authenticating user' \
  | grep -oP '\S+(?= port \d+)' || true)

[[ -z $eventi ]] && exit 0

sort <<< "$eventi" | uniq -c | while read -r n ip; do
  (( n >= SOGLIA )) || continue
  [[ $ip =~ $FIDATI ]] && continue

  if   [[ $ip =~ ^[0-9.]+$ ]];       then set=bloccati4
  elif [[ $ip =~ ^[0-9a-fA-F:]+$ ]]; then set=bloccati6
  else continue                      # non è un indirizzo: si ignora
  fi

  # già bloccato: non serve rifarlo
  nft get element $TABELLA "$set" "{ $ip }" &> /dev/null && continue

  nft add element $TABELLA "$set" "{ $ip timeout $DURATA }"
  logger -t blocca-violazioni "bloccato $ip per $DURATA: $n eventi SSH falliti"
done
sudo chmod 750 /usr/local/sbin/blocca-violazioni
sudo /usr/local/sbin/blocca-violazioni           # prima prova a mano: nessun errore = ok
/etc/systemd/system/blocca-violazioni.service
[Unit]
Description=Blocca per 120 minuti gli IP con troppi accessi SSH falliti
After=nftables.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/blocca-violazioni
/etc/systemd/system/blocca-violazioni.timer
[Unit]
Description=Controllo degli accessi SSH falliti ogni 5 minuti

[Timer]
OnBootSec=2min
OnUnitActiveSec=5min

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now blocca-violazioni.timer
systemctl list-timers blocca-violazioni.timer

Qualche dettaglio sulle scelte dello script:

  • Prima di bloccare controlla se l’indirizzo è già nel set (nft get element). Così un indirizzo bloccato da 3 minuti, che compare ancora nella finestra degli ultimi 10, non vede ripartire il suo conto alla rovescia.
  • Ogni indirizzo viene verificato prima di passarlo a nft: nel log arriva testo scritto da altri, e lo script non deve mai usarlo senza controllarlo.
  • Il || true dopo i grep serve perché, con pipefail, “nessun tentativo trovato” verrebbe scambiato per un errore e systemd segnerebbe il servizio come fallito.
fail2ban o script, non tutti e due

Per SSH scegli uno dei due. Usarli insieme non rompe niente, ma ogni attaccante finirebbe in due elenchi e i conti diventerebbero confusi. Le regole nftables con soglia invece si affiancano bene a entrambi, perché lavorano sui pacchetti e non sui log.

Verificare che funzioni

Prova da una macchina che non sia tra i fidati, per esempio un server di prova o la connessione del telefono, e tieni aperta una sessione da un indirizzo fidato.

# dalla macchina di prova: 6 accessi con un utente che non esiste
for i in 1 2 3 4 5 6; do ssh -o BatchMode=yes -o ConnectTimeout=5 utente-prova@server.example.it true; done
# sul server: l'indirizzo di prova compare nel set?
sudo nft list set inet blocchi bloccati4         # script o regole nftables
sudo fail2ban-client status sshd                 # fail2ban

# quante volte sono scattati i blocchi
sudo nft list chain inet blocchi input | grep counter
sudo journalctl -k --since today --grep 'nft-blocco' -o cat | wc -l
sudo journalctl -t blocca-violazioni --since today

Dalla macchina di prova la connessione ora deve restare appesa fino al timeout: il pacchetto viene scartato senza risposta. Dopo 120 minuti torna a funzionare da sola. Se non vuoi aspettare, sblocca l’indirizzo a mano con nft delete element o fail2ban-client set sshd unbanip.

Un riepilogo giornaliero

echo "== Bloccati adesso ==";  sudo nft list set inet blocchi bloccati4 | grep -c expires
echo "== Blocchi scattati oggi (nftables) =="
sudo journalctl -k --since today --grep 'nft-blocco' -o cat | grep -oP 'nft-blocco-\w+' | sort | uniq -c
echo "== Accessi SSH riusciti oggi =="
sudo journalctl -u ssh --since today -o cat | grep Accepted

L’ultima parte è la più importante: tutto il resto dice chi è stato fermato, questa dice chi è entrato.

Problemi comuni

SintomoCausa probabile e soluzione
Mi sono chiuso fuoriIl tuo indirizzo non era tra i fidati. Entra dalla console del provider, esegui nft delete element inet blocchi bloccati4 '{ tuo-ip }' e aggiungi l’indirizzo a fidati4 e a ignoreip di fail2ban.
Dopo un reload i blocchi sono sparitiÈ normale: flush ruleset ricrea i set vuoti. Dopo il reload riavvia anche fail2ban (systemctl restart fail2ban), che ricrea la sua tabella e i ban ancora validi.
Error: Could not process rule: No such file or directoryLa tabella inet blocchi non è caricata: manca la riga include in /etc/nftables.conf o il servizio nftables non è attivo.
Viene bloccato un intero ufficio o una rete mobileDietro un NAT aziendale o un CGNAT dell’operatore molti utenti escono con lo stesso indirizzo. Alza le soglie per quel servizio o metti la rete dell’ufficio tra i fidati.
Viene bloccato il proxy o la CDNNei log compare l’indirizzo del proxy, non quello del client. Configura il server web per registrare l’IP reale, e fai il blocco sul proxy: quello locale non ferma il traffico che arriva da lì.
Gli attacchi IPv6 continuano da indirizzi sempre diversiCon IPv6 un attaccante ha a disposizione un’intera rete /64 e cambia indirizzo a ogni tentativo. Bloccare il singolo indirizzo serve a poco: in questi casi aggiungi a mano l’intera /64 a un set con flags interval.
Lo script non blocca nullaEsegui a mano la prima parte della pipeline (journalctl … | grep …) e guarda se trova gli indirizzi. Se è vuota, controlla l’unità (-u ssh) e che lo script giri come root.

Il passo successivo

Bloccare per due ore chi insiste è il livello più esterno della difesa. Sotto devono esserci un firewall con policy restrittiva, che lasci aperte solo le porte che servono (Firewall completo con nftables), e SSH con le sole chiavi e senza accesso diretto di root (SSH sicuro su Linux). Con queste due basi in ordine, la maggior parte dei tentativi che vedi nei log non ha nessuna possibilità di riuscire, e i blocchi servono soprattutto a tenere puliti i log e a far emergere quello che conta davvero.

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.