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_processes times worker_connections, but each proxied request consumes two, one client and one upstream.
  • worker_rlimit_nofile is useless unless the service manager also raises the limit; check with systemctl show nginx -p LimitNOFILE.
  • tcp_nopush and tcp_nodelay are not contradictory: nopush fills packets when sending a file, nodelay sends immediately for small responses.
  • Increase keepalive_requests for 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;
SymptomLikely causeDirection
upstream sent too big headerproxy_buffer_size too smallRaise it to a few multiples of the header
413 on uploadclient_max_body_sizeRaise for the upload location only
Slow first byteUpstream, not nginxCheck $upstream_response_time
Workers busy with WritingSlow clientslimit_rate, more workers
Timeouts under loadBacklog or upstream saturationCheck 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.

Load balancing and upstreams Logging, metrics and debugging a config

Last refreshed 2026-09-18.