Performance tuning: workers, buffers and timeouts
Worker processes and connections, sendfile and tcp_nopush, buffer sizing, keepalive timeouts, file descriptor limits, and how to benchmark a change.
Worker processes and connections
worker_processes auto; # one per CPU core
worker_rlimit_nofile 65535; # must match the systemd LimitNOFILE
events {
worker_connections 8192;
multi_accept on; # accept as many pending connections as possible
use epoll; # the default on Linux, stated for clarity
}
http {
sendfile on; # copy file to socket in the kernel
tcp_nopush on; # send full packets with sendfile
tcp_nodelay on; # do not delay small writes
keepalive_timeout 65 20; # 65s to the client, 20s in the Keep-Alive header
keepalive_requests 1000; # reuse a connection for more requests
reset_timedout_connection on;
types_hash_max_size 4096;
}- The theoretical connection ceiling is
worker_processestimesworker_connections, but each proxied request consumes two, one client and one upstream. worker_rlimit_nofileis useless unless the service manager also raises the limit; check withsystemctl show nginx -p LimitNOFILE.tcp_nopushandtcp_nodelayare not contradictory: nopush fills packets when sending a file, nodelay sends immediately for small responses.- Increase
keepalive_requestsfor asset-heavy pages or the client reconnects constantly across a single page load.
Buffer and timeout sizing
client_body_buffer_size 16k; # in-memory buffer for a request body
client_max_body_size 10m; # larger uploads get a 413
client_body_timeout 12s;
client_header_timeout 12s;
send_timeout 10s;
proxy_buffers 16 16k;
proxy_buffer_size 16k; # must hold the response headers
proxy_busy_buffers_size 32k;
proxy_max_temp_file_size 1024m;
# tune for throughput on a fast internal network, not latency per request
proxy_connect_timeout 3s;
proxy_read_timeout 60s;
# serve a response slowly to a slow client without holding a worker
limit_rate_after 1m;
limit_rate 512k;| Symptom | Likely cause | Direction |
|---|---|---|
upstream sent too big header | proxy_buffer_size too small | Raise it to a few multiples of the header |
| 413 on upload | client_max_body_size | Raise for the upload location only |
| Slow first byte | Upstream, not nginx | Check $upstream_response_time |
Workers busy with Writing | Slow clients | limit_rate, more workers |
| Timeouts under load | Backlog or upstream saturation | Check Waiting and upstream timings |
Measure, then change one thing
# quick load test with concurrency, against a static file first
ab -n 20000 -c 200 https://example.com/static/app.css
# then against the dynamic path
ab -n 2000 -c 50 https://example.com/api/health
# watch the error log and the dashboard during the run
tail -f /var/log/nginx/error.log &
curl -s http://127.0.0.1/nginx_status⚠️
Benchmark the whole path, not nginx in isolation. A tuning change that improves nginx's own throughput while the database is already saturated moves no user-visible metric. Establish where the time actually goes with
$upstream_response_time before changing a single buffer.FAQ
How many worker processes should I run?
One per available CPU core, which is what
auto does. More workers than cores adds context switching and no throughput; fewer leaves cores idle.Why does nginx report worker_connections exceeded?
You have hit the per-worker connection ceiling. Raise
worker_connections and worker_rlimit_nofile, and remember that each proxied request uses a client connection plus an upstream connection.Related
Load balancing and upstreams Logging, metrics and debugging a config
Last refreshed 2026-09-18.