Nginx Performance Tuning op een VPS
Tune Nginx voor productieverkeer op een Linux VPS. Worker processes, compressie, caching, HTTP/2, TLS-optimalisatie, kernel sysctl en benchmarks met wrk.
Een standaard Nginx-installatie verwerkt gemiddeld verkeer zonder problemen. Maar de standaardwaarden zijn conservatief. Op een VPS met 4 vCPU's en 8 GB RAM kun je aanzienlijk meer requests per seconde afhandelen door worker processes, compressie, caching en kernel-parameters af te stemmen. Deze gids doorloopt elke laag, met benchmarks als bewijs dat elke wijziging ertoe doet.
We gaan ervan uit dat Nginx al geïnstalleerd is en verkeer afhandelt. Zo niet, begin dan met de handleiding voor Nginx-administratie op een VPS.
Alle voorbeelden zijn gericht op Nginx mainline op Debian 12 of Ubuntu 24.04. De configuratiebestandsstructuur wordt behandeld in de handleiding over de Nginx-configuratiebestandsstructuur.
Hoe stel je een performance baseline vast voor Nginx?
Voordat je iets wijzigt, meet je de huidige prestaties met wrk. Dit geeft je een baseline om na het tunen mee te vergelijken. Zonder cijfers ben je aan het gissen.
Installeer wrk op een aparte machine (je lokale werkstation of een andere VPS). Benchmark nooit vanaf dezelfde server die je test. De benchmarktool en de webserver concurreren om CPU, en de resultaten worden zinloos.
apt install wrk
Voer een 30-seconden test uit met 4 threads en 200 verbindingen tegen een statische pagina:
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
Noteer vier getallen: requests/sec, gemiddelde latency, maximale latency en transfer/sec. Dit is je baseline.
Hoeveel worker processes en verbindingen moet Nginx gebruiken?
Stel worker_processes auto in om één worker per CPU-core te starten. Op een VPS met 4 vCPU's betekent dit 4 workers. Elke worker is single-threaded en verwerkt verbindingen onafhankelijk via epoll. Eén worker per core voorkomt overhead door context switching.
De formule voor het maximale aantal gelijktijdige verbindingen:
max verbindingen = worker_processes x worker_connections
| vCPU's | worker_processes | worker_connections | Max verbindingen |
|---|---|---|---|
| 1 | 1 | 2048 | 2.048 |
| 2 | 2 | 2048 | 4.096 |
| 4 | 4 | 2048 | 8.192 |
| 8 | 8 | 2048 | 16.384 |
Elke verbinding verbruikt een file descriptor. Stel worker_rlimit_nofile hoger in dan worker_connections om de OS-limiet niet te bereiken. Een veilige waarde is worker_connections * 2, wat rekening houdt met upstream-verbindingen bij proxying.
Bewerk /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 bindt elke worker aan een CPU-core, waardoor cache misses door procesmigratie afnemen. multi_accept on laat een worker alle wachtende verbindingen tegelijk accepteren in plaats van één voor één. use epoll is standaard op Linux, maar het is goed om expliciet te zijn.
De accept_mutex directive staat standaard op off sinds Nginx 1.11.3 omdat Linux kernels 4.5+ EPOLLEXCLUSIVE ondersteunen, wat verbindingen over workers verdeelt zonder mutex. Laat het uitgeschakeld.
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
Welke TCP-directives verbeteren de Nginx-doorvoer?
Drie directives werken samen om te optimaliseren hoe Nginx data over TCP verstuurt. sendfile slaat de userspace-buffer over door data direct tussen file descriptors in de kernel te kopiëren. tcp_nopush bundelt response headers en het begin van een bestand in één TCP-pakket. tcp_nodelay schakelt Nagle's algoritme uit zodat kleine pakketten (zoals het einde van een response) direct worden verstuurd.
Voeg deze toe aan het http-blok in /etc/nginx/nginx.conf:
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
#...bestaande directives...
}
sendfile maakt het grootste verschil bij het serveren van statische bestanden. Zonder deze directive leest Nginx het bestand in een buffer en schrijft de buffer vervolgens naar de socket. Twee kopieën. Met sendfile doet de kernel een zero-copy transfer. Op een drukke statische bestandsserver vermindert dit alleen al het CPU-gebruik merkbaar.
tcp_nopush en tcp_nodelay spreken elkaar niet tegen. Nginx past tcp_nopush toe tijdens het opbouwen van de response en schakelt dan over naar tcp_nodelay voor het laatste pakket. Het resultaat: minder pakketten totaal, zonder vertraging bij het laatste.
Hoe stel je keepalive-verbindingen in voor Nginx?
Keepalive-verbindingen laten een client een TCP-verbinding hergebruiken voor meerdere HTTP-requests. Dit voorkomt de overhead van TCP-handshakes en TLS-onderhandeling bij elke request. Een enkele paginalaad kan 20-50 subrequests triggeren voor CSS, JS, afbeeldingen en lettertypen.
http {
keepalive_timeout 65;
keepalive_requests 1000;
#...bestaande directives...
}
keepalive_timeout 65 sluit inactieve verbindingen na 65 seconden. Te hoog verspilt file descriptors aan inactieve clients. Te laag dwingt herverbindingen af. 65 seconden is een redelijke standaard voor de meeste workloads.
keepalive_requests 1000 staat tot 1.000 requests per verbinding toe voordat Nginx deze sluit. De standaardwaarde is 1000 sinds Nginx 1.19.10. Als je achter een load balancer zit die veel requests per verbinding stuurt, verhoog dan naar 10000.
Upstream keepalive
Als Nginx proxyt naar een backend (Node.js, Python, Go), voorkomen upstream keepalive-verbindingen dat Nginx bij elke request een nieuwe TCP-verbinding naar de backend opent. Hier verspillen de meeste proxy-setups tijd.
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 houdt 64 inactieve verbindingen naar de backend open per worker. proxy_http_version 1.1 is vereist omdat keepalive een HTTP/1.1-functie is. De lege Connection header wist de Connection: close header van de client zodat Nginx deze niet doorstuurt naar de upstream.
Meer over proxy-configuratie vind je in de handleiding over Nginx reverse proxy.
Hoe dimensioneer je de proxy-buffers van Nginx?
Wanneer Nginx een request proxyt, buffert het de response van de backend. Als de buffer te klein is, schrijft Nginx de response naar een tijdelijk bestand op schijf. Schijf-I/O is ordes van grootte langzamer dan 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;
#...bestaande directives...
}
proxy_buffer_size 16k verwerkt de response headers van de backend. De meeste headers passen in 4k-8k, maar applicaties met veel cookies of aangepaste headers hebben meer nodig. 16k is veilig zonder verspillend te zijn.
proxy_buffers 4 32k wijst 4 buffers van 32k elk toe (128k totaal per verbinding) voor de response body. Dimensioneer deze op basis van je typische responsegrootte. API-responses onder 100k passen gemakkelijk. Als je grote payloads serveert, verhoog dan het aantal buffers in plaats van de grootte.
proxy_busy_buffers_size 64k bepaalt hoeveel gebufferde data Nginx naar de client kan sturen terwijl het nog van de backend leest. Dit mag het totaal van proxy_buffers niet overschrijden.
Let op dit in je error log:
an upstream response is buffered to a temporary file
Als je dit vaak ziet, verhoog proxy_buffers. Controleer het log:
journalctl -u nginx --no-pager | grep "temporary file"
Hoe configureer je caching van statische bestanden in Nginx?
Caching van statische bestanden vertelt browsers om assets lokaal op te slaan. Dit elimineert herhaalde requests volledig. Voor assets met gehashte bestandsnamen (zoals app.a1b2c3.js) stel je een agressieve vervaldatum in. Voor HTML hou je het kort.
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 vertelt de browser om het asset helemaal niet te hervalideren. Gebruik dit alleen voor bestandsnamen met fingerprint. access_log off op statische assets vermindert schijf-I/O door logging.
Voor cache headers en hun interactie met security headers, zie.
open_file_cache
Nginx kan file descriptors, wijzigingstijden en bestaanscontroles cachen voor veelgebruikte bestanden. Dit voorkomt herhaalde stat() en open() systeemaanroepen.
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 bewaart tot 10.000 entries. Dimensioneer dit op basis van het aantal statische bestanden dat je serveert. Als je 500 bestanden serveert, is max=1000 voldoende. Als je 50.000 assets serveert vanuit een CDN-origin, verhoog dan dienovereenkomstig.
inactive=30s verwijdert entries die 30 seconden niet zijn benaderd. open_file_cache_min_uses 2 cachet alleen bestanden die minstens twee keer zijn benaderd tijdens het inactive venster. Dit voorkomt dat eenmalige requests de cache vervuilen.
open_file_cache_errors on cachet ook 404-lookups. Als een client herhaaldelijk een niet-bestaand bestand opvraagt, antwoordt Nginx vanuit de cache in plaats van elke keer het bestandssysteem te raadplegen.
Hoe schakel je gzip- en Brotli-compressie in bij Nginx?
Gzip op niveau 4-6 biedt de beste balans tussen CPU-kosten en compressieverhouding. Boven niveau 6 win je minder dan 2% compressie terwijl de CPU-tijd verdubbelt. Brotli op niveau 4 bereikt doorgaans betere compressie dan gzip op niveau 9, bij vergelijkbare CPU-kosten.
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 slaat bestanden kleiner dan 256 bytes over. Het comprimeren van kleine bestanden kan output opleveren die groter is dan de input vanwege gzip-headers. gzip_vary on voegt een Vary: Accept-Encoding header toe zodat caches gecomprimeerde en ongecomprimeerde versies apart opslaan. gzip_proxied any comprimeert responses zelfs als het request via een proxy kwam.
Brotli
Brotli levert 15-25% betere compressie dan gzip op tekstuele assets. Het wordt ondersteund door alle moderne browsers. Op Ubuntu 24.04 met het Nginx-pakket van de distributie installeer je de module direct:
apt install libnginx-mod-http-brotli-filter libnginx-mod-http-brotli-static
Op Debian 12 of bij gebruik van Nginx mainline van nginx.org is de Brotli-module niet inbegrepen. Je moet het als dynamische module compileren of een repository van derden gebruiken. De ngx_brotli repository bevat de build-instructies.
Na het laden van de module configureer je het:
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 serveert voorgecomprimeerde .br-bestanden als deze bestaan. Dit laat je assets tijdens de build comprimeren met een hoger compressieniveau (bijv. 11) zonder de CPU-kosten tijdens runtime te betalen.
brotli_comp_level 4 is de sweet spot voor dynamische compressie. Anders dan bij gzip zijn Brotli's niveaus 1-4 snel. Niveaus 5+ worden aanzienlijk langzamer.
Compressievergelijking
| Contenttype | gzip niveau 5 | Brotli niveau 4 | Winnaar |
|---|---|---|---|
| HTML | 72% | 78% | Brotli |
| CSS | 80% | 85% | Brotli |
| JavaScript | 75% | 82% | Brotli |
| JSON | 78% | 83% | Brotli |
De verhoudingen geven bespaarde bytes aan ten opzichte van het origineel. Brotli wint consistent met 5-8 procentpunten voorsprong.
Beide modules kunnen gelijktijdig draaien. Nginx serveert Brotli aan clients die Accept-Encoding: br aangeven en valt terug op gzip voor de rest.
Hoe optimaliseer je TLS- en HTTP/2-prestaties in Nginx?
TLS voegt latency toe door handshakes en sleuteluitwisseling. Session caching, OCSP stapling en TLS 1.3 minimaliseren die overhead. HTTP/2 multiplext requests over een enkele verbinding en elimineert head-of-line blocking op HTTP-niveau.
HTTP/2
Sinds Nginx 1.25.1 is de http2 parameter op de listen directive verouderd. Gebruik in plaats daarvan de http2 directive:
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
#...
}
TLS-prestaties
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 slaat TLS-sessieparameters op in een 10 MB gedeelde geheugenzone. Eén megabyte bevat ongeveer 4.000 sessies. Terugkerende clients slaan de volledige TLS-handshake over en hervatten met een veel goedkopere operatie.
ssl_session_tickets off is de veiligere standaard. Session tickets gebruiken een symmetrische sleutel die, bij compromittering, alle eerdere sessies ontsleutelt (geen forward secrecy). Als je tickets nodig hebt voor multi-server setups, roteer de sleutels dan regelmatig.
ssl_stapling on laat Nginx de OCSP-response van je CA ophalen en cachen, en vervolgens opnemen in de TLS-handshake. De client hoeft de CA niet apart te contacteren. Dit bespaart 100-300 ms bij de eerste verbinding.
ssl_prefer_server_ciphers off is correct voor TLS 1.3, waar client en server ciphers anders onderhandelen. Voor TLS 1.2 achterwaartse compatibiliteit doen de geselecteerde ciphers er nog toe, maar TLS 1.3 cipher suites zijn allemaal sterk.
Voor de volledige TLS- en Let's Encrypt-setup, zie de handleiding over TLS en Let's Encrypt met Nginx.
Welke Linux kernel-instellingen verbeteren de Nginx-prestaties?
Vier kernel-parameters beperken het vermogen van Nginx om hoge verbindingsvolumes te verwerken. De standaardwaarden zijn conservatief voor een algemene server. Het afstemmen ervan verwijdert knelpunten op OS-niveau.
| Parameter | Standaard | Aanbevolen | Waarom |
|---|---|---|---|
net.core.somaxconn |
4096 | 65535 | Maximale listen backlog-wachtrij. Lage waarden veroorzaken verbindingsuitval bij pieken. |
fs.file-max |
~100000 | 500000 | Systeembrede file descriptor-limiet. Elke verbinding is een file descriptor. |
net.ipv4.tcp_tw_reuse |
0 | 1 | Hergebruik TIME_WAIT-sockets voor nieuwe verbindingen. Versnelt verbindingsrecycling. |
net.ipv4.tcp_fastopen |
0 | 3 | Schakelt TCP Fast Open in voor zowel client als server. Bespaart één roundtrip bij nieuwe verbindingen. |
net.ipv4.ip_local_port_range |
32768 60999 | 1024 65535 | Vergroot het bereik van efemere poorten voor uitgaande verbindingen (proxy/upstream). |
net.core.netdev_max_backlog |
1000 | 16384 | Wachtrijlengte voor inkomende pakketten wanneer de interface sneller ontvangt dan de kernel verwerkt. |
Direct toepassen zonder herstart:
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
Persistent maken na herstart:
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
Verhoog ook de per-process file descriptor-limiet voor de Nginx systemd-unit. Maak een override aan:
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
Hoe optimaliseer je de access log-prestaties?
Een logregel schrijven voor elk request verbruikt schijf-I/O. Op servers met veel verkeer kan access logging een knelpunt worden. Gebufferd loggen schrijft in batches naar schijf.
http {
access_log /var/log/nginx/access.log combined buffer=64k flush=5s;
}
buffer=64k verzamelt logentries in een 64 KB geheugenbuffer. flush=5s schrijft de buffer minstens elke 5 seconden naar schijf, zelfs als deze niet vol is. Je ruilt een paar seconden logvertraging in voor veel minder schijf-I/O.
Als je geen access logs nodig hebt voor statische assets (afbeeldingen, CSS, JS), schakel ze dan per location uit zoals getoond in de cachingsectie hierboven.
Hoeveel sneller is een getunede Nginx?
Voer dezelfde wrk-benchmark uit na het toepassen van alle wijzigingen. Test vanaf dezelfde machine, met dezelfde parameters:
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
Voor vs. na
| Metriek | Voor | Na | Verandering |
|---|---|---|---|
| Requests/sec | 16.442 | 39.014 | +137% |
| Gem. latency | 12,34 ms | 5,21 ms | -58% |
| Max latency | 89,12 ms | 42,56 ms | -52% |
| Transfer/sec | 65,52 MB | 155,61 MB | +137% |
Deze cijfers komen van een Virtua Cloud VPS met 4 vCPU's, 8 GB RAM op Debian 12 met Nginx mainline, die een statische HTML-pagina met CSS- en JavaScript-assets serveert. Je resultaten variëren afhankelijk van workload, netwerkcondities en of je naar een backend proxyt.
De grootste winst komt van kernel sysctl (verwijdert OS-knelpunten), worker/verbinding-tuning (benut alle beschikbare CPU) en compressie (vermindert bytes op de lijn). TLS session caching en HTTP/2 hebben een kleiner maar meetbaar effect, vooral op first-connection latency.
Volledige getunede configuratie
Volledig /etc/nginx/nginx.conf-bestand met alle tuning toegepast:
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;
# TCP-optimalisatie
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Keepalive
keepalive_timeout 65;
keepalive_requests 1000;
# Buffers
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;
# Bestandscache
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 (indien module geïnstalleerd)
# 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;
# Versie verbergen
server_tokens off;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
server_tokens off verbergt de Nginx-versie in response headers en foutpagina's. Versie-onthulling helpt aanvallers bekende kwetsbaarheden te targeten.
Gaat er iets mis?
Controleer eerst het error log:
journalctl -u nginx -f
Veelvoorkomende problemen na het tunen:
- "too many open files" in het error log:
worker_rlimit_nofileis lager danworker_connections, of de systemdLimitNOFILEis niet ingesteld. Controleer beide. - "could not build optimal types_hash": verhoog
types_hash_max_sizenaar 4096 in hethttp-blok. - Brotli-module laadt niet: voer
nginx -V 2>&1 | grep brotliuit om te controleren of de module is gecompileerd. Bij dynamische modules, controleer ofload_moduledirectives aanwezig zijn bovenaannginx.conf. - OCSP stapling werkt niet: het eerste request na het starten van Nginx zal geen stapled response hebben. Test met
openssl s_client -connect your-server:443 -status < /dev/null 2>&1 | grep -A 2 "OCSP Response". Als het "no response sent" toont, controleer ofssl_trusted_certificatenaar de volledige chain wijst enresolveris ingesteld. - wrk toont geen verbetering: zorg dat je vanaf een andere machine test. Bij testen over het internet domineert netwerklatency en verbergt het de serverbijdrage. Test vanaf een VPS in hetzelfde datacenter voor nauwkeurige cijfers.
Copyright 2026 Virtua.Cloud. Alle rechten voorbehouden. Deze inhoud is een origineel werk van het Virtua.Cloud-team. Reproductie, herpublicatie of herdistributie zonder schriftelijke toestemming is verboden.
Klaar om het zelf te proberen?
Deploy uw eigen server in seconden. Linux, Windows of FreeBSD.
Bekijk VPS-aanbod