1,201 26/03/2026 07/04/2026 7 min

Diagnosi probabile

In produzione, i problemi più comuni con Nginx non dipendono da un solo parametro ma da una combinazione di limiti troppo bassi, cache assente o mal configurata, upstream lenti e timeout incoerenti tra proxy, PHP-FPM o applicazione. Il sintomo tipico è una pagina che rallenta sotto carico, compare un 504 in modo sporadico oppure il server regge bene in orari normali ma crolla quando entrano più utenti, bot o picchi da newsletter.

Un caso molto frequente è questo: il frontend statico risponde velocemente, ma le richieste dinamiche saturano i worker dell’upstream. Se la cache non distingue bene tra contenuti pubblici e privati, si finisce con cache miss continui o, peggio, con contenuti errati serviti agli utenti. Per questo una configurazione “da produzione” deve partire da un’analisi del traffico reale: quali URL sono cacheabili, quali header arrivano dall’applicazione, quanto dura davvero una risposta media e quanto margine serve per assorbire i picchi.

Verifiche immediate

Prima di modificare la configurazione, conviene verificare tre punti essenziali: stato dei log, tempo di risposta dell’upstream e presenza di header utili o problematici. Se il problema è già visibile nei log errori, la diagnosi diventa molto più rapida.

Controlla gli errori recenti di Nginx e cerca timeout, upstream chiusi o cache bypassate:

sudo tail -n 80 /var/log/nginx/error.log

Esito atteso: vedi eventuali righe con upstream timed out, connect() failed o cache miss ripetuti. Se i messaggi sono assenti, il collo di bottiglia può stare nell’applicazione o nel database.

Misura la risposta con header inclusi, così puoi valutare cache, cookie e TLS:

curl -I https://www.esempio.it/

Esito atteso: compaiono header come Cache-Control, Strict-Transport-Security, X-Content-Type-Options e, se usi proxy cache, eventuali header diagnostici come X-Cache o simili.

Se hai un upstream PHP-FPM o un backend applicativo, testa anche il tempo di risposta diretto verso il servizio interno. Un backend lento rende inutile qualsiasi ottimizzazione del solo Nginx.

Soluzione consigliata passo-passo

L’obiettivo è impostare Nginx in modo prudente: limiti ragionevoli, cache selettiva, header di sicurezza, TLS moderno e timeout coerenti con il comportamento reale dell’applicazione. L’esempio seguente è adatto a un sito WordPress, WooCommerce o a un PHP custom con pagine pubbliche cacheabili e area autenticata da escludere.

1. Salva la configurazione attuale

Prima di intervenire, fai una copia del file del virtual host o del blocco principale. Su molte installazioni il percorso è in /etc/nginx/sites-available/ oppure in /etc/nginx/conf.d/.

sudo cp /etc/nginx/sites-available/sito.conf /etc/nginx/sites-available/sito.conf.bak

Esito atteso: hai un rollback immediato se un test successivo fallisce.

2. Imposta limiti e protezioni di base

Per ridurre abusi e richieste anomale, usa limiti per connessioni e rate limit. Non blocca il traffico legittimo, ma aiuta contro bot aggressivi e picchi improvvisi.

http {
    limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
}

Nel server block, applica i limiti alle aree pubbliche più esposte:

location / {
    limit_req zone=req_limit burst=30 nodelay;
    limit_conn conn_limit 20;
}

Esito atteso: il sito continua a funzionare per gli utenti normali, mentre gli accessi troppo aggressivi ricevono rallentamenti o rifiuti controllati. Se hai un e-commerce, valuta soglie più alte sulle pagine di checkout e login, perché sono più sensibili.

3. Configura la cache in modo selettivo

Una cache utile non deve essere “sempre attiva”, ma coerente con il tipo di contenuto. La scelta prudente è cacheare solo GET e HEAD per contenuti pubblici, escludendo cookie di sessione, utenti autenticati, carrelli e aree amministrative.

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=SITE_CACHE:100m inactive=60m max_size=5g use_temp_path=off;

map $http_cookie $skip_cache {
    default 0;
    ~*wordpress_logged_in 1;
    ~*comment_author 1;
    ~*woocommerce_items_in_cart 1;
    ~*woocommerce_cart_hash 1;
}

map $request_method $cache_method {
    default 0;
    GET 1;
    HEAD 1;
}

Nel blocco del sito:

location / {
    proxy_cache SITE_CACHE;
    proxy_cache_bypass $skip_cache $cache_method = 0;
    proxy_no_cache $skip_cache $cache_method = 0;
    proxy_cache_valid 200 301 302 10m;
    proxy_cache_valid 404 1m;
    add_header X-Cache $upstream_cache_status always;
    proxy_pass http://backend;
}

Esito atteso: le pagine pubbliche mostrano MISS al primo accesso e HIT ai successivi. Le aree con cookie sensibili restano escluse dalla cache.

4. Allinea timeout e buffering con il carico reale

Molti 504 nascono da timeout troppo stretti, non da un guasto vero. La regola pratica è: timeout abbastanza ampi da coprire picchi temporanei, ma non così alti da tenere occupati i worker inutilmente. Se l’applicazione impiega fino a 8-10 secondi in casi eccezionali, impostare 1 o 2 secondi significa generare errori artificiali.

location / {
    proxy_connect_timeout 5s;
    proxy_send_timeout 30s;
    proxy_read_timeout 30s;
    send_timeout 30s;

    proxy_buffering on;
    proxy_buffers 16 16k;
    proxy_buffer_size 32k;
    proxy_busy_buffers_size 64k;
}

Esito atteso: Nginx assorbe meglio le risposte un po’ lente del backend e riduce gli errori intermittenti. Se il backend è davvero lento in modo costante, questi valori non bastano: serve intervenire sull’applicazione o sul database.

5. Gestisci correttamente gli header di sicurezza

In produzione, il proxy deve aggiungere header utili senza rompere quelli dell’applicazione. Tra i più importanti ci sono HSTS, protezione MIME, frame-ancestors e policy sui referrer. Non forzare header troppo aggressivi se il sito usa servizi esterni non verificati, ma non lasciare il minimo indispensabile scoperto.

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

Se l’applicazione già invia header propri, controlla che non ci siano conflitti. Per esempio, due istruzioni diverse per CSP possono creare comportamento imprevedibile. In quel caso, conviene unificare la policy in un solo punto.

6. TLS: scegli impostazioni moderne ma compatibili

Una configurazione da produzione non si limita a “attivare HTTPS”. Serve verificare protocollo, cipher e rinnovo del certificato. Se usi certificati di Let’s Encrypt, controlla che il rinnovo automatico sia attivo e che il virtual host punti ai file corretti.

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:20m;
ssl_session_timeout 10m;
ssl_stapling on;
ssl_stapling_verify on;

Esito atteso: il sito risponde con un handshake stabile, le versioni vecchie e deboli restano escluse e il certificato viene presentato correttamente anche sotto carico. Se il certificato intermedio non è completo, alcuni client falliranno in modo intermittente.

7. Proteggi l’upstream

Se Nginx parla con PHP-FPM o con un servizio applicativo interno, limita l’esposizione del backend. L’upstream deve ascoltare solo su localhost o su rete privata, non su interfacce pubbliche.

upstream backend {
    server 127.0.0.1:8080 max_fails=3 fail_timeout=10s;
    keepalive 32;
}

Esito atteso: il backend non è accessibile dall’esterno e Nginx può riutilizzare connessioni con maggiore efficienza. Se il backend è un pool PHP-FPM, verifica anche i parametri di process manager, perché troppi pochi processi causano code e timeout anche con Nginx perfettamente configurato.

8. Evita cache e header su contenuti privati

Un errore comune è cacheare per sbaglio risposte personalizzate. Se il sito usa area account, carrello, preferenze utente o contenuti dietro login, escludi sempre questi percorsi dalla cache e aggiungi header no-store quando necessario.

location ~* ^/(wp-admin/|cart/|checkout/|my-account/) {
    proxy_no_cache 1;
    proxy_cache_bypass 1;
    add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
    proxy_pass http://backend;
}

Esito atteso: nessun contenuto sensibile finisce nella cache condivisa e gli utenti autenticati vedono sempre dati aggiornati.

Controlli finali / rollback

Quando la configurazione è pronta, esegui sempre un test sintattico prima del reload:

sudo nginx -t

Esito atteso: syntax is ok e test is successful. Se fallisce, non ricaricare il servizio.

Poi applica il reload senza interrompere le connessioni attive:

sudo systemctl reload nginx

Verifica subito tre cose: una pagina pubblica deve rispondere, l’header X-Cache deve mostrare il comportamento previsto e i log non devono riportare nuovi timeout. Se i 504 persistono, il rollback più sicuro è ripristinare il file salvato e ricaricare Nginx:

sudo cp /etc/nginx/sites-available/sito.conf.bak /etc/nginx/sites-available/sito.conf
sudo nginx -t && sudo systemctl reload nginx

Se il problema resta anche dopo il rollback, la causa è quasi certamente fuori da Nginx: backend saturo, query lente, DNS interno lento, disco in sofferenza o limiti del sistema operativo. In quel caso conviene misurare CPU, RAM, I/O e tempi medi dell’applicazione prima di ritoccare ancora il proxy.

Checklist finale: 1. cache solo sui contenuti pubblici; 2. timeout coerenti con il backend; 3. header di sicurezza attivi; 4. upstream non esposto all’esterno; 5. test e rollback pronti prima del reload. Assunzione: il sito usa Nginx come reverse proxy davanti a un backend PHP o applicativo simile.