Installare e configurare nginx su Debian e Ubuntu

Pubblicato il 18 min di lettura

Dal pacchetto apt al primo blocco server con PHP-FPM e HTTPS: quale versione di nginx porta ogni distribuzione, che cosa cambia con il repository nginx.org e come liberarsi degli errori più tipici.

Installare nginx richiede trenta secondi. Il resto della giornata se ne va perché il blocco server non viene applicato, perché PHP restituisce un 502 oppure perché Apache occupa già la porta 80. Questo articolo affronta esattamente questi punti, tenendo separate Debian 13, Debian 12, Ubuntu 24.04 e Ubuntu 22.04, dato che i quattro sistemi si comportano in modo diverso su più aspetti.

Quale nginx: pacchetto della distribuzione o repository ufficiale

La prima decisione arriva prima ancora del primo comando. Ogni distribuzione fornisce una versione congelata, che riceve soltanto patch di sicurezza. A luglio 2026 la situazione è questa:

Sistemanginx dal pacchetto della distribuzione
Debian 13 (trixie)1.26.3
Debian 12 (bookworm)1.22.1
Ubuntu 24.04 LTS (noble)1.24.0
Ubuntu 22.04 LTS (jammy)1.18.0

Su nginx.org, a luglio 2026, sono disponibili i rami 1.30.4 (stable) e 1.31.3 (mainline). La distanza rispetto a Ubuntu 22.04 corrisponde quindi a circa sei anni di sviluppo di nuove funzioni.

Scegli il pacchetto della distribuzione se gestisci siti web classici o configurazioni reverse proxy e vuoi che sia unattended-upgrades a fare il lavoro. Scegli il repository nginx.org se ti servono HTTP/3 e QUIC (del tutto assenti in Ubuntu 22.04, e nemmeno in Debian 12 sono presenti), se vuoi moduli dinamici già pronti come nginx-module-brotli o il modulo ACME, oppure se vuoi far girare esattamente la stessa versione su più distribuzioni.

Quello che molte guide tacciono: i due pacchetti non sono lo stesso programma in versioni diverse, sono compilati in modo diverso e impacchettati in modo diverso. È il motivo più frequente per cui una guida copiata non funziona.

CaratteristicaPacchetto Debian/UbuntuPacchetto nginx.org
Utente del processowww-datanginx
Docroot predefinita/var/www/html/usr/share/nginx/html
sites-available e sites-enabledpresentinon esistono
/etc/nginx/snippets/presentenon esiste
Profili ufw (Nginx Full ecc.)presentinon esistono
Moduli dinamicilibnginx-mod-*nginx-module-*

Installazione dal pacchetto della distribuzione

Identica su tutti e quattro i sistemi:

apt update
apt install -y nginx
apt install -y curl

Il metapacchetto nginx è sufficiente. nginx-full e nginx-extras esistono ancora in Debian 12 e 13, ma si limitano ad aggiungere pacchetti di moduli. I singoli moduli li installi in modo mirato, per esempio con apt install libnginx-mod-http-headers-more-filter.

curl compare subito di proposito. Non è una dipendenza di nginx e manca su una Debian o una Ubuntu appena installata, anche dopo che nginx è stato installato correttamente. Senza questo passaggio il primo comando di verifica qui sotto si interrompe con curl: command not found, e la stessa trappola colpisce più avanti il test dell'header Host e il test PHP.

Adesso la parte che la maggior parte delle guide salta: la prova che il servizio gira davvero. Un apt install senza messaggi di errore non dimostra proprio nulla.

nginx -v
systemctl is-enabled nginx
systemctl is-active nginx
curl -I http://127.0.0.1/

Ti aspetti enabled, active e un HTTP/1.1 200 OK con un header Server che nomina nginx (Debian 13 risponde con Server: nginx, Ubuntu 24.04 con Server: nginx/1.24.0). Solo allora il server web è davvero in piedi. Se vuoi sapere con quali opzioni è stato compilato il pacchetto, usa nginx -V (V maiuscola): lì vedi anche se --with-http_v3_module è incluso.

Se ufw è attivo, manca ancora la regola firewall. Prima però uno sguardo al pacchetto stesso: su Ubuntu Server ufw è installato di serie e semplicemente inattivo, su Debian non è presente affatto. Lì il primo richiamo risponderebbe altrimenti con ufw: command not found. Installalo quindi insieme al resto, il pacchetto esiste su tutti e quattro i sistemi:

apt install -y ufw
ufw app list
ufw allow 'Nginx Full'

I profili arrivano insieme al pacchetto nginx-common della distribuzione: Nginx Full, Nginx HTTP e Nginx HTTPS, su Debian 13 in aggiunta Nginx QUIC. Se invece installi dal repository nginx.org, in ufw app list non compaiono nemmeno (vedi la tabella sopra).

Aggiungere il repository ufficiale di nginx

nginx.org supporta bookworm, trixie, jammy e noble. apt-key è deprecato: la chiave va in un keyring dedicato e viene referenziata tramite signed-by.

apt install -y curl gnupg2 ca-certificates lsb-release debian-archive-keyring

Su Ubuntu l'ultimo pacchetto si chiama ubuntu-keyring invece di debian-archive-keyring. Poi:

mkdir -p /root/.gnupg && chmod 700 /root/.gnupg
curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor | tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
gpg --dry-run --quiet --no-keyring --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg

Il mkdir nella prima riga non è zavorra. Su un server appena installato /root/.gnupg non esiste ancora, e il gpg 2.4.7 di Debian 13 non crea da solo la directory con esattamente questa combinazione di parametri. Il comando di verifica si interrompe allora con gpg: Fatal: /root/.gnupg: directory does not exist!, e lo fa a metà dell'output, cioè prima che compaiano i fingerprint. Per vederli devi creare prima la directory, in alternativa basta lanciare una volta gpg --list-keys.

Anche l'ultimo comando non è un ornamento, è la controprova vera e propria. Mostra tre chiavi, il che è voluto e non è un motivo per fermarsi: la chiave di firma attuale 8540 A6F1 8833 A80E 9C16 53A4 2FD2 1310 B49F 6B46 (signing-key-2@nginx.com), più 573B FD6B 3D8F BC64 1079 A6AB ABF5 BD82 7BD9 BF62 e 9E9B E90E ACBC DE69 FE9B 204C BCDC D8A3 8D88 A2B3. Se lì compaiono altri fingerprint, il download ha consegnato qualcosa di diverso da quanto atteso, e a questo punto ti fermi.

Adesso la fonte dei pacchetti. Fai attenzione al segmento di percorso dopo /packages/, perché nginx.org mantiene due alberi di directory separati per Debian e Ubuntu. Sotto /packages/debian/dists/ non esistono né noblejammy, i pacchetti Ubuntu stanno esclusivamente sotto /packages/ubuntu/. Questa versione imposta il segmento da sola e gira quindi invariata su entrambe le famiglie:

OS=$(. /etc/os-release; echo $ID)
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/$OS $(lsb_release -cs) nginx" | tee /etc/apt/sources.list.d/nginx.list

$ID su Debian vale debian e su Ubuntu ubuntu, così il percorso si adatta in automatico alla distribuzione. Se invece scrivi fisso packages/debian perché stai seguendo una guida per Debian su una Ubuntu, echo scrive la riga senza protestare e l'errore arriva solo al successivo apt update:

Err: http://nginx.org/packages/debian noble Release
E: The repository 'http://nginx.org/packages/debian noble Release' does not have a Release file.

Su Ubuntu 22.04 al suo posto compare jammy invece di noble, ma la causa è la stessa. Per il ramo mainline anteponi mainline/ al nome della distribuzione. Perché apt preferisca davvero il pacchetto nginx.org a quello della distribuzione serve un pinning, altrimenti a seconda del numero di versione vince quello sbagliato:

printf 'Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n' | tee /etc/apt/preferences.d/99nginx
apt update
apt-cache policy nginx
apt install -y nginx

apt-cache policy nginx è il controllo prima dell'installazione: come Candidate deve comparire la versione di nginx.org (per esempio 1.30.4-1~noble) e come priorità il valore 900 preso dal file di pin. Se lì resta la versione della distribuzione, il pinning non ha effetto e installi subito il pacchetto sbagliato.

Passare da un'installazione esistente della distribuzione

Se il pacchetto della distribuzione è già in funzione, l'aggiornamento fallisce. Il messaggio suona più o meno così:

dpkg: error processing archive /var/cache/apt/archives/nginx_1.30.4-1~bookworm_amd64.deb (--unpack):
 trying to overwrite '/etc/nginx/mime.types', which is also in package nginx-common 1.22.1-9+deb12u9

Il pacchetto nginx.org non conosce nginx-common, per questo i file entrano in conflitto. La strada pulita: prima il backup della configurazione, poi la rimozione del pacchetto della distribuzione, poi la nuova installazione.

tar czf /root/nginx-config-backup.tar.gz /etc/nginx
systemctl stop nginx
apt purge -y nginx nginx-common
apt install -y nginx

Che tar stampi tar: Removing leading '/' from member names è normale e non è un errore. Dopo di che /etc/nginx/sites-available non c'è più e i tuoi vecchi vhost esistono soltanto nel tarball. Copiali in /etc/nginx/conf.d/ e dai loro l'estensione .conf, altrimenti non vengono caricati. Fai attenzione a due cose: include snippets/fastcgi-php.conf; non esiste nel pacchetto nginx.org, e l'utente del processo adesso si chiama nginx, il che riguarda i permessi sui file e i socket PHP-FPM.

Capire davvero sites-available e sites-enabled

Il modello a due directory è un'invenzione tutta di Debian, nginx di suo non lo conosce. In /etc/nginx/sites-available/ stanno tutti i file di configurazione, in /etc/nginx/sites-enabled/ stanno i symlink verso quelli attivi. Viene caricato solo ciò che compare in nginx.conf dentro un include. Nel dubbio verificalo tu stesso:

grep include /etc/nginx/nginx.conf

Nel pacchetto Debian e Ubuntu lì si trovano due righe, include /etc/nginx/conf.d/*.conf; e include /etc/nginx/sites-enabled/*;. Nel pacchetto nginx.org c'è solo la prima. Da qui nasce esattamente il classico caso: qualcuno segue una guida per Ubuntu su un pacchetto nginx.org, crea /etc/nginx/sites-available/mio-sito, imposta il symlink, ottiene da nginx -t un pulito syntax is ok, e comunque non succede niente. Non compare alcun messaggio di errore, perché quella directory semplicemente non interessa a nessuno.

La controprova che dice sempre la verità è nginx -T con la T maiuscola. Stampa la configurazione completa e già risolta, cioè esattamente quello che nginx vede davvero:

nginx -T | grep -n "server_name\|listen\|root"

Se il tuo server_name non compare lì, il file non viene letto. Punto. Continuare a fare debug sulla configurazione stessa è a quel punto tempo perso.

Il secondo errore frequente è un symlink che punta nel vuoto, per esempio dopo un errore di battitura o perché il file di destinazione è stato rinominato:

nginx: [emerg] open() "/etc/nginx/sites-enabled/mio-sito" failed (2: No such file or directory) in /etc/nginx/nginx.conf:62

I symlink rotti li trovi con find /etc/nginx/sites-enabled/ -xtype l. Un sito, del resto, non si disattiva mai cancellandolo in sites-available, ma rimuovendo il symlink con unlink /etc/nginx/sites-enabled/mio-sito.

Il primo blocco server tutto tuo

Creiamo un sito statico. Prima la directory e un file di test:

mkdir -p /var/www/esempio/html
echo '<h1>Pagina di test KernelHost</h1>' > /var/www/esempio/html/index.html
chown -R www-data:www-data /var/www/esempio

Con il pacchetto nginx.org il proprietario è nginx:nginx. Adesso la configurazione, e davvero come passaggio a sé: il file deve esistere prima che il symlink punti su di esso. Crealo con l'editor oppure scrivilo direttamente con un heredoc; con il pacchetto nginx.org finisce invece in /etc/nginx/conf.d/esempio.conf:

cat > /etc/nginx/sites-available/esempio <<'EOF'
server {
    listen 80;
    listen [::]:80;

    server_name esempio.it www.esempio.it;
    root /var/www/esempio/html;
    index index.html;

    access_log /var/log/nginx/esempio.access.log;
    error_log  /var/log/nginx/esempio.error.log;

    location / {
        try_files $uri $uri/ =404;
    }
}
EOF

Gli apici singoli intorno a 'EOF' sono importanti, altrimenti la shell sostituisce $uri con il nulla e il blocco è già rotto al momento del salvataggio. Solo a quel punto si attiva, si verifica e si ricarica:

ln -s /etc/nginx/sites-available/esempio /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

L'ordine non è casuale. ln crea il symlink anche quando il file di destinazione non esiste affatto, e lo fa senza dire una parola. La cosa viene a galla soltanto durante il test:

nginx: [emerg] open() "/etc/nginx/sites-enabled/esempio" failed (2: No such file or directory) in /etc/nginx/nginx.conf:61

Il numero di riga cambia a seconda del sistema (61 su Debian 13, 60 su Debian 12 e sulle due Ubuntu), il messaggio è identico. In questo stato un reload verrebbe rifiutato, quindi la configurazione in esecuzione resta intatta. Proprio questo caso lo individua anche find /etc/nginx/sites-enabled/ -xtype l.

A questo punto ricorrono con regolarità altri tre errori.

Default server doppio. Se copi default_server da una guida mentre la pagina predefinita di Debian è ancora attiva, ottieni:

nginx: [emerg] a duplicate default server for 0.0.0.0:80 in /etc/nginx/sites-enabled/esempio:2

O togli la parola chiave, oppure disattivi la pagina predefinita con unlink /etc/nginx/sites-enabled/default.

Nome server doppio. Se lo stesso nome compare in due blocchi, vince senza commenti quello caricato per primo e arriva soltanto un avviso:

nginx: [warn] conflicting server name "esempio.it" on 0.0.0.0:80, ignored

Nome server troppo lungo. Con domini lunghi o con molti sottodomini:

nginx: [emerg] could not build server_names_hash, you should increase server_names_hash_bucket_size: 32

Il rimedio è server_names_hash_bucket_size 64; nel blocco http di nginx.conf.

La prova di funzionamento passa dall'header Host, senza bisogno del DNS:

curl -H 'Host: esempio.it' -sS http://127.0.0.1/

Se arriva la pagina predefinita di Debian al posto della tua pagina di test, il tuo blocco non viene applicato e finisci sul default server. Se arriva la tua pagina, è tutto a posto.

Collegare PHP-FPM

Qui si nasconde la trappola più fastidiosa di tutta la guida, e fa perdere un'ora a una lunga fila di persone. Il metapacchetto php dipende, tramite php8.x, dall'alternativa libapache2-mod-php8.x | php8.x-fpm | php8.x-cgi. apt sceglie sempre la prima, quindi apt install php installa immancabilmente anche Apache, e Apache occupa poi la porta 80. Installa perciò in modo mirato:

apt install -y php-fpm php-mysql php-xml php-curl php-mbstring php-zip

Quale versione di PHP ottieni e come si chiama il socket dipende dalla distribuzione:

SistemaPHPSocketServizio
Debian 138.4/run/php/php8.4-fpm.sockphp8.4-fpm
Debian 128.2/run/php/php8.2-fpm.sockphp8.2-fpm
Ubuntu 24.048.3/run/php/php8.3-fpm.sockphp8.3-fpm
Ubuntu 22.048.1/run/php/php8.1-fpm.sockphp8.1-fpm

Non indovinare il percorso, leggilo:

ls -l /run/php/

Il blocco PHP dentro il server (esempio con Debian 12, adatta il percorso):

    index index.php index.html;

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }

Su Debian e Ubuntu puoi sostituire le prime righe con include snippets/fastcgi-php.conf;, nel pacchetto nginx.org questo snippet non esiste e lì ti serve la versione scritta per esteso. Il try_files $uri =404; non è un vezzo stilistico, impedisce che vengano eseguiti file caricati dagli utenti con un .php aggiunto nel percorso.

Manca però ancora un passaggio, e viene saltato quasi sempre. La chiamata di test qui sotto passa dal vhost predefinito fornito con il pacchetto, e in /etc/nginx/sites-available/default la sezione location ~ \.php$ è di serie completamente commentata. Finché resta così, nginx consegna i file .php senza toccarli: ricevi un 200 OK con Content-Type: application/octet-stream e il codice sorgente PHP in chiaro. Non è un difetto estetico. In un file reale in quel punto ci sono le credenziali del database o le chiavi API, e le legge chiunque conosca la URL.

Quindi prima di tutto attiva il blocco. In /etc/nginx/sites-available/default togli i caratteri di commento davanti, oppure inseriscilo per esteso:

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.4-fpm.sock;
    }

Il nome del socket dipende dalla distribuzione, la tabella sopra lo indica: php8.4 su Debian 13, php8.3 su Ubuntu 24.04, php8.2 su Debian 12, php8.1 su Ubuntu 22.04. Poi verifica e ricarica:

nginx -t
systemctl reload nginx

Solo adesso la verifica ha valore e dimostra che PHP gira davvero tramite FPM e che il file non viene semplicemente offerto in download:

printf '<?php echo "PHP ", PHP_VERSION, " via ", php_sapi_name(), "\n";' > /var/www/html/kh-check.php
curl -s http://127.0.0.1/kh-check.php
rm /var/www/html/kh-check.php

È corretto un output come PHP 8.4.23 via fpm-fcgi, su Ubuntu 24.04 di conseguenza PHP 8.3.6 via fpm-fcgi. Se invece torna indietro il codice sorgente, location ~ \.php$ nel vhost attivo non sta ancora facendo effetto: resta quindi commentato oppure punta al socket sbagliato. Non dimenticare di cancellare il file, e non usare phpinfo() su un server raggiungibile dall'esterno.

Leggere correttamente un 502 Bad Gateway

Un 502 non è una diagnosi, la diagnosi sta in /var/log/nginx/error.log. Ci sono tre righe tipiche:

connect() to unix:/run/php/php8.2-fpm.sock failed (2: No such file or directory) while connecting to upstream

Percorso sbagliato oppure FPM non in esecuzione. Verifica con systemctl status php8.2-fpm e ls /run/php/. Una causa frequente è un aggiornamento di distribuzione: dopo il salto da Debian 12 a 13 la vecchia configurazione punta ancora a php8.2-fpm.sock, ma quella installata è la 8.4.

connect() to unix:/run/php/php8.2-fpm.sock failed (13: Permission denied) while connecting to upstream

Questo colpisce quasi esclusivamente le installazioni dal repository nginx.org. Il pool FPM appartiene a www-data e ha modo 0660, ma lì nginx gira come utente nginx. In /etc/php/8.2/fpm/pool.d/www.conf imposta il valore listen.group = nginx e riavvia FPM.

FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream

SCRIPT_FILENAME punta nel vuoto. Di solito root si trova nel blocco location invece che nel blocco server, oppure manca del tutto.

HTTPS con Let's Encrypt

Le versioni di certbot delle distribuzioni sono molto distanti tra loro: Debian 13 fornisce la 4.0.0, Ubuntu 24.04 la 2.9.0, Debian 12 la 2.1.0 e Ubuntu 22.04 soltanto la 1.21.0. Tutte parlano ACMEv2, ma per funzioni più recenti come i certificati a vita breve la 1.21 è troppo vecchia. Su Ubuntu 22.04 conviene il giro tramite snap.

apt install -y certbot python3-certbot-nginx
certbot --version

Prima dell'emissione devono essere soddisfatte tre condizioni, altrimenti la validazione fallisce senza un messaggio utile: il record A (ed eventualmente AAAA) punta al server, la porta 80 è raggiungibile dall'esterno ed esiste un blocco server con il server_name giusto. L'ultimo punto è decisivo, perché il plugin nginx trova il blocco tramite server_name, non tramite il nome del file. Poi:

certbot --nginx -d esempio.it -d www.esempio.it

A quel punto certbot scrive nel tuo file di configurazione: un secondo blocco server con listen 443 ssl, i percorsi verso fullchain.pem e privkey.pem, un include /etc/letsencrypt/options-ssl-nginx.conf e un redirect dalla porta 80. Il fatto che il file venga modificato sorprende regolarmente. Chi poi lo sovrascrive a mano perde l'HTTPS e al reload successivo ottiene:

nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/esempio.it/fullchain.pem": BIO_new_file() failed

Il rinnovo automatico passa da un timer systemd, non da un cronjob. Controlla entrambe le cose:

systemctl list-timers | grep certbot
certbot renew --dry-run

L'esecuzione a vuoto è l'unica prova concreta che il rinnovo funzionerà tra novanta giorni. Se usi il repository nginx.org, dalla versione 1.29 esiste inoltre con nginx-module-acme una variante del tutto priva di certbot, in cui è nginx stesso a richiedere e rinnovare i certificati.

nginx -t, reload e restart

La regola è semplice e viene comunque violata di continuo: mai un reload senza aver prima eseguito nginx -t. In caso di errore di sintassi nginx si rifiuta di adottare la configurazione rotta, ma con un restart il vecchio processo è già terminato e il sito è offline.

nginx -t

Ci si aspetta esattamente questo:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

reload avvia nuovi worker con la nuova configurazione e lascia che i vecchi portino a termine le connessioni ancora aperte. Non va persa nemmeno una richiesta. restart ti serve solo quando cambia qualcosa nel processo stesso, per esempio dopo un aggiornamento di pacchetto, con un user modificato oppure quando carichi direttive load_module.

Se l'avvio fallisce, l'output di systemd è quasi inutile:

Job for nginx.service failed because the control process exited with error code.
See "systemctl status nginx.service" and "journalctl -xeu nginx.service" for details.

Il testo utile sta un livello più in basso:

journalctl -xeu nginx.service --no-pager -n 30

Risolvere il conflitto di porta con Apache sulla porta 80

L'errore di nginx più noto in assoluto:

nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)

Tieni presente che nginx -t non individua questo errore, perché il test non apre alcuna porta. A fallire è soltanto l'avvio. Scopri prima di tutto chi occupa la porta, invece di tirare a indovinare:

apt install -y iproute2
ss -tlnp | grep ':80'

L'output indica il processo in chiaro, tipicamente users:(("apache2",pid=612,fd=4)). In nove casi su dieci Apache è finito nel sistema tramite apt install php oppure insieme a un pannello. Esistono tre vie d'uscita pulite.

Primo, spegnere Apache. Se non ti serve, basta disattivarlo, così non torna dopo il prossimo riavvio:

systemctl disable --now apache2
systemctl start nginx

Secondo, rimuovere Apache. Attenzione: se ci sta appeso libapache2-mod-php, apt purge apache2 può portarsi via anche PHP. Controlla l'elenco che apt mostra prima dell'esecuzione e installa poi php-fpm.

Terzo, far girare entrambi in parallelo. Ha senso quando i vhost Apache esistenti con .htaccess devono continuare a funzionare e nginx lavora davanti come reverse proxy. Per farlo imposti in /etc/apache2/ports.conf un Listen 127.0.0.1:8080, cambi in ogni vhost <VirtualHost *:80> in <VirtualHost *:8080> e lasci che sia nginx a inoltrare:

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

Perché Apache nei suoi log non veda più soltanto 127.0.0.1, attiva lì il modulo remoteip. Senza questo passaggio le regole Fail2ban sui log di Apache non valgono nulla, dato che ogni richiesta sembra arrivare da localhost.

Due varianti dello stesso errore vengono spesso trascurate. Se in due dei tuoi file compare due volte la stessa voce listen, nginx segnala allo stesso modo Address already in use, anche se Apache non c'entra nulla. E con bind() to [::]:80 failed è interessato solo IPv6, di solito perché anche un secondo blocco imposta listen [::]:80 senza ipv6only=on.

Il collaudo: otto verifiche al posto delle sensazioni

Prima di considerare finita un'installazione, passa in rassegna questi punti. Ognuno di essi ha già evitato almeno una volta un guasto silenzioso.

  1. nginx -t segnala test is successful.
  2. systemctl is-enabled nginx restituisce enabled, quindi il servizio torna su dopo un reboot.
  3. ss -tlnp | grep nginx mostra la porta 80 e, se configurata, la porta 443.
  4. nginx -T | grep server_name elenca ogni dominio che deve funzionare.
  5. curl -I http://127.0.0.1/ restituisce un 200 oppure un redirect voluto.
  6. Il file di test PHP stampa fpm-fcgi ed è stato poi cancellato.
  7. certbot renew --dry-run viene eseguito senza errori.
  8. /var/log/nginx/error.log non contiene nuove voci dopo un accesso di prova.

Su un server appena configurato manca poi ancora il firewall, vedi a riguardo la nostra guida alla configurazione di ufw. Debian 10 e Ubuntu 20.04 sono senza supporto rispettivamente da giugno 2024 e da maggio 2025 e non ricevono più aggiornamenti di sicurezza per nginx: lì un upgrade non è una questione di comodità, è semplicemente in ritardo.

Che cosa aggiunge KernelHost

Sui server root KVM e sui server dedicati di KernelHost installi Debian 13, Debian 12, Ubuntu 24.04 oppure Ubuntu 22.04 direttamente dall'area clienti e hai poi accesso root completo, quindi i passaggi descritti sopra funzionano senza modifiche. I sistemi si trovano nel datacenter maincubes di Francoforte sul Meno, certificato TÜV secondo TIER3+, collegato tramite la nostra rete. La protezione DDoS interviene davanti al server e non soltanto dentro nginx: 3,2 Tbps di filtraggio Arbor in tempo reale direttamente in loco, nelle tariffe Professional in aggiunta fino a 17 Tbps di capacità di filtraggio globale. Tutto funziona su base PrePaid, quindi senza durata minima, senza preavviso di disdetta e senza costi di attivazione.

Domande frequenti

Conviene installare nginx dal pacchetto della distribuzione o dal repository ufficiale nginx.org?
Per siti web classici e configurazioni reverse proxy il pacchetto della distribuzione basta e avanza: riceve aggiornamenti di sicurezza per tutta la durata del supporto senza che tu debba fare nulla. Il repository nginx.org conviene se ti servono HTTP/3 e QUIC (non inclusi in Debian 12 e Ubuntu 22.04), se vuoi usare moduli dinamici già pronti come Brotli o il modulo ACME, oppure se vuoi far girare esattamente la stessa versione su più distribuzioni. Tieni presente che il pacchetto nginx.org è costruito in modo diverso: gira come utente nginx invece che www-data e non conosce né sites-available né la directory snippets.
Perché il mio blocco server in sites-available viene ignorato del tutto, anche se nginx -t va a buon fine?
O manca il symlink in sites-enabled, oppure la tua nginx.conf non carica affatto quella directory. Il secondo caso è sempre vero con il pacchetto di nginx.org, dove esiste solo include /etc/nginx/conf.d/*.conf. nginx -t controlla soltanto i file effettivamente inclusi, per questo il test resta verde. La verità la mostra nginx -T con la T maiuscola: se il tuo server_name non compare in quell'output, il file non viene letto. Con il pacchetto nginx.org sposti la configurazione in /etc/nginx/conf.d/ e le dai l'estensione .conf.
nginx non parte e segnala bind() to 0.0.0.0:80 failed (98: Address already in use). Che cosa faccio?
Un altro processo occupa la porta 80, quasi sempre Apache. Trovalo con ss -tlnp | grep ':80', l'output indica il nome del processo e il PID. Molto spesso Apache è entrato nel sistema senza farsi notare tramite apt install php, perché il metapacchetto PHP risolve libapache2-mod-php come prima alternativa. A quel punto hai tre opzioni: spegnere Apache in modo definitivo con systemctl disable --now apache2, rimuoverlo con apt purge (verificando se PHP se ne va insieme), oppure spostarlo su 127.0.0.1:8080 in /etc/apache2/ports.conf e mettere nginx davanti come reverse proxy. Importante: nginx -t non individua questo errore, perché il test di configurazione non apre alcuna porta.
Quale versione di PHP e quale socket FPM mi servono sulla mia distribuzione?
Debian 13 fornisce PHP 8.4 con /run/php/php8.4-fpm.sock, Debian 12 PHP 8.2 con php8.2-fpm.sock, Ubuntu 24.04 PHP 8.3 con php8.3-fpm.sock e Ubuntu 22.04 PHP 8.1 con php8.1-fpm.sock. Non indovinare il percorso, leggilo con ls -l /run/php/. Installa in modo mirato php-fpm e non il metapacchetto php, altrimenti apt si porta dentro anche Apache. Dopo un aggiornamento di distribuzione devi adattare il percorso del socket nella tua configurazione nginx, altrimenti ottieni un 502 con la riga di log connect() to unix:/run/php/... failed (2: No such file or directory).
Come faccio a capire con certezza che nginx gira davvero come deve e non che è passato soltanto il comando?
Controlla cinque cose: nginx -t segnala test is successful, systemctl is-enabled nginx restituisce enabled (altrimenti dopo il prossimo reboot il server web non c'è più), ss -tlnp | grep nginx mostra le porte attese, nginx -T | grep server_name elenca ogni dominio che deve funzionare e curl -I http://127.0.0.1/ restituisce un 200 (curl non è una dipendenza di nginx e manca su un sistema appena installato, prendilo prima con apt install -y curl). Per PHP crei per un attimo un file con php_sapi_name(), l'output deve essere fpm-fcgi, poi lo cancelli di nuovo. Se al posto dell'output torna indietro il codice sorgente PHP, il blocco PHP nel vhost attivo è ancora commentato, così com'è di serie in /etc/nginx/sites-available/default. Per HTTPS certbot renew --dry-run è l'unica prova concreta che il rinnovo funzionerà tra novanta giorni.

nginx Debian Ubuntu Server web PHP-FPM HTTPS Let's Encrypt Amministrazione Linux