In produzione la domanda non è “Nginx o Apache?”. La domanda vera è dove vuoi far lavorare il server sotto pressione.
Se hai API lente, checkout sensibili o contenuti cacheabili, il punto cambia subito. Nginx regge bene da reverse proxy e da cache layer. Apache resta forte quando ti serve compatibilità applicativa, .htaccess o moduli storici già integrati nel flusso del sito.
Qui confronto due approcci concreti. Primo: Nginx davanti ad Apache o a un upstream applicativo. Secondo: Apache diretto, con tuning mirato e fallback più semplice. Useremo sintomi reali: 502, timeout, header incoerenti, cache che non salva il backend e TLS che aggiunge latenza inutile.
Scenario reale: traffico misto con home cacheabile, area login non cacheabile, API lente tra 400 ms e 3 s, picchi di richieste simultanee e piccole ondate di retry dai client.
Prerequisiti
Prima di cambiare config, servono pochi dati, ma precisi.
- Versione di Nginx o Apache installata.
- Schema del backend: PHP-FPM, Node.js, Python, Java o altro.
- Elenco degli header attesi: Cache-Control, Vary, X-Frame-Options, HSTS.
- Timeout osservati nei log: connect, read, send, proxy.
- Un test di carico minimo con curl, ab o wrk.
Warning: non partire dai timeout “a sentimento”. Se alzi tutto senza misurare, nascondi solo il collo di bottiglia.
Note: se il sito usa CDN o WAF, verifica prima che non stia riscrivendo header e cache policy a monte.
Step 1: capire dove conviene Nginx e dove conviene Apache
Il primo passo è scegliere l’architettura. Nginx è spesso migliore quando vuoi assorbire connessioni, terminare TLS e servire cache o statici. Apache è più comodo quando il sito dipende da regole per-directory, mod_security già tarato sul modulo, o da una migrazione con compatibilità stretta.
Se l’applicazione ha molte richieste brevi e ripetitive, Nginx davanti al backend riduce il lavoro ripetuto. Se il sito è vecchio, pieno di rewrite locali e plugin che leggono .htaccess, Apache diretto evita sorprese.
Un confronto rapido:
- Nginx: migliore su picchi, cache, TLS, upstream multipli, limiti per IP o per route.
- Apache: più flessibile sul comportamento legacy, più lineare per ambienti che dipendono da .htaccess.
- Nginx + Apache: utile quando vuoi separare edge e applicazione senza riscrivere subito il sito.
Quando il carico reale include tante connessioni in attesa, Nginx tende a consumare meno memoria per connessione. Quando invece l’applicazione fa affidamento su regole locali distribuite tra cartelle e virtual host, Apache resta più semplice da mantenere.
Se devi scegliere in fretta, usa questa regola pratica:
- contenuti statici, cache, API e TLS al bordo: Nginx;
- sito legacy, hosting condiviso, regole sparse e compatibilità: Apache;
- migrazione graduale o separazione dei ruoli: Nginx davanti ad Apache.
Step 2: configurare gli header senza creare incoerenze
Gli header sono spesso il primo punto in cui emergono differenze tra i due stack. Il problema non è solo “mandare l’header giusto”, ma non mandarlo due volte, non perderlo lungo il proxy e non invalidare la cache per errore.
Gli header più importanti nel confronto sono:
- Cache-Control: definisce cosa può essere memorizzato e per quanto.
- Vary: evita che la cache mescoli risposte diverse.
- ETag e Last-Modified: aiutano la validazione condizionata.
- HSTS: forzatura HTTPS, da usare solo se il dominio è pronto.
- X-Frame-Options e Content-Security-Policy: protezione contro embedding e injection.
Con Nginx, puoi centralizzare gli header a livello di server o location. Con Apache, spesso li gestisci via mod_headers e regole nel vhost. Il rischio in Apache è la frammentazione: una regola in directory, una nel virtual host, una nel backend. Il rischio in Nginx è l’eccesso di override che cancella header utili del backend.
Esempio Nginx per una risposta cacheabile ma sicura:
location /static/ {
expires 1h;
add_header Cache-Control "public, max-age=3600, immutable" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
}
Esempio Apache con mod_headers:
<Directory "/var/www/html/static">
Header always set Cache-Control "public, max-age=3600, immutable"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
</Directory>
Se usi un backend applicativo, controlla che non stia sovrascrivendo gli header del proxy. Per esempio, un’app che restituisce Cache-Control: no-store su una home che vorresti cacheare rende inutile tutto il lavoro del reverse proxy.
Per verificare il risultato reale:
curl -I https://example.com/
curl -I https://example.com/static/app.css
Se gli header cambiano tra richiesta diretta al backend e richiesta attraverso il proxy, devi decidere chi è autorevole. In generale, lascia al backend la logica applicativa e al proxy le policy di bordo, ma evita conflitti.
Step 3: cache reale, non cache teorica
La cache funziona solo se le regole sono coerenti con il traffico reale. In un sito misto, non puoi cacheare tutto. Devi distinguere tra pagine pubbliche, contenuti personalizzati e risposte che dipendono da cookie o sessione.
Con Nginx puoi usare cache proxy o FastCGI cache. Con Apache puoi ottenere cache efficaci, ma il setup è spesso meno immediato e meno usato come edge cache puro. Se l’obiettivo è assorbire il carico di picchi brevi, Nginx è normalmente più pratico.
Un esempio Nginx con proxy cache:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=sitecache:100m inactive=60m max_size=2g;
server {
listen 443 ssl http2;
server_name example.com;
location / {
proxy_pass http://app_backend;
proxy_cache sitecache;
proxy_cache_valid 200 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
add_header X-Cache-Status $upstream_cache_status always;
}
}
La variabile $upstream_cache_status ti dice se hai MISS, HIT o BYPASS. È utile perché molte volte il problema non è la cache in sé, ma il fatto che non venga mai usata.
Per esempio, se un cookie di sessione invalida sempre la cache, puoi separare le route pubbliche da quelle private:
location / {
if ($http_cookie ~* "sessionid=") {
set $skip_cache 1;
}
proxy_no_cache $skip_cache;
proxy_cache_bypass $skip_cache;
proxy_pass http://app_backend;
}
In Apache, la cache può essere utile, ma spesso il vantaggio maggiore arriva da una buona combinazione di mod_expires, mod_headers e un backend ben ottimizzato. Se il tuo problema è il traffico di picco, il reverse proxy davanti resta più efficace come primo strato.
Attenzione a non cacheare risposte personalizzate. Se una home mostra nome utente, carrello o prezzi per segmento, devi variare per cookie o disabilitare la cache su quelle route.
Step 4: TLS e latenza inutile
Il TLS non è il nemico, ma può diventarlo se viene configurato male o ripetuto dove non serve. Nginx è spesso scelto per terminare TLS davanti all’applicazione, lasciando il backend in HTTP interno o in mTLS solo se davvero necessario.
Se Apache è esposto direttamente, la configurazione TLS è assolutamente valida, ma devi curare bene cipher, session reuse e HTTP/2. Se hai un’architettura a due livelli, il TLS esterno di Nginx può ridurre il lavoro sul backend e semplificare il deployment.
Esempio Nginx con impostazioni TLS essenziali:
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.crt;
ssl_certificate_key /etc/ssl/private/example.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_prefer_server_ciphers off;
location / {
proxy_pass http://app_backend;
}
}
Con Apache, un vhost TLS moderno può essere impostato così:
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/example.crt
SSLCertificateKeyFile /etc/ssl/private/example.key
Protocols h2 http/1.1
SSLProtocol -all +TLSv1.2 +TLSv1.3
</VirtualHost>
Se la latenza aumenta dopo aver attivato TLS, controlla prima la catena certificati, la negoziazione HTTP/2 e la session reuse. In molti casi il problema non è il protocollo, ma il fatto che il server fa troppo lavoro per ogni handshake.
Step 5: timeout upstream senza 502
Il 502 arriva spesso quando il proxy non ottiene una risposta valida dal backend nei tempi previsti. Non significa solo “backend rotto”: può essere un timeout troppo basso, un upstream saturo, una connessione chiusa male o una risposta troppo lenta rispetto al carico.
Con Nginx, i parametri più importanti sono proxy_connect_timeout, proxy_send_timeout e proxy_read_timeout. Se usi FastCGI, diventano fastcgi_connect_timeout, fastcgi_send_timeout e fastcgi_read_timeout.
Configurazione Nginx tipica per un backend che a volte impiega fino a 3 secondi:
location /api/ {
proxy_pass http://app_backend;
proxy_connect_timeout 2s;
proxy_send_timeout 10s;
proxy_read_timeout 15s;
proxy_next_upstream error timeout http_502 http_503 http_504;
}
Se il backend è lento ma sano, aumentare solo il read timeout può bastare. Se invece il problema è la connessione iniziale, serve guardare connect_timeout e la salute del backend.
In Apache, se lo usi come reverse proxy, i parametri equivalenti dipendono dai moduli proxy e proxy_fcgi. Esempio:
ProxyPass /api/ http://app_backend/api/ connectiontimeout=2 timeout=15 retry=0
ProxyPassReverse /api/ http://app_backend/api/
Per PHP-FPM dietro Apache:
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php/php-fpm.sock|fcgi://localhost/"
</FilesMatch>
Il punto chiave è questo: il timeout deve essere più alto del tempo normale di elaborazione, ma più basso del tempo che trasforma il cliente in attesa inutile. Se il 95° percentile della tua API è 2,2 secondi, un timeout a 2 secondi è troppo aggressivo. Se il 99° percentile è 12 secondi e il business non lo accetta, il problema non è il proxy: è l’API.
Per distinguere un timeout di rete da uno applicativo, guarda i log e i codici:
502: gateway non valido o upstream che chiude male.504: upstream troppo lento o non risponde in tempo.499: il client ha chiuso prima della risposta, spesso per latenza eccessiva.
Una buona pratica è registrare il tempo upstream. In Nginx puoi usare un log format con $request_time e $upstream_response_time:
log_format main '$remote_addr - $request "$status" '
'rt=$request_time urt=$upstream_response_time '
'ua="$http_user_agent"';
Questo ti permette di capire se il ritardo è nel proxy, nel backend o nella rete.
Step 6: evitare che i retry amplifichino il problema
Sotto carico, i retry dei client possono peggiorare tutto. Se il backend rallenta e il proxy risponde con errori o timeout, alcuni client riprovano subito. Il risultato è una coda più lunga e più 502.
Nginx può aiutare con proxy_next_upstream, ma va usato con criterio. Se hai più upstream, puoi fare failover controllato. Se hai un solo backend e un problema di saturazione, il retry automatico rischia di moltiplicare le richieste.
Esempio con bilanciamento semplice:
upstream app_backend {
server 10.0.0.11:8080 max_fails=3 fail_timeout=10s;
server 10.0.0.12:8080 max_fails=3 fail_timeout=10s;
keepalive 32;
}
Con Apache, il concetto è simile ma spesso meno immediato da rifinire. Se vuoi alta disponibilità e failover pulito, Nginx tende a offrire più controllo front-end.
Se il problema è il sovraccarico, non salvare tutto con timeout più lunghi. Meglio ridurre il numero di richieste simultanee al backend, cacheare ciò che puoi e mettere in coda solo ciò che ha davvero senso.
Step 7: test pratici con curl, ab e wrk
Per capire se la configurazione regge, fai test brevi ma mirati. Non serve un benchmark da laboratorio se il tuo problema è un 502 durante il picco di mezzogiorno.
Controllo header:
curl -I https://example.com/
curl -I https://example.com/api/status
Test rapido con ab:
ab -n 500 -c 25 https://example.com/
Test più realistico con wrk:
wrk -t4 -c100 -d60s https://example.com/api/status
Se vedi che la home va in cache ma l’area login no, è corretto. Se invece anche le pagine pubbliche restano MISS, la cache non è configurata bene o viene bypassata da cookie, header o regole errate.
Controlla anche i log di errore. In Nginx, messaggi come upstream timed out o connect() failed sono il segnale più utile. In Apache, cerca errori del proxy, del worker e del backend applicativo.
Step 8: configurazione Nginx davanti ad Apache
Questo è il caso più interessante quando vuoi migliorare subito senza rifare il sito.
Nginx termina TLS, gestisce statici e cache, e inoltra le richieste dinamiche ad Apache. Apache continua a gestire compatibilità, rewrite e moduli legacy. È una soluzione molto usata quando vuoi stabilità e un miglior controllo del traffico.
Esempio base:
upstream apache_backend {
server 127.0.0.1:8080;
keepalive 16;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.crt;
ssl_certificate_key /etc/ssl/private/example.key;
location /static/ {
root /var/www/html;
expires 1h;
add_header Cache-Control "public, max-age=3600, immutable" always;
}
location / {
proxy_pass http://apache_backend;
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 https;
proxy_connect_timeout 2s;
proxy_read_timeout 15s;
}
}
Su Apache, il backend deve ascoltare su una porta interna e non essere esposto pubblicamente se Nginx è il vero edge. Questo riduce superficie d’attacco e semplifica il controllo del traffico.
Vantaggi pratici di questo schema:
- TLS e HTTP/2 gestiti in un solo punto.
- Statici serviti senza toccare Apache.
- Cache e rate limit più facili da applicare.
- Apache resta dedicato alla logica applicativa.
Step 9: Apache diretto, quando ha senso
Apache diretto ha senso se vuoi semplicità operativa, un solo punto di configurazione e compatibilità con un ecosistema già consolidato. In alcuni ambienti è anche la scelta più prudente, soprattutto se il team conosce bene il suo modo di lavorare.
Per esempio, un sito con molte regole .htaccess può essere più facile da gestire in Apache diretto che dietro un proxy con regole duplicate. In questo caso, il tuning va fatto su worker, keepalive, moduli attivi e log.
Un esempio di base con MPM moderno e keepalive ragionevole:
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 2
Timeout 60
Se il sito serve contenuti statici e dinamici insieme, Apache può andare bene, ma il caching front-end resta in genere meno efficiente rispetto a Nginx come reverse proxy. Se il traffico cresce, il salto architetturale verso Nginx davanti è spesso il passo successivo naturale.
Step 10: diagnosi rapida dei problemi più comuni
Quando qualcosa non va, non partire dal riavvio. Parti dal sintomo.
- 502: controlla che il backend sia in ascolto, che il socket o la porta siano corretti e che il timeout non sia troppo stretto.
- 504: verifica tempi applicativi, query lente, code e saturazione del backend.
- Header duplicati: cerca override in proxy, backend e regole locali.
- Cache che non funziona: controlla cookie,
Vary,Cache-Controle bypass involontari. - Latenza TLS: verifica handshake, session reuse e certificati.
Comandi utili:
nginx -t
apachectl configtest
journalctl -u nginx -n 50
journalctl -u apache2 -n 50
tail -f /var/log/nginx/error.log
tail -f /var/log/apache2/error.log
Se hai accesso al backend, misura anche lì. A volte il proxy è innocente e il problema è una query SQL lenta, un lock o un pool esaurito.
Conclusione: scegliere in base al collo di bottiglia
Se il tuo collo di bottiglia è il numero di connessioni, la terminazione TLS, la cache e la gestione dei picchi, Nginx è spesso la scelta migliore come front layer. Se invece il tuo sito dipende da compatibilità legacy, configurazioni distribuite e comportamento storico, Apache diretto resta valido e più lineare.
Nel traffico reale, la soluzione più robusta è spesso ibrida: Nginx davanti per assorbire, cacheare e proteggere; Apache dietro per servire l’applicazione dove serve compatibilità. In questo modo puoi correggere gli header, evitare timeout troppo aggressivi e ridurre i 502 senza cambiare subito il codice.
La regola finale è semplice: non ottimizzare il server che ti piace di più, ottimizza il punto in cui il sistema sta davvero cedendo.
Se vuoi, come passo successivo puoi creare una matrice decisionale per il tuo ambiente: tipo di sito, backend, volume di traffico, uso di .htaccess, necessità di cache e tolleranza ai timeout. Da lì la scelta tra Nginx 1.24 e Apache 2.4 diventa molto più concreta.
Commenti (0)
Nessun commento ancora.
Segnala contenuto
Elimina commento
Eliminare definitivamente questo commento?
L'azione non si può annullare.