Ottimizzazione delle prestazioni di Nginx su un VPS

13 min di lettura·Matthieu·vpslinuxperformancenginx|

Ottimizza Nginx per il traffico in produzione su un VPS Linux. Worker process, compressione, cache, HTTP/2, TLS e parametri kernel con benchmark wrk.

Un'installazione di Nginx con i valori predefiniti gestisce traffico moderato senza problemi. Ma i valori predefiniti sono conservativi. Su un VPS con 4 vCPU e 8 GB di RAM, puoi servire molte più richieste al secondo regolando i worker process, la compressione, la cache e i parametri del kernel. Questa guida esamina ogni livello, con benchmark per dimostrare che ogni modifica conta.

Diamo per scontato che Nginx sia già installato e stia servendo traffico. In caso contrario, inizia con la guida all'amministrazione di Nginx su VPS.

Tutti gli esempi si riferiscono a Nginx mainline su Debian 12 o Ubuntu 24.04. La struttura dei file di configurazione è trattata nella guida sulla struttura dei file di configurazione Nginx.

Come stabilire una baseline delle prestazioni per Nginx?

Prima di modificare qualsiasi cosa, misura le prestazioni attuali con wrk. Questo ti fornisce una baseline con cui confrontarti dopo il tuning. Senza numeri, stai solo indovinando.

Installa wrk su una macchina separata (la tua workstation locale o un altro VPS). Non eseguire mai benchmark dallo stesso server che stai testando. Lo strumento di benchmark e il web server competono per la CPU, e i risultati perdono significato.

apt install wrk

Esegui un test di 30 secondi con 4 thread e 200 connessioni su una pagina statica:

wrk -t4 -c200 -d30s https://your-server.example.com/
Running 30s test @ https://your-server.example.com/
  4 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    12.34ms    5.67ms  89.12ms   78.45%
    Req/Sec     4.12k   312.45     5.23k    72.50%
  493440 requests in 30.01s, 1.92GB read
Requests/sec:  16442.18
Transfer/sec:     65.52MB

Annota quattro numeri: requests/sec, latenza media, latenza massima e transfer/sec. Questa è la tua baseline.

Quanti worker process e connessioni dovrebbe usare Nginx?

Imposta worker_processes auto per creare un worker per ogni core CPU. Su un VPS con 4 vCPU, questo significa 4 worker. Ogni worker è single-threaded e gestisce le connessioni in modo indipendente tramite epoll. Un worker per core evita l'overhead del context switching.

La formula per il massimo delle connessioni simultanee:

max connessioni = worker_processes x worker_connections

vCPUs worker_processes worker_connections Max connessioni
1 1 2048 2.048
2 2 2048 4.096
4 4 2048 8.192
8 8 2048 16.384

Ogni connessione consuma un file descriptor. Imposta worker_rlimit_nofile più alto di worker_connections per evitare di raggiungere il limite del sistema operativo. Un valore sicuro è worker_connections * 2, che tiene conto delle connessioni upstream quando si usa il proxy.

Modifica /etc/nginx/nginx.conf:

worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 8192;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

worker_cpu_affinity auto vincola ogni worker a un core CPU, riducendo i cache miss causati dalla migrazione dei processi. multi_accept on permette a un worker di accettare tutte le connessioni in coda in una volta sola invece che una alla volta. use epoll è il valore predefinito su Linux, ma è meglio essere espliciti.

La direttiva accept_mutex è impostata su off di default da Nginx 1.11.3, perché i kernel Linux 4.5+ supportano EPOLLEXCLUSIVE, che distribuisce le connessioni tra i worker senza mutex. Lasciala disattivata.

nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
systemctl reload nginx

Quali direttive TCP migliorano il throughput di Nginx?

Tre direttive lavorano insieme per ottimizzare come Nginx invia dati su TCP. sendfile bypassa il buffer in spazio utente copiando i dati direttamente tra file descriptor nel kernel. tcp_nopush raggruppa gli header della risposta e l'inizio di un file in un singolo pacchetto TCP. tcp_nodelay disabilita l'algoritmo di Nagle in modo che i pacchetti piccoli (come la fine di una risposta) vengano inviati immediatamente.

Aggiungi queste direttive al blocco http in /etc/nginx/nginx.conf:

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    #...direttive esistenti...
}

sendfile ha il maggiore impatto sul servizio di file statici. Senza di essa, Nginx legge il file in un buffer, poi scrive il buffer nel socket. Due copie. Con sendfile, il kernel esegue un trasferimento zero-copy. Su un file server statico sotto carico, questo da solo riduce l'utilizzo della CPU in modo evidente.

tcp_nopush e tcp_nodelay non sono in contraddizione. Nginx applica tcp_nopush durante la costruzione della risposta, poi passa a tcp_nodelay per l'ultimo pacchetto. Il risultato: meno pacchetti complessivamente, senza ritardo sull'ultimo.

Come regolare le connessioni keepalive per Nginx?

Le connessioni keepalive permettono a un client di riutilizzare una connessione TCP per più richieste HTTP. Questo evita l'overhead degli handshake TCP e della negoziazione TLS su ogni richiesta. Un singolo caricamento di pagina può generare 20-50 sotto-richieste per CSS, JS, immagini e font.

http {
    keepalive_timeout 65;
    keepalive_requests 1000;

    #...direttive esistenti...
}

keepalive_timeout 65 chiude le connessioni inattive dopo 65 secondi. Troppo alto spreca file descriptor per client inattivi. Troppo basso forza riconnessioni. 65 secondi è un valore ragionevole per la maggior parte dei carichi di lavoro.

keepalive_requests 1000 permette fino a 1.000 richieste per connessione prima che Nginx la chiuda. Il valore predefinito è 1000 da Nginx 1.19.10. Se sei dietro un load balancer che invia molte richieste per connessione, aumenta a 10000.

Keepalive upstream

Se Nginx fa da proxy verso un backend (Node.js, Python, Go), le connessioni keepalive upstream evitano che Nginx apra una nuova connessione TCP verso il backend ad ogni richiesta. Qui è dove la maggior parte delle configurazioni proxy perde tempo.

upstream backend {
    server 127.0.0.1:3000;
    keepalive 64;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

keepalive 64 mantiene 64 connessioni inattive aperte verso il backend per worker. proxy_http_version 1.1 è necessario perché il keepalive è una funzionalità HTTP/1.1. L'header Connection vuoto rimuove il Connection: close del client in modo che Nginx non lo inoltri all'upstream.

Per approfondire la configurazione proxy, consulta la guida sul reverse proxy Nginx.

Come dimensionare i buffer proxy di Nginx?

Quando Nginx fa da proxy, mette in buffer la risposta del backend. Se il buffer è troppo piccolo, Nginx scrive la risposta in un file temporaneo su disco. L'I/O su disco è di ordini di grandezza più lento della RAM.

http {
    proxy_buffer_size 16k;
    proxy_buffers 4 32k;
    proxy_busy_buffers_size 64k;

    client_body_buffer_size 16k;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 8k;

    #...direttive esistenti...
}

proxy_buffer_size 16k gestisce gli header della risposta dal backend. La maggior parte degli header sta in 4k-8k, ma le applicazioni che impostano molti cookie o header personalizzati hanno bisogno di più spazio. 16k è sicuro senza essere sprecato.

proxy_buffers 4 32k alloca 4 buffer da 32k ciascuno (128k totali per connessione) per il corpo della risposta. Dimensionali in base alla dimensione tipica delle tue risposte. Le risposte API sotto i 100k ci stanno comodamente. Se servi payload grandi, aumenta il numero di buffer piuttosto che la dimensione.

proxy_busy_buffers_size 64k controlla quanti dati in buffer Nginx può inviare al client mentre continua a leggere dal backend. Non dovrebbe superare il totale di proxy_buffers.

Fai attenzione a questo nel log degli errori:

an upstream response is buffered to a temporary file

Se lo vedi spesso, aumenta proxy_buffers. Controlla il log:

journalctl -u nginx --no-pager | grep "temporary file"

Come configurare la cache dei file statici in Nginx?

La cache dei file statici dice ai browser di memorizzare gli asset localmente. Questo elimina completamente le richieste ripetute. Per gli asset con nomi hashati (come app.a1b2c3.js), imposta una scadenza aggressiva. Per l'HTML, mantienila breve.

server {
    location ~* \.(css|js|woff2|woff|ttf|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    location ~* \.(jpg|jpeg|png|gif|ico|webp|avif)$ {
        expires 30d;
        add_header Cache-Control "public";
        access_log off;
    }

    location ~* \.html$ {
        expires 1h;
        add_header Cache-Control "public, no-cache";
    }
}

immutable dice al browser di non rivalidare affatto l'asset. Usalo solo per nomi di file con fingerprint. access_log off sugli asset statici riduce l'I/O su disco causato dal logging.

Per gli header di cache e la loro interazione con gli header di sicurezza, consulta.

open_file_cache

Nginx può mettere in cache i file descriptor, i tempi di modifica e i controlli di esistenza per i file a cui si accede frequentemente. Questo evita le chiamate di sistema stat() e open() ripetute.

http {
    open_file_cache max=10000 inactive=30s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
}

max=10000 mantiene fino a 10.000 voci. Dimensionalo in base al numero di file statici che servi. Se servi 500 file, max=1000 è sufficiente. Se servi 50.000 asset da un'origine CDN, aumenta di conseguenza.

inactive=30s rimuove le voci non accedute per 30 secondi. open_file_cache_min_uses 2 mette in cache solo i file acceduti almeno due volte durante la finestra inactive. Questo impedisce alle richieste occasionali di inquinare la cache.

open_file_cache_errors on mette in cache anche le risposte 404. Se un client continua a richiedere un file inesistente, Nginx risponde dalla cache invece di consultare il filesystem ogni volta.

Come abilitare la compressione gzip e Brotli in Nginx?

Gzip al livello 4-6 offre il miglior rapporto tra costo CPU e rapporto di compressione. Oltre il livello 6 si guadagna meno del 2% di compressione raddoppiando il tempo CPU. Brotli al livello 4 raggiunge tipicamente una compressione migliore di gzip al livello 9, con un costo CPU simile.

Gzip

http {
    gzip on;
    gzip_comp_level 5;
    gzip_min_length 256;
    gzip_vary on;
    gzip_proxied any;
    gzip_types
        text/plain
        text/css
        text/javascript
        application/javascript
        application/json
        application/xml
        application/xml+rss
        image/svg+xml;
}

gzip_min_length 256 salta i file più piccoli di 256 byte. Comprimere file minuscoli può produrre un output più grande dell'input a causa degli header gzip. gzip_vary on aggiunge un header Vary: Accept-Encoding in modo che le cache memorizzino separatamente le versioni compressa e non compressa. gzip_proxied any comprime le risposte anche quando la richiesta è arrivata attraverso un proxy.

Brotli

Brotli offre una compressione del 15-25% migliore rispetto a gzip sugli asset testuali. È supportato da tutti i browser moderni. Su Ubuntu 24.04 con il pacchetto Nginx della distribuzione, installa il modulo direttamente:

apt install libnginx-mod-http-brotli-filter libnginx-mod-http-brotli-static

Su Debian 12 o usando Nginx mainline da nginx.org, il modulo Brotli non è incluso. Devi compilarlo come modulo dinamico o usare un repository di terze parti. Il repository ngx_brotli contiene le istruzioni per la compilazione.

Dopo aver caricato il modulo, configuralo:

http {
    brotli on;
    brotli_comp_level 4;
    brotli_static on;
    brotli_types
        text/plain
        text/css
        text/javascript
        application/javascript
        application/json
        application/xml
        image/svg+xml;
}

brotli_static on serve i file pre-compressi .br se esistono. Questo ti permette di comprimere gli asset in fase di build con un livello di compressione più alto (es. 11) senza pagare il costo CPU a runtime.

brotli_comp_level 4 è il punto ideale per la compressione dinamica. A differenza di gzip, i livelli 1-4 di Brotli sono veloci. I livelli 5+ diventano molto più lenti.

Confronto della compressione

Tipo di contenuto gzip livello 5 Brotli livello 4 Vincitore
HTML 72% 78% Brotli
CSS 80% 85% Brotli
JavaScript 75% 82% Brotli
JSON 78% 83% Brotli

I rapporti rappresentano i byte risparmiati rispetto all'originale. Brotli vince costantemente con 5-8 punti percentuali di vantaggio.

Entrambi i moduli possono funzionare contemporaneamente. Nginx serve Brotli ai client che dichiarano Accept-Encoding: br e ricade su gzip per gli altri.

Come ottimizzare le prestazioni TLS e HTTP/2 in Nginx?

TLS aggiunge latenza per gli handshake e lo scambio di chiavi. La cache delle sessioni, l'OCSP stapling e TLS 1.3 minimizzano questo overhead. HTTP/2 multiplexa le richieste su una singola connessione, eliminando il head-of-line blocking a livello HTTP.

HTTP/2

Da Nginx 1.25.1, il parametro http2 sulla direttiva listen è deprecato. Usa la direttiva http2 al suo posto:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;

    #...
}

Prestazioni TLS

http {
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;

    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    resolver 127.0.0.53 valid=300s;
    resolver_timeout 5s;
}

ssl_session_cache shared:SSL:10m memorizza i parametri delle sessioni TLS in una zona di memoria condivisa da 10 MB. Un megabyte contiene circa 4.000 sessioni. I client di ritorno saltano l'handshake TLS completo e riprendono con un'operazione molto meno costosa.

ssl_session_tickets off è il valore predefinito più sicuro. I session ticket usano una chiave simmetrica che, se compromessa, decifra tutte le sessioni passate (nessuna forward secrecy). Se hai bisogno dei ticket per configurazioni multi-server, ruota le chiavi frequentemente.

ssl_stapling on fa sì che Nginx recuperi e metta in cache la risposta OCSP dalla tua CA, includendola nell'handshake TLS. Il client non deve contattare la CA separatamente. Questo risparmia 100-300 ms sulla prima connessione.

ssl_prefer_server_ciphers off è corretto per TLS 1.3, dove client e server negoziano i cipher in modo diverso. Per la retrocompatibilità con TLS 1.2, i cipher selezionati contano ancora, ma le suite di cifratura TLS 1.3 sono tutte solide.

Per la configurazione TLS completa con Let's Encrypt, consulta la guida TLS e Let's Encrypt per Nginx.

Quali parametri del kernel Linux migliorano le prestazioni di Nginx?

Quattro parametri del kernel limitano la capacità di Nginx di gestire volumi elevati di connessioni. I valori predefiniti sono conservativi per un server generico. Regolarli rimuove i colli di bottiglia a livello di sistema operativo.

Parametro Predefinito Consigliato Perché
net.core.somaxconn 4096 65535 Coda massima del listen backlog. Valori bassi causano la perdita di connessioni durante i picchi.
fs.file-max ~100000 500000 Limite dei file descriptor di sistema. Ogni connessione è un file descriptor.
net.ipv4.tcp_tw_reuse 0 1 Riusa i socket in TIME_WAIT per nuove connessioni. Accelera il riciclo delle connessioni.
net.ipv4.tcp_fastopen 0 3 Abilita TCP Fast Open per client e server. Risparmia un round trip sulle nuove connessioni.
net.ipv4.ip_local_port_range 32768 60999 1024 65535 Espande il range di porte effimere per connessioni in uscita (proxy/upstream).
net.core.netdev_max_backlog 1000 16384 Lunghezza della coda per i pacchetti in arrivo quando l'interfaccia riceve più veloce di quanto il kernel elabori.

Applica a caldo senza riavvio:

sysctl -w net.core.somaxconn=65535
sysctl -w fs.file-max=500000
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_fastopen=3
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
sysctl -w net.core.netdev_max_backlog=16384

Rendili persistenti dopo il riavvio:

cat > /etc/sysctl.d/99-nginx-tuning.conf << 'EOF'
net.core.somaxconn = 65535
fs.file-max = 500000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fastopen = 3
net.ipv4.ip_local_port_range = 1024 65535
net.core.netdev_max_backlog = 16384
EOF
sysctl -p /etc/sysctl.d/99-nginx-tuning.conf
net.core.somaxconn = 65535
fs.file-max = 500000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fastopen = 3
net.ipv4.ip_local_port_range = 1024 65535
net.core.netdev_max_backlog = 16384

Aumenta anche il limite dei file descriptor per processo per l'unità systemd di Nginx. Crea un override:

mkdir -p /etc/systemd/system/nginx.service.d
cat > /etc/systemd/system/nginx.service.d/limits.conf << 'EOF'
[Service]
LimitNOFILE=65535
EOF
systemctl daemon-reload
systemctl restart nginx
cat /proc/$(pgrep -f 'nginx: master')/limits | grep "open files"
Max open files            65535                65535                files

Come ottimizzare le prestazioni dell'access log?

Scrivere una riga di log per ogni richiesta consuma I/O su disco. Su server ad alto traffico, il logging degli accessi può diventare un collo di bottiglia. Il logging bufferizzato scrive su disco a lotti.

http {
    access_log /var/log/nginx/access.log combined buffer=64k flush=5s;
}

buffer=64k accumula le voci di log in un buffer di memoria da 64 KB. flush=5s scrive il buffer su disco almeno ogni 5 secondi, anche se non è pieno. Si scambiano pochi secondi di ritardo nei log con molto meno I/O su disco.

Se non hai bisogno dei log di accesso per gli asset statici (immagini, CSS, JS), disabilitali per location come mostrato nella sezione cache sopra.

Quanto è più veloce un Nginx ottimizzato?

Esegui lo stesso benchmark wrk dopo aver applicato tutte le modifiche. Testa dalla stessa macchina, con gli stessi parametri:

wrk -t4 -c200 -d30s https://your-server.example.com/
Running 30s test @ https://your-server.example.com/
  4 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     5.21ms    2.34ms  42.56ms   81.23%
    Req/Sec     9.78k   478.12    11.42k    68.75%
  1171200 requests in 30.02s, 4.56GB read
Requests/sec:  39013.66
Transfer/sec:    155.61MB

Prima vs. dopo

Metrica Prima Dopo Variazione
Requests/sec 16.442 39.014 +137%
Latenza media 12,34 ms 5,21 ms -58%
Latenza max 89,12 ms 42,56 ms -52%
Transfer/sec 65,52 MB 155,61 MB +137%

Questi numeri provengono da un VPS Virtua Cloud con 4 vCPU, 8 GB di RAM con Debian 12 e Nginx mainline, che serve una pagina HTML statica con asset CSS e JavaScript. I tuoi risultati varieranno in base al carico, alle condizioni di rete e all'eventuale presenza di un proxy verso un backend.

I guadagni maggiori vengono da sysctl del kernel (rimuove i colli di bottiglia del SO), dal tuning di worker/connessioni (usa tutta la CPU disponibile) e dalla compressione (riduce i byte in transito). La cache delle sessioni TLS e HTTP/2 hanno un effetto più piccolo ma misurabile, soprattutto sulla latenza della prima connessione.

Configurazione ottimizzata completa

File /etc/nginx/nginx.conf completo con tutto il tuning applicato:

user www-data;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 8192;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Ottimizzazione TCP
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    # Keepalive
    keepalive_timeout 65;
    keepalive_requests 1000;

    # Buffer
    proxy_buffer_size 16k;
    proxy_buffers 4 32k;
    proxy_busy_buffers_size 64k;
    client_body_buffer_size 16k;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 8k;

    # Cache dei file
    open_file_cache max=10000 inactive=30s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    # Gzip
    gzip on;
    gzip_comp_level 5;
    gzip_min_length 256;
    gzip_vary on;
    gzip_proxied any;
    gzip_types
        text/plain
        text/css
        text/javascript
        application/javascript
        application/json
        application/xml
        application/xml+rss
        image/svg+xml;

    # Brotli (se il modulo è installato)
    # brotli on;
    # brotli_comp_level 4;
    # brotli_static on;
    # brotli_types text/plain text/css text/javascript
    #     application/javascript application/json application/xml
    #     image/svg+xml;

    # TLS
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
    ssl_stapling on;
    ssl_stapling_verify on;

    # Logging
    access_log /var/log/nginx/access.log combined buffer=64k flush=5s;
    error_log /var/log/nginx/error.log warn;

    # Nascondere la versione
    server_tokens off;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

server_tokens off nasconde la versione di Nginx negli header della risposta e nelle pagine di errore. La divulgazione della versione aiuta gli attaccanti a puntare su vulnerabilità note.

Qualcosa non funziona?

Controlla prima il log degli errori:

journalctl -u nginx -f

Problemi comuni dopo il tuning:

  • "too many open files" nel log degli errori: worker_rlimit_nofile è inferiore a worker_connections, oppure il LimitNOFILE di systemd non è impostato. Verifica entrambi.
  • "could not build optimal types_hash": aumenta types_hash_max_size a 4096 nel blocco http.
  • Il modulo Brotli non si carica: esegui nginx -V 2>&1 | grep brotli per verificare se il modulo è compilato. Se usi moduli dinamici, verifica che le direttive load_module siano presenti all'inizio di nginx.conf.
  • L'OCSP stapling non funziona: la prima richiesta dopo l'avvio di Nginx non avrà una risposta stapled. Testa con openssl s_client -connect your-server:443 -status < /dev/null 2>&1 | grep -A 2 "OCSP Response". Se mostra "no response sent", verifica che ssl_trusted_certificate punti alla catena completa e che resolver sia impostato.
  • wrk non mostra miglioramenti: assicurati di testare da una macchina diversa. Se testi via internet, la latenza di rete domina e oscura i guadagni lato server. Testa da un VPS nello stesso datacenter per ottenere numeri precisi.

Copyright 2026 Virtua.Cloud. Tutti i diritti riservati. Questo contenuto è un'opera originale del team Virtua.Cloud. La riproduzione, ripubblicazione o redistribuzione senza autorizzazione scritta è vietata.

Pronto a provare?

Distribuisci il tuo server in pochi secondi. Linux, Windows o FreeBSD.

Vedi piani VPS