Obiettivo e scenario
SSH è la porta d’ingresso di ogni server Linux. Una configurazione predefinita funziona, ma accetta password, permette tentativi ripetuti e non limita chi può entrare. In questa guida la rendi solida, un passo alla volta:
- accesso solo con chiavi Ed25519, niente password;
- niente accesso diretto di
root, solo gli utenti di un gruppo dedicato; - configurazione in un file separato, verificabile prima di applicarla;
- facoltativo: secondo fattore TOTP e accesso tramite un jump host.
Gli indirizzi sono di documentazione o privati: sostituiscili con i tuoi. Gli esempi usano l’utente admin; il server di partenza è un Ubuntu 24.04 con OpenSSH 9.6.
Per tutta la guida tieni aperta la sessione SSH che stai usando. Ogni modifica la provi da un secondo terminale. Le sessioni già aperte sopravvivono al riavvio di sshd: se la nuova configurazione ti blocca, correggi dalla prima. Se il server è una VM, tieni a portata di mano anche la console dell’hypervisor.
Chiavi Ed25519
1. Generare la coppia di chiavi sul tuo PC
La chiave si crea sul client, mai sul server. Ed25519 è veloce, ha chiavi corte ed è supportata da tutte le versioni recenti di OpenSSH:
ssh-keygen -t ed25519 -a 100 -C "admin@portatile"
-a 100 indica i cicli di derivazione usati per cifrare la chiave privata con la passphrase: rende molto più lento un attacco a forza bruta se qualcuno ti ruba il file. Metti sempre una passphrase e usa ssh-agent per non doverla digitare a ogni connessione. Ottieni due file:
| File | Contenuto | Dove va |
|---|---|---|
~/.ssh/id_ed25519 | Chiave privata (cifrata con la passphrase) | Resta solo sul tuo PC |
~/.ssh/id_ed25519.pub | Chiave pubblica | Sui server, in ~/.ssh/authorized_keys |
2. Copiare la chiave pubblica sul server
Finché l’accesso con password è attivo, il modo più semplice è ssh-copy-id:
ssh-copy-id -i ~/.ssh/id_ed25519.pub admin@10.0.20.11
Il comando aggiunge la chiave in fondo a ~/.ssh/authorized_keys dell’utente remoto e sistema i permessi. Se lo fai a mano, controlla che siano questi, altrimenti sshd ignora la chiave (opzione StrictModes, attiva per impostazione predefinita):
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
# la home non deve essere scrivibile da altri
chmod go-w ~Sul PC vale lo stesso: la chiave privata deve avere permessi 600, altrimenti il client la rifiuta con il messaggio “UNPROTECTED PRIVATE KEY FILE”.
3. Provare l’accesso con la chiave
ssh -i ~/.ssh/id_ed25519 admin@10.0.20.11
Ti deve chiedere la passphrase della chiave, non la password dell’utente. Se ti chiede la password, la chiave non viene accettata: risolvi adesso, prima di disattivare le password.
4. Un gruppo per chi può entrare
Invece di elencare gli utenti uno per uno, crea un gruppo e consenti SSH solo ai suoi membri. Aggiungere o togliere un amministratore diventa una sola operazione:
sudo groupadd ssh-utenti
sudo usermod -aG ssh-utenti admin
id admin # deve comparire ssh-utenti
Fallo prima di attivare AllowGroups: un utente fuori dal gruppo viene respinto anche con la chiave giusta.
La configurazione di sshd
Perché un file separato
Su Ubuntu 24.04 il file /etc/ssh/sshd_config contiene, in cima, la riga Include /etc/ssh/sshd_config.d/*.conf. Per quasi tutte le direttive sshd usa il primo valore che incontra: quello che scrivi nei file inclusi prevale quindi sul resto di sshd_config. Tra i file inclusi, vince quello che viene prima in ordine alfabetico.
Per questo il file si chiama 10-sicurezza.conf: viene letto prima, per esempio, di 50-cloud-init.conf, che sulle immagini cloud contiene spesso PasswordAuthentication yes. Il file principale resta intatto e gli aggiornamenti del pacchetto non ti chiedono di unire modifiche.
# --- Autenticazione ---
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no
AuthenticationMethods publickey
# --- Chi può entrare ---
AllowGroups ssh-utenti
# --- Limiti ai tentativi ---
MaxAuthTries 3
LoginGraceTime 30
MaxStartups 10:30:60
# --- Funzioni non necessarie ---
X11Forwarding no
AllowAgentForwarding no
# su un jump host questa riga va tolta: vedi più avanti
AllowTcpForwarding no
# --- Sessioni inattive e log ---
ClientAliveInterval 300
ClientAliveCountMax 2
LogLevel VERBOSE| Direttiva | Effetto |
|---|---|
PermitRootLogin no | Root non entra via SSH, nemmeno con la chiave. Entri come utente e usi sudo. |
PasswordAuthentication no | Disattiva le password: gli attacchi a dizionario diventano inutili. |
KbdInteractiveAuthentication no | Disattiva anche la richiesta “interattiva” via PAM, che altrimenti chiederebbe comunque la password. Sostituisce la vecchia ChallengeResponseAuthentication. |
AuthenticationMethods publickey | Rende esplicito che l’unico metodo accettato è la chiave. |
AllowGroups ssh-utenti | Entra solo chi appartiene al gruppo. Tutti gli altri utenti, anche di sistema, sono esclusi. |
MaxAuthTries 3 | Dopo 3 tentativi falliti nella stessa connessione, sshd chiude. |
LoginGraceTime 30 | Secondi a disposizione per completare l’accesso (predefinito 120). |
MaxStartups 10:30:60 | Oltre 10 connessioni non ancora autenticate ne scarta il 30%, fino al 100% a quota 60. È il valore predefinito: lo scrivo per renderlo visibile. |
X11Forwarding no, AllowAgentForwarding no, AllowTcpForwarding no | Disattivano inoltri che su un server di solito non servono e che allargano la superficie d’attacco. |
ClientAliveInterval 300 e ClientAliveCountMax 2 | Chiude le sessioni il cui client non risponde più da circa 10 minuti. |
LogLevel VERBOSE | Registra l’impronta della chiave usata a ogni accesso: sai sempre quale chiave è entrata. |
UsePAM yes resta come nel file principale di Ubuntu: serve per sessioni, limiti e, se lo attivi, per il secondo fattore.
L’Include legge solo i file *.conf. Un 10-sicurezza.txt o un 10-sicurezza.conf.bak vengono ignorati senza alcun avviso.
Verificare e applicare senza chiudersi fuori
1. Controllo della sintassi
sudo sshd -t
Se non stampa nulla, la sintassi è corretta. Un errore indica file e riga. Non riavviare mai sshd se sshd -t segnala problemi.
2. Controllo della configurazione effettiva
sshd -T stampa i valori che sshd userà davvero, dopo aver letto tutti i file. È il modo più sicuro per scoprire se un altro file ti sta sovrascrivendo:
sudo sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication|authenticationmethods|allowgroups|maxauthtries|x11forwarding)'
Le direttive compaiono in minuscolo. Per vedere come si applicano eventuali blocchi Match a una connessione specifica:
sudo sshd -T -C user=admin,host=pc-admin,addr=10.0.99.25 | grep -i authenticationmethods
3. Applicare
sudo systemctl restart ssh
Su Ubuntu il servizio si chiama ssh, non sshd. La sessione corrente resta aperta.
4. Provare da un secondo terminale
# deve entrare con la chiave
ssh admin@10.0.20.11
# deve essere respinto: "Permission denied (publickey)"
ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password admin@10.0.20.11
# deve essere respinto anche root
ssh root@10.0.20.11
Solo quando il primo comando funziona e gli altri due falliscono puoi chiudere la sessione originale. Nel registro trovi il motivo di ogni rifiuto:
sudo journalctl -u ssh -f
Ubuntu 24.04: ssh.socket e cambio di porta
Da Ubuntu 22.10 sshd parte tramite socket activation di systemd: è l’unità ssh.socket ad ascoltare sulla porta 22 e ad avviare ssh.service alla prima connessione. Puoi vederlo così:
systemctl status ssh.socket
sudo ss -tlnp | grep ':22' # in ascolto c'è systemd, non sshd
Per quasi tutte le direttive non cambia nulla: modifichi il file e riavvii ssh. Cambia invece per Port, ListenAddress e AddressFamily, perché le porte in ascolto le apre la socket, non sshd.
Come funziona oggi
Ubuntu 24.04 include un generatore di systemd che legge Port e ListenAddress da sshd_config (file inclusi compresi) e configura di conseguenza ssh.socket. Il generatore però gira solo quando systemd ricarica la configurazione. Dopo aver cambiato la porta servono quindi due comandi, che trovi anche nel commento in cima a sshd_config:
# 1. apri PRIMA la nuova porta nel firewall (esempio con UFW)
sudo ufw allow 2222/tcp
# 2. aggiungi "Port 2222" al tuo file e verifica
echo 'Port 2222' | sudo tee -a /etc/ssh/sshd_config.d/10-sicurezza.conf
sudo sshd -t
# 3. rigenera la socket e riavviala
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
# 4. controlla e prova da un altro terminale
sudo ss -tlnp | grep ':2222'
ssh -p 2222 admin@10.0.20.11Un semplice systemctl restart ssh non basta: sshd leggerebbe la nuova porta, ma la socket continuerebbe ad ascoltare sulla 22. Il comportamento è stato corretto con gli aggiornamenti di OpenSSH per la 24.04 (bug Launchpad 2069041): tieni il sistema aggiornato.
Riduce il rumore nei log, perché i robot provano soprattutto la 22, ma chi ti cerca davvero trova la porta in pochi secondi. La protezione vera sono le chiavi, AllowGroups e un firewall che limita gli indirizzi di origine.
Tornare al servizio classico (facoltativo)
Se preferisci che sshd resti sempre in esecuzione e apra le porte da solo, come nelle versioni precedenti:
sudo systemctl disable --now ssh.socket
# eventuali override lasciati dagli aggiornamenti dalle versioni precedenti
sudo rm -f /etc/systemd/system/ssh.service.d/00-socket.conf
sudo rm -f /etc/systemd/system/ssh.socket.d/addresses.conf
sudo systemctl daemon-reload
sudo systemctl enable --now ssh.service
sudo ss -tlnp | grep sshd
Fallo da console o tenendo aperta la sessione: tra i due comandi nessuno ascolta sulla porta SSH, ma le sessioni esistenti restano attive.
Algoritmi crittografici (facoltativo)
OpenSSH 9.6 ha già impostazioni predefinite moderne: niente algoritmi davvero deboli attivi. Restringere ulteriormente l’elenco è utile se devi rispettare una politica aziendale o un controllo di conformità, ma va fatto con prudenza: un client vecchio, un apparato di rete o uno strumento di backup potrebbero non riuscire più a collegarsi.
Prima guarda cosa supporta il server:
ssh -Q kex
ssh -Q cipher
ssh -Q mac
sudo sshd -T | grep -Ei '^(kexalgorithms|ciphers|macs)'
Un elenco ristretto e ancora molto compatibile con i client degli ultimi anni:
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.comsntrup761x25519-sha512@openssh.com è uno scambio di chiavi ibrido resistente ai computer quantistici, già predefinito in OpenSSH 9. Le versioni più recenti (dalla 9.9) aggiungono mlkem768x25519-sha256, che però non è disponibile nel pacchetto di Ubuntu 24.04. Verifica sempre con sshd -t e prova da tutti i client che usi. Per un controllo dall’esterno c’è il pacchetto ssh-audit:
sudo apt install ssh-audit
ssh-audit 10.0.20.11
Secondo fattore TOTP (facoltativo)
Sui server più esposti, per esempio il jump host, puoi chiedere oltre alla chiave un codice a 6 cifre generato da un’app sul telefono. Si usa il modulo PAM di Google Authenticator, compatibile con qualsiasi app TOTP.
1. Installare e attivare per l’utente
sudo apt install libpam-google-authenticator
# come utente admin (non con sudo):
google-authenticator -t -d -f -r 3 -R 30 -w 3
Le opzioni: codici basati sull’ora (-t), ogni codice usabile una sola volta (-d), al massimo 3 accessi ogni 30 secondi (-r 3 -R 30), tolleranza di un codice prima e dopo (-w 3). Inquadra il QR code con l’app e conserva i codici di emergenza in un posto sicuro. Il segreto finisce in ~/.google_authenticator. L’orologio del server deve essere sincronizzato: controlla con timedatectl.
2. Configurare PAM
# Standard Un*x authentication.
# commentata: con la chiave come primo fattore non serve la password
#@include common-auth
# secondo fattore TOTP
auth required pam_google_authenticator.soCommentare common-auth evita che, dopo la chiave, venga chiesta anche la password. Durante il passaggio puoi scrivere pam_google_authenticator.so nullok: gli utenti che non hanno ancora configurato il TOTP entrano con la sola chiave. Toglilo appena tutti sono pronti.
3. Configurare sshd
Modifica lo stesso file 10-sicurezza.conf: per la regola del primo valore, un file 20-… non riuscirebbe a sovrascrivere i valori già impostati.
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactivesudo sshd -t && sudo systemctl restart ssh
La virgola significa “entrambi, in quest’ordine”: prima la chiave, poi il codice. Prova da un secondo terminale: dopo la passphrase della chiave deve comparire Verification code:. PasswordAuthentication resta no.
Jump host con ProxyJump
Esporre su Internet un solo server, il jump host, e raggiungere gli altri passando da lì riduce molto la superficie d’attacco. Con ProxyJump il jump host si limita a inoltrare la connessione TCP: la sessione SSH è cifrata dal tuo PC fino al server finale, e l’autenticazione con la chiave avviene direttamente su quest’ultimo. La chiave privata non passa mai dal jump host.
Lato client: ~/.ssh/config
Host bastion
HostName 203.0.113.20
User admin
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
Host srv-*
User admin
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
ProxyJump bastion
Host srv-web
HostName 10.0.20.11
Host srv-db
HostName 10.0.20.12Ora ssh srv-web passa automaticamente dal jump host. Anche scp e rsync usano questo file. Senza file di configurazione, l’equivalente al volo è:
ssh -J admin@203.0.113.20 admin@10.0.20.11
IdentitiesOnly yes fa offrire al client solo la chiave indicata: se nell’agent ne hai molte, eviti di esaurire i tentativi di MaxAuthTries.
Con ForwardAgent yes l’agent del tuo PC diventa utilizzabile dal jump host: chi controlla quel server, finché sei collegato, può usare le tue chiavi per entrare altrove. ProxyJump non ha bisogno dell’inoltro dell’agent.
Lato jump host
ProxyJump richiede che sul jump host sia permesso l’inoltro TCP. Nel suo 10-sicurezza.conf sostituisci AllowTcpForwarding no con un inoltro limitato alle sole porte SSH dei server interni:
AllowTcpForwarding local
PermitOpen 10.0.20.11:22 10.0.20.12:22
PermitTTY yesAllowTcpForwarding local consente solo l’inoltro dal client verso il server (quello che usa ProxyJump), non quello inverso. PermitOpen elenca le uniche destinazioni raggiungibili. Sui server interni, infine, permetti SSH solo dall’indirizzo LAN del jump host (10.0.20.1) e dalla rete di amministrazione.
Firewall e fail2ban
La configurazione di sshd decide come si entra; il firewall decide da dove. Le due cose si completano.
- Limita gli indirizzi di origine: se gli amministratori arrivano da reti note (sede, VPN), consenti SSH solo da lì. Con UFW:
sudo ufw allow from 10.0.99.0/24 to any port 22 proto tcp. Con nftables vedi la guida Firewall completo con nftables. - Rallenta i tentativi sulle porte che devono restare aperte a tutti:
sudo ufw limit 22/tcprespinge un indirizzo che tenta 6 o più connessioni in 30 secondi. - Banna chi insiste: fail2ban legge i log di sshd e blocca per un periodo gli indirizzi che sbagliano ripetutamente. La configurazione è nella guida Proteggere i servizi con fail2ban.
Con le sole chiavi gli attacchi a dizionario non possono riuscire, ma generano rumore nei log e consumano risorse: firewall e fail2ban li fermano prima che arrivino a sshd. Se ti serve accedere da Internet senza esporre la porta 22, valuta una VPN: vedi VPN IPsec tra Windows e Ubuntu.
Problemi comuni
| Sintomo | Causa probabile | Cosa fare |
|---|---|---|
Permission denied (publickey) con la chiave giusta | Permessi troppo larghi su home, ~/.ssh o authorized_keys | Applica 700 e 600; nel log cerca “bad ownership or modes” |
| Respinto, nel log “not allowed because none of user’s groups are listed in AllowGroups” | Utente non nel gruppo ssh-utenti | sudo usermod -aG ssh-utenti utente; vale dalla connessione successiva |
| Chiede ancora la password | Il file non finisce in .conf, oppure un file letto prima imposta PasswordAuthentication yes | Controlla con sudo sshd -T | grep -i passwordauth e ls /etc/ssh/sshd_config.d/ |
| “Too many authentication failures” | Il client offre troppe chiavi dall’agent e supera MaxAuthTries | IdentitiesOnly yes e IdentityFile nel ~/.ssh/config |
| Nuova porta ignorata, sshd ascolta ancora sulla 22 | Socket activation: la porta la apre ssh.socket | sudo systemctl daemon-reload e sudo systemctl restart ssh.socket |
sshd -t: “Missing privilege separation directory: /run/sshd” | Con la socket activation la cartella viene creata solo all’avvio del servizio | sudo mkdir -p /run/sshd e ripeti il test; non è un errore di configurazione |
| ProxyJump fallisce con “channel 0: open failed: administratively prohibited” | Inoltro TCP vietato sul jump host o destinazione non in PermitOpen | Sul jump host AllowTcpForwarding local e controlla PermitOpen |
| Dopo il TOTP chiede anche la password | @include common-auth ancora attivo in /etc/pam.d/sshd | Commentalo e riavvia ssh |
| Fuori dal server, nessuna sessione aperta | Configurazione errata applicata | Accedi dalla console (hypervisor, IPMI, cloud), correggi il file, sshd -t, riavvia |
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.