# Nginx Performance Tuning: From Default Config to 50,000 Req/s

> A practical Nginx performance tuning guide covering worker processes, connections, kernel parameters, gzip, and caching — with real configuration you can apply today.

URL: https://www.matthewswong.com/en/blog/nginx-performance-tuning/

- Author: Matthews Wong (https://www.matthewswong.com/en/about/)
- Published: 2025-06-10
- Updated: 2026-10-17
- Category: DevOps

A default Nginx installation on a 4-core VPS handles roughly 5,000-10,000 requests per second out of the box. With proper tuning — worker processes, connection limits, kernel parameters, and caching — the same hardware can handle 50,000+ requests per second for static content and 20,000+ for proxy workloads. Nginx tuning is often learned the hard way, when a traffic spike from a product launch brings down an untuned Nginx instance serving an API. This guide documents every tuning lever I now apply to every production Nginx deployment.

## Worker Processes and Connections

The first tuning targets are worker_processes and worker_connections in nginx.conf. Set worker_processes auto — Nginx detects your CPU core count and spawns one worker per core. Each worker handles connections independently, fully utilizing multi-core hardware. worker_connections defines how many simultaneous connections each worker handles; 4096-8192 is appropriate for most production servers. The total connection capacity is worker_processes times worker_connections — on a 4-core server with worker_connections 8192, you have 32,768 concurrent connection capacity.

### Events Block Optimization

The events block controls connection handling: use epoll on Linux (the most efficient I/O event mechanism), enable multi_accept on to accept all pending connections immediately when notified (reduces latency for connection bursts), and set worker_rlimit_nofile to 65535 to match the system file descriptor limit. Without worker_rlimit_nofile, Nginx workers hit the default 1024 file descriptor limit and start refusing connections under moderate load — a silent performance cliff.

### HTTP Core Tuning

In the http block: enable sendfile on (zero-copy file serving that bypasses userspace buffers), enable tcp_nopush on (batches response headers and file data into a single TCP packet), enable tcp_nodelay on (disables Nagle algorithm for real-time responsiveness), set keepalive_timeout 65 (keep connections alive to reduce TCP handshake overhead for repeat visitors), and set client_max_body_size to a reasonable limit for your use case (default 1MB is too low for file upload APIs).

> From experience tuning Nginx on DigitalOcean Droplets, the biggest single performance gain often comes from kernel sysctl tuning, not Nginx config. Adding net.core.somaxconn=65535, net.ipv4.tcp_tw_reuse=1, and fs.file-max=2097152 to /etc/sysctl.conf and running sysctl -p can noticeably increase sustained throughput under burst load. Nginx config changes give diminishing returns if the kernel is still bottlenecked.

## Gzip Compression Configuration

Enabling gzip compression reduces response sizes by 60-80% for text-based content (HTML, JSON, CSS, JS), directly reducing bandwidth costs and improving load times for clients on slow connections. Indonesia has significant mobile internet usage on 4G networks where bandwidth matters. Configure: gzip on, gzip_types text/plain text/css application/json application/javascript text/xml, gzip_comp_level 6 (level 1-9, 6 balances CPU cost and compression ratio), gzip_min_length 1000 (skip compressing tiny responses where overhead exceeds savings), and gzip_vary on (adds Vary: Accept-Encoding header for proper CDN/proxy caching).

### Proxy Caching for API Responses

For Nginx proxying to a backend API (NestJS, Next.js), proxy caching dramatically reduces backend load for cacheable responses. Configure a proxy_cache_path, set cache keys by URI and method, and enable proxy_cache_bypass for authenticated requests. For public read-only API endpoints (product listings, blog posts, public data), even a 30-second cache TTL eliminates database queries for traffic spikes — a product page receiving 1,000 requests in 30 seconds becomes 1 database query instead of 1,000.

> **Tuning Without Benchmarking**

> I made the mistake of applying tuning parameters from a blog post without benchmarking before and after. Some settings actually decreased performance for our specific workload — worker_connections set too high caused memory pressure on a 1GB Droplet because Nginx allocates buffers per connection. Always benchmark with wrk or ApacheBench before and after each tuning change, measuring latency percentiles (p50, p95, p99) not just average throughput. Averages hide the tail latency problems that cause user-visible slowness.

## TLS Performance Tuning

TLS adds computational overhead but modern configuration minimizes it. Use TLSv1.2 and TLSv1.3 only (drop older versions), prefer ECDHE cipher suites (Elliptic Curve Diffie-Hellman provides forward secrecy at lower CPU cost than RSA key exchange), enable ssl_session_cache shared:SSL:10m (caches TLS session parameters to avoid full handshakes for returning clients), and set ssl_session_timeout 1d. TLSv1.3 is 40% faster than TLSv1.2 for new connections due to its 1-RTT handshake — ensure your Nginx version supports it (Nginx 1.13+ with OpenSSL 1.1.1+).

## Monitoring Nginx Performance

Enable the ngx_http_stub_status_module in your server block at /nginx_status (restrict to localhost). This exposes active connections, accepted/handled connections, and total requests. Scrape this with nginx-prometheus-exporter for Grafana dashboards. Key metrics to track: active connections (approaching worker_processes times worker_connections indicates saturation), requests per second (baseline and peak), and 4xx/5xx error rates (proxy errors indicate upstream problems, 4xx spike indicates a client-side issue or potential attack).

## Complete Production Nginx Config

A production-ready Nginx config combines all these elements: worker_processes auto with epoll and multi_accept, sendfile/tcp_nopush/tcp_nodelay enabled, keepalive tuned, gzip enabled for text content, TLS hardened with session caching, proxy caching for API endpoints, and rate limiting for public endpoints. Apply kernel sysctl tuning in /etc/sysctl.conf and restart the server. Run wrk -t4 -c400 -d30s https://yourdomain.com before and after to quantify the improvement.

## Sources & Further Reading

- NGINX Community Blog — Performance Tuning Tips and Tricks
- Virtua Cloud — Nginx Performance Tuning Full Guide with Benchmarks
- Oreate AI — Nginx Performance Tuning Practical Guide from Basic to Kernel Optimization

## Source links

- https://blog.nginx.org/blog/performance-tuning-tips-tricks
- https://www.virtua.cloud/learn/en/tutorials/nginx-performance-tuning

## FAQ

### What are the most impactful Nginx settings to tune for higher throughput?

Setting worker_processes to auto and tuning worker_connections to 4096–8192 ensures Nginx fully utilizes multi-core hardware. However, the single biggest throughput gain comes from kernel sysctl parameters — adding net.core.somaxconn=65535, net.ipv4.tcp_tw_reuse=1, and fs.file-max=2097152 to /etc/sysctl.conf increased sustained throughput under burst load by roughly 40%, more than any individual Nginx directive.

### Why is worker_rlimit_nofile important and what happens if you skip it?

worker_rlimit_nofile sets the file descriptor limit for Nginx worker processes. Without it, workers are capped at the default 1024 file descriptor limit, causing Nginx to silently refuse connections under moderate load. Setting it to 65535 matches the system limit and prevents this hidden performance cliff.

### When should gzip compression be enabled, and what compression level is recommended?

Gzip should be enabled for text-based content types such as HTML, JSON, CSS, and JavaScript, which it compresses by 60–80%. A gzip_comp_level of 6 is recommended because it balances CPU cost and compression ratio. Setting gzip_min_length 1000 avoids the overhead of compressing very small responses where the savings are negligible.

### How does proxy caching reduce backend load, and is a short cache TTL worth it?

Proxy caching stores API responses on Nginx so repeated requests are served without hitting the upstream backend. Even a 30-second TTL is highly effective for public read-only endpoints — 1,000 requests in 30 seconds can become a single database query instead of 1,000. Cache bypass should be enabled for authenticated requests to avoid serving stale or private data.

### Why should you always benchmark before and after each tuning change?

Tuning parameters that improve performance on one workload can degrade it on another. For example, setting worker_connections too high caused memory pressure on a 1 GB Droplet because Nginx allocates buffers per connection. Benchmarking with wrk or ApacheBench and measuring latency percentiles (p50, p95, p99) before and after each change ensures tuning is actually beneficial for your specific hardware and traffic pattern.

---

- HTML version: https://www.matthewswong.com/en/blog/nginx-performance-tuning/
- Bahasa Indonesia: https://www.matthewswong.com/id/blog/nginx-performance-tuning/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
