Configurare un nuovo server root: checklist per i primi 30 minuti

Pubblicato il 16 min di lettura

I primi 30 minuti su un nuovo server root decidono tutto il resto. Nove passaggi nell'ordine giusto, comprese le trappole di Debian 13 e Ubuntu 24.04 che nella maggior parte delle guide mancano.

Un nuovo server root è raggiungibile dal primo secondo e viene scansionato dal primo minuto. I tentativi di accesso automatizzati sulla porta 22 iniziano, per esperienza, prima ancora che tu abbia fatto il tuo primo login. Questa lista porta un server appena consegnato, in circa mezz'ora, in uno stato in cui puoi lasciarlo lavorare con tranquillità.

Tutti i comandi presuppongono che tu stia lavorando come root, esattamente come subito dopo il provisioning. Non appena sei collegato con il tuo nuovo utente, anteponi sudo a ogni comando. La sequenza è verificata su Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS e Ubuntu 22.04 LTS. Dove i quattro sistemi si comportano in modo diverso, lo trovi indicato.

Passo 0: assicurati la via di ritorno prima di cambiare qualcosa

L'unico errore di questa lista che non puoi riparare via SSH è proprio quello che ti toglie SSH. Per i prossimi 30 minuti vale quindi una sola regola ferrea: apri una seconda finestra di terminale con una connessione SSH attiva e non chiuderla. Una sessione SSH già aperta sopravvive sia al riavvio del servizio SSH sia all'attivazione del firewall. Se dopo una modifica una nuova connessione non funziona più, annulla la modifica dalla sessione ancora aperta.

Vale anche la pena sapere dove si trova l'accesso alla console del tuo server prima di averne bisogno. Da KernelHost lo raggiungi nell'area clienti, indipendentemente da SSH e indipendentemente dal firewall. Chi comincia a cercarlo solo quando è già chiuso fuori perde tempo.

Questi messaggi di errore conviene saperli distinguere, perché indicano cause completamente diverse:

  • ssh: connect to host 203.0.113.10 port 22: Connection refused significa che il pacchetto è arrivato, ma sulla porta non ascolta nessuno. Il servizio SSH non è in esecuzione oppure ascolta su un'altra porta.
  • ssh: connect to host 203.0.113.10 port 22: Connection timed out significa che il pacchetto è stato scartato. Nella quasi totalità dei casi si tratta del firewall.
  • Permission denied (publickey) significa che il servizio è attivo e il firewall ti lascia passare, semplicemente la tua chiave non va bene.

Passo 1: aggiornare il sistema

Un'immagine appena installata è raramente aggiornata. Tra la creazione dell'immagine e il tuo ordine passano spesso settimane, e nel frattempo sono usciti aggiornamenti di sicurezza.

cat /etc/os-release
apt update
apt full-upgrade -y

full-upgrade al posto di upgrade è una scelta deliberata: su un sistema appena installato apt può rimuovere pacchetti se una dipendenza lo richiede. Su un sistema in produzione già avviato controlleresti prima che cosa verrebbe rimosso.

Su Ubuntu 22.04 e 24.04 needrestart è preinstallato e interrompe l'upgrade con una schermata colorata a tutto schermo che chiede quali servizi riavviare. Se non lo vuoi, per esempio dentro uno script:

NEEDRESTART_MODE=a DEBIAN_FRONTEND=noninteractive apt full-upgrade -y

Poi fai pulizia e verifica se serve un riavvio:

apt autoremove --purge -y
apt list --upgradable
test -f /var/run/reboot-required && echo "riavvio necessario" || echo "nessun riavvio necessario"

Differenze tra le distribuzioni: il file /var/run/reboot-required viene creato in modo affidabile solo da Ubuntu, arriva dal pacchetto update-notifier-common. Debian, di base, non segnala affatto un riavvio necessario. Su Debian installi a questo scopo needrestart, che al richiamo ti dice se è pronto un kernel nuovo. Debian 13 porta inoltre una generazione più recente di apt, con output colorato e formattato in colonne: non è un errore, è solo insolito.

Verifica: apt list --upgradable non stampa più nulla oltre alla riga iniziale Listing.... Se durante l'aggiornamento compare il messaggio Release file for ... is not valid yet, l'orologio del tuo server è sbagliato: salta al passo 5 e ripeti poi il passo 1.

Approfondimento, anche sulla gestione dei pacchetti trattenuti e delle repository di terze parti: Aggiornare un server Linux con apt.

Passo 2: creare un utente invece di lavorare come root

Come root non si lavora, perché ogni errore di battitura colpisce subito l'intero sistema e perché il nome utente root lo conosce già qualsiasi attaccante. Sulle immagini minimali di Debian sudo spesso non è nemmeno installato:

apt install -y sudo
adduser --disabled-password --gecos "" kernel
usermod -aG sudo kernel

L'opzione --disabled-password crea l'utente senza password, che è esattamente ciò che serve per un accesso basato solo su chiave. Se vuoi impostare comunque una password, per esempio per usare sudo dalla console, lo fai con passwd kernel.

Differenze tra le distribuzioni: su Debian e Ubuntu il gruppo degli amministratori si chiama sudo. Solo se arrivi da un sistema di tipo RHEL cerchi wheel, che qui non esiste.

Adesso la chiave pubblica. Crea la directory con i permessi corretti, perché i permessi sbagliati sono la causa più frequente di un accesso a chiave rifiutato:

mkdir -p /home/kernel/.ssh
chmod 700 /home/kernel/.ssh
touch /home/kernel/.ssh/authorized_keys
chmod 600 /home/kernel/.ssh/authorized_keys
chown -R kernel:kernel /home/kernel/.ssh

Il contenuto della tua chiave pubblica va inserito in authorized_keys, ma dal tuo computer è più comodo farlo con ssh-copy-id kernel@203.0.113.10.

Verifica, e da fare prima di toccare SSH:

id kernel
sudo -l -U kernel

La seconda riga deve contenere (ALL : ALL) ALL. Poi collegati in una terza finestra come kernel ed esegui una volta sudo -v. Solo quando questo funziona si prosegue. Dettagli: Creare un utente e configurare sudo e Creare e installare chiavi SSH.

Passo 3: mettere in sicurezza SSH

Sulle immagini Debian più essenziali il server SSH non è nemmeno installato, in quel caso lo aggiungi per prima cosa:

apt install -y openssh-server

Tutti e quattro i sistemi trattati qui leggono configurazione aggiuntiva da /etc/ssh/sshd_config.d/. Non modificare quindi il grande sshd_config, crea invece un file tuo. Quello sopravvive agli aggiornamenti dei pacchetti senza porti domande.

Un dettaglio che quasi tutte le guide sbagliano: nella configurazione SSH vince il primo valore trovato, non l'ultimo. Su Debian e Ubuntu la riga Include /etc/ssh/sshd_config.d/*.conf si trova proprio in cima, e i file in quella directory vengono letti in ordine alfabetico. Sulle immagini Ubuntu spesso c'è già 50-cloud-init.conf con PasswordAuthentication yes. Un file chiamato 99-... resterebbe quindi senza effetto. Guarda prima che cosa c'è già:

ls -l /etc/ssh/sshd_config.d/
cat > /etc/ssh/sshd_config.d/10-kernelhost.conf <<'EOF'
PermitRootLogin prohibit-password
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
X11Forwarding no
MaxAuthTries 3
EOF

prohibit-password al posto di no è una scelta consapevole: root può ancora accedere con la chiave, ma mai con la password. Ti salva quando qualcosa va storto con l'utente sudo. Chi vuole stringere di più imposta no, ma a quel punto deve avere davvero provato l'accesso alla console.

Prima di ogni riavvio del servizio, controlla la sintassi:

ssh-keygen -A
sshd -t && echo "configurazione ok"
sshd -T | grep -E "^(permitrootlogin|passwordauthentication|pubkeyauthentication|port) "

sshd -T mostra i valori realmente in vigore, dopo la risoluzione di tutti i file inclusi. È l'unica prova affidabile che la tua modifica sia arrivata a destinazione. Se il comando segnala sshd: no hostkeys available -- exiting, mancano le host key e ssh-keygen -A le genera. Se invece il controllo si interrompe con Missing privilege separation directory: /run/sshd, il servizio non è mai partito da quando il sistema è stato avviato e manca la directory di runtime. Un mkdir -p /run/sshd oppure un systemctl restart ssh la crea, dopodiché sshd -t torna a valutare la tua configurazione.

Un output crea regolarmente confusione: per PermitRootLogin prohibit-password il comando sshd -T stampa la riga permitrootlogin without-password. È lo stesso valore sotto il suo nome più vecchio e non indica affatto che la tua impostazione non sia arrivata.

Solo a questo punto:

systemctl restart ssh

La trappola del socket su Ubuntu 24.04 e Debian 13

Da Ubuntu 22.10, e quindi anche nella 24.04, SSH viene avviato tramite socket activation. La conseguenza: una riga Port nella configurazione di sshd viene ignorata, la porta arriva da ssh.socket. Chi vuole cambiare la porta ha bisogno di un overlay systemd:

systemctl edit ssh.socket

Al suo interno metti questo, dove la prima riga vuota cancella l'impostazione predefinita:

[Socket]
ListenStream=
ListenStream=0.0.0.0:2222
ListenStream=[::]:2222

Poi systemctl daemon-reload e systemctl restart ssh.socket. Ubuntu 22.04 non conosce ancora questo meccanismo, lì basta la riga Port nella configurazione.

Su Debian 13 si presenta una stranezza analoga. Alcune immagini nuove hanno ssh.socket attivo, i sistemi aggiornati da Debian 12 no. Se sono attivi entrambi contemporaneamente, un reload fallisce con fatal: Cannot bind any address., perché servizio e socket si contendono la porta 22. Controlla e, nel dubbio, decidi:

systemctl is-enabled ssh.socket
systemctl disable --now ssh.socket
systemctl enable --now ssh.service

Verifica: ss -tlnp | grep ssh mostra la porta attesa e un nuovo tentativo di connessione da una finestra appena aperta va a buon fine. In dettaglio: Mettere in sicurezza SSH e disattivare il login di root e Cambiare la porta SSH.

Passo 4: attivare il firewall

Su Ubuntu ufw è installato, ma inattivo. Sulle immagini minimali di Debian manca del tutto. Qui l'ordine è vitale: prima consenti SSH, poi accendi.

apt install -y ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

L'ultimo comando è il più importante di tutta la lista. Regole e policy predefinite da sole non filtrano niente, è ufw enable ad armare il firewall. Chi lo salta si ritrova alla fine con un firewall configurato in ogni dettaglio, ma privo di effetto.

All'attivazione ufw chiede: Command may disrupt existing ssh connections. Proceed with operation (y|n)?. L'avviso è serio, ma se la regola per la porta 22 (oppure per la porta che hai cambiato) viene prima, non succede nulla, e proprio per questo ufw allow 22/tcp compare nella lista sopra ufw enable. Se al passo 3 hai cambiato la porta, qui deve esserci ufw allow 2222/tcp, altrimenti ti chiudi fuori. In uno script o in una sessione non interattiva usa ufw --force enable, che salta la domanda.

Verifica:

ufw status verbose

Ti aspetti Status: active, sotto Default: deny (incoming), allow (outgoing) e nell'elenco delle regole una riga 22/tcp ALLOW IN per la tua porta SSH. Se lì compare ancora Status: inactive, manca ufw enable e non c'è niente di protetto, per quanto complete possano sembrare le regole. Apri poi una nuova finestra e connettiti prima di chiudere quella vecchia.

A completamento serve anche un meccanismo di blocco contro i tentativi di accesso:

apt install -y fail2ban python3-systemd
cat > /etc/fail2ban/jail.local <<'EOF'
[DEFAULT]
backend = systemd
bantime = 1h
findtime = 10m
maxretry = 5

[sshd]
enabled = true
EOF

Perché backend = systemd: dalla versione 12 Debian non installa più rsyslog, quindi non esiste alcun /var/log/auth.log. Il valore predefinito backend = auto cerca esattamente quel file e fail2ban non parte affatto, con il messaggio Failed during configuration: Have not found any log file for sshd jail. Il pacchetto python3-systemd è il presupposto perché l'accesso al journal funzioni. Su Ubuntu 22.04 e 24.04 il file esiste ancora grazie a rsyslog, ma anche lì la variante systemd funziona ed è la scelta a prova di futuro. Puoi controllare così:

test -f /var/log/auth.log && echo "auth.log presente" || echo "nessun auth.log, serve backend systemd"
fail2ban-client -t

Verifica: fail2ban-client status sshd mostra una riga Currently banned. Se invece arriva Sorry but the jail 'sshd' does not exist, la configurazione non è stata caricata. Altro in Configurare il firewall UFW e Configurare Fail2ban.

Passo 5: fuso orario e ora di sistema

Un orologio sbagliato rende i log inutilizzabili, fa fallire le verifiche dei certificati e può bloccare apt con Release file is not valid yet. Su un server vero:

timedatectl set-timezone Europe/Vienna
timedatectl status

Nell'output devono tornare queste righe: Time zone: Europe/Vienna, System clock synchronized: yes e NTP service: active. Se lì c'è NTP service: inactive, non è in funzione nessuna sincronizzazione oraria. Ubuntu porta con sé systemd-timesyncd di serie, le immagini minimali di Debian spesso no:

DEBIAN_FRONTEND=noninteractive apt install -y systemd-timesyncd tzdata
date

Molti operatori lasciano volutamente i server su UTC, così i log di sedi diverse restano confrontabili. Entrambe le scelte sono difendibili, decisivo è che tu sappia quale hai fatto. Se timedatectl non è disponibile in un ambiente container, funziona anche il metodo classico:

ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime
dpkg-reconfigure -f noninteractive tzdata

Approfondimento: Configurare fuso orario e sincronizzazione oraria.

Passo 6: impostare l'hostname

L'hostname compare nei log, nelle email in uscita e nei messaggi di monitoraggio. Impostalo presto, altrimenti più avanti tutti i tuoi server si chiameranno allo stesso modo.

hostnamectl set-hostname srv01.tuo-dominio.it
hostname -f

Subito dopo serve una voce corrispondente in /etc/hosts, altrimenti a ogni richiamo di sudo ti accoglie il messaggio sudo: unable to resolve host srv01: Name or service not known, con un ritardo ben percepibile. La riga suona più o meno così: 127.0.1.1 srv01.tuo-dominio.it srv01.

La trappola: sulle immagini con cloud-init, che su Ubuntu sono la regola, l'hostname viene riportato indietro al riavvio successivo. L'interruttore che lo impedisce:

command -v cloud-init || echo "cloud-init non installato"
mkdir -p /etc/cloud/cloud.cfg.d
printf 'preserve_hostname: true\n' > /etc/cloud/cloud.cfg.d/99_hostname.cfg

Verifica: dopo un riavvio hostnamectl restituisce ancora il tuo nome. Dettagli: Cambiare l'hostname su Linux in modo permanente.

Passo 7: aggiornamenti di sicurezza automatici

Il server più pericoloso è quello che nessuno tocca più. Gli aggiornamenti di sicurezza automatici sono il singolo passo più efficace di questa lista.

apt install -y unattended-upgrades
cat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
EOF

Installare il pacchetto da solo non basta ovunque, è questo file ad attivare davvero l'esecuzione quotidiana. Le regolazioni personali vanno in un file con un numero più alto di quello fornito con il pacchetto, 50unattended-upgrades, così vincono e non vengono sovrascritte a ogni aggiornamento:

cat > /etc/apt/apt.conf.d/52unattended-upgrades-local <<'EOF'
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::MinimalSteps "true";
EOF

Di serie lo strumento, su entrambe le distribuzioni, attinge solo alla repository di sicurezza e non agli aggiornamenti normali. È voluto ed è quasi sempre la scelta giusta per i sistemi in produzione. Se imposti Automatic-Reboot "true", imposta assolutamente anche un orario, altrimenti il server si riavvia quando fa comodo al timer.

Verifica: una prova a vuoto mostra quali pacchetti verrebbero presi in considerazione, senza installare nulla. Fai attenzione al singolare nel nome del comando:

unattended-upgrade --dry-run --debug
apt-config dump | grep -iE "unattended|periodic"

Nel log sotto /var/log/unattended-upgrades/, dopo la prima esecuzione, deve esserci qualcosa. Se resta vuoto, la configurazione non fa presa. In dettaglio: Configurare gli aggiornamenti di sicurezza automatici.

Passo 8: monitoraggio

Nei primi 30 minuti monitoraggio non significa costruire un Grafana. Significa che vieni avvisato quando il server si ferma o il disco si riempie.

apt install -y htop tmux curl
df -h /
free -m

Per iniziare bastano tre cose. Primo, un controllo di raggiungibilità esterno, che verifica da fuori e ti avvisa, perché un server andato in crash non manda più nessun allarme su se stesso. Secondo, un avviso sullo spazio disco, perché un filesystem pieno è la causa più frequente dei guasti che nessuno aveva visto arrivare. Terzo, uno sguardo al journal quando qualcosa è strano:

journalctl -p 3 -b --no-pager | tail -n 30
systemctl --failed

systemctl --failed dovrebbe restituire 0 loaded units listed. Ogni riga presente lì è un servizio che non parte e che conviene riparare adesso, non fra tre mesi. Altro qui: Configurare il monitoraggio del server.

Passo 9: il backup prima che ci sia qualcosa da perdere

Il momento migliore per il primo backup è quando i dati non ci sono ancora. Così provi la procedura senza pressione. Due cose vale la pena salvare subito, perché ricostruirle costa più tempo di tutto il resto: la configurazione sotto /etc e l'elenco dei pacchetti installati.

tar -czf /root/etc-backup-$(date +%F).tar.gz /etc
dpkg --get-selections > /root/pacchetti.txt
tar -tzf /root/etc-backup-$(date +%F).tar.gz | wc -l

Con questo non hai ancora un backup, ma solo una copia sullo stesso supporto. Un backup sta su un altro sistema, idealmente in un altro luogo. Uno strumento con cifratura e deduplica dei blocchi identici conviene dal primo giorno:

apt install -y borgbackup
borg --version

La verità scomoda: un backup da cui non è mai stato recuperato niente è soltanto un'ipotesi. Fissa un appuntamento per il primo ripristino e riporta indietro un singolo file. La strada per farlo la trovi in Strategia di backup per server root.

Se ti sei chiuso fuori

Succede, di solito al passo 3 o al passo 4. La via di ritorno è sempre la stessa: accedi tramite la console nell'area clienti, lì con nome utente e password invece che con la chiave. Poi, a seconda della causa:

  • Firewall troppo severo: ufw disable, correggi la regola, ufw enable.
  • Configurazione SSH rotta: rm /etc/ssh/sshd_config.d/10-kernelhost.conf, poi sshd -t e systemctl restart ssh.
  • Porta sbagliata dopo la modifica del socket: systemctl revert ssh.socket azzera l'overlay, poi servono systemctl daemon-reload e systemctl restart ssh.socket.
  • Bloccato da fail2ban: fail2ban-client set sshd unbanip 203.0.113.10. Perché non ti ricapiti, inserisci il tuo indirizzo fisso sotto ignoreip nel file jail.local.
  • La chiave viene rifiutata: quasi sempre sono i permessi. chmod 700 sulla directory, chmod 600 sul file, ed entrambi devono appartenere all'utente, non a root.

Il controllo finale

Che un comando non restituisca errori non significa che abbia avuto effetto. Questi sei controlli mostrano lo stato reale:

  1. sshd -T | grep -E "^(permitrootlogin|passwordauthentication|port) " mostra i valori in vigore, non quelli desiderati.
  2. ufw status verbose segnala Status: active con una regola per la tua porta SSH.
  3. timedatectl status segnala System clock synchronized: yes.
  4. systemctl --failed non elenca nulla.
  5. unattended-upgrade --dry-run --debug arriva in fondo senza messaggi di errore.
  6. Una nuova connessione SSH da una finestra appena aperta funziona, con la chiave e senza richiesta di password.

Solo quando il punto sei è a posto puoi chiudere la vecchia finestra di terminale.

Che cosa viene dopo

Una parola sulla scelta della distribuzione, perché decide i prossimi anni. Debian 12 da luglio 2026 è fuori dal supporto regolare e viene mantenuto fino a metà 2028 dal team LTS, con una selezione di pacchetti ridotta. Chi installa oggi da zero prende Debian 13 oppure Ubuntu 24.04 LTS. Ubuntu 22.04 LTS resta nel supporto standard fino al 2027, ma per un server destinato a girare per anni non è più la prima scelta. Debian 10 e Ubuntu 20.04 sono definitivamente chiusi rispettivamente da giugno 2024 e da maggio 2025 e non hanno posto su un server nuovo.

Contano nella pratica anche le differenze di versione nelle repository. Debian non fornisce per principio mysql-server, lì lo standard è MariaDB. Chi ha bisogno di una versione precisa di PHP, Node o Java fa meglio a controllare prima che cosa porta la distribuzione, invece di aggiungere repository di terze parti in un secondo momento. E se aggiungi repository di terze parti: apt-key è deprecato, le chiavi vanno in /etc/apt/keyrings/ e si referenziano nella definizione della fonte con signed-by.

Con questo hai un server aggiornato, che si mantiene aggiornato da solo, che fa entrare solo te e che segnala quando qualcosa non va. Tutto il resto, web server, database, certificati, si costruisce sopra questa base e non accanto.

Domande frequenti

In che ordine conviene configurare un nuovo server root?
Prima aggiorna il sistema, poi crea un utente con permessi sudo e con la chiave SSH depositata, quindi metti in sicurezza SSH e infine attiva il firewall. Solo dopo vengono fuso orario, hostname, aggiornamenti di sicurezza automatici, monitoraggio e backup. Conta soprattutto una cosa: utente e chiave devono funzionare prima che tu disattivi l'accesso con password, e la regola per SSH deve essere già nel firewall prima che tu lo accenda.
Perché la mia modifica in sshd_config non ha effetto?
Su Debian e Ubuntu la riga Include /etc/ssh/sshd_config.d/*.conf si trova in cima a sshd_config, e nella configurazione SSH vince il primo valore trovato. Un file come 50-cloud-init.conf batte quindi sia il file principale sia un tuo file con un numero più alto. Controlla con sshd -T quali valori sono davvero in vigore e rinomina il tuo file con un numero più basso, per esempio 10-kernelhost.conf.
Perché la porta SSH su Ubuntu 24.04 non cambia?
Da Ubuntu 22.10 SSH parte tramite socket activation. La porta arriva allora da ssh.socket e la riga Port nella configurazione di sshd viene ignorata. Ti serve un overlay systemd creato con systemctl edit ssh.socket, con una riga ListenStream vuota che cancella l'impostazione predefinita e subito dopo il valore che vuoi. Su Ubuntu 22.04 continua a bastare la riga Port.
Fail2ban non parte e segnala di non aver trovato nessun file di log per la jail sshd. Che fare?
Dalla versione 12 Debian non installa più rsyslog, quindi non esiste alcun /var/log/auth.log. Imposta in /etc/fail2ban/jail.local, sotto [DEFAULT], la voce backend = systemd e installa il pacchetto python3-systemd. Da quel momento fail2ban legge direttamente dal journal. Puoi verificarlo con fail2ban-client -t e poi con fail2ban-client status sshd.
Perché dopo il riavvio il mio hostname è di nuovo sparito?
Sulle immagini con cloud-init, che su Ubuntu sono la regola, l'hostname viene reimpostato a ogni avvio. Crea /etc/cloud/cloud.cfg.d/99_hostname.cfg con la riga preserve_hostname: true e la tua impostazione resta. Aggiungi inoltre la voce corrispondente in /etc/hosts, altrimenti sudo segnala a ogni richiamo di non riuscire a risolvere l'host.
Basta apt upgrade o serve apt full-upgrade?
Su un server appena consegnato full-upgrade è la scelta migliore, perché installa anche gli aggiornamenti per i quali dei pacchetti devono essere rimossi o sostituiti. Su un sistema in produzione già avviato dovresti prima controllare con apt list --upgradable e con una simulazione che cosa succederebbe, e solo dopo eseguire full-upgrade.

Server root Configurazione server Linux SSH Firewall Debian Ubuntu Sicurezza server Checklist