Skip to content
Logo
Table of contents
VeryCloud
DocumentationVPS Linux

Tune Nginx for high load

Tune Nginx for high load

Configure Nginx to serve thousands of simultaneous connections. This guide covers workers, keepalive, gzip/brotli compression, caching and TLS/HTTP2 optimizations for a production VPS.

Category
VPS Linux
Level
Intermediate
Reading time
6 min
Published
May 17, 2026

Introduction

Default Nginx correctly serves a few hundred requests per second. With proper tuning on a modern VPS, it easily exceeds 10,000 req/s for static content and 1000-3000 req/s for dynamic content.

This guide gives essential parameters to:

  • Maximize simultaneous connections
  • Enable compression (gzip + brotli)
  • Optimize static file cache
  • Configure modern TLS (HTTP/2, OCSP stapling)
  • Reduce latency

Prerequisites

  • Linux VPS with Nginx 1.18+
  • Root access
  • Kernel optimizations already applied (see /docs/article/kernel-tuning)

Step 1: Identify your Nginx

nginx -V 2>&1

Note compiled modules (gzip, brotli, http2, etc.). If brotli is missing on Debian/Ubuntu, install libnginx-mod-brotli:

sudo apt install -y libnginx-mod-brotli

Step 2: Configure workers

The worker is the process serving requests. On a multi-core VPS, configure:

sudo nano /etc/nginx/nginx.conf

Main block:

# Number of workers = number of CPUs
worker_processes auto;

# Increase file descriptors per worker
worker_rlimit_nofile 65535;

events {
    # Simultaneous connections per worker (worker_processes × worker_connections = theoretical max)
    worker_connections 16384;
    
    # Multi-accept (accept multiple connections per event)
    multi_accept on;
    
    # epoll is best on Linux
    use epoll;
}

With worker_processes auto and 4 cores: 4 workers × 16384 = 65536 max simultaneous connections.

Step 3: Global HTTP optimizations

In the http { } block:

http {
    # === Basic performance ===
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    
    # === Keepalive (TCP connection reuse) ===
    keepalive_timeout 30;
    keepalive_requests 1000;
    
    # === Hash tables for faster server resolution ===
    server_names_hash_bucket_size 128;
    server_names_hash_max_size 4096;
    types_hash_max_size 2048;
    
    # === Buffers ===
    client_body_buffer_size 16K;
    client_header_buffer_size 1k;
    client_max_body_size 100M;       # Uploads
    large_client_header_buffers 4 16k;
    
    # === Timeouts ===
    client_body_timeout 12;
    client_header_timeout 12;
    send_timeout 10;
    reset_timedout_connection on;
    
    # === Hide Nginx version (security) ===
    server_tokens off;
}

Step 4: Enable gzip compression

http {
    # === Gzip compression ===
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_min_length 1024;
    gzip_types
        application/atom+xml
        application/javascript
        application/json
        application/ld+json
        application/manifest+json
        application/rss+xml
        application/vnd.geo+json
        application/vnd.ms-fontobject
        application/x-font-ttf
        application/x-web-app-manifest+json
        application/xhtml+xml
        application/xml
        font/opentype
        image/bmp
        image/svg+xml
        image/x-icon
        text/cache-manifest
        text/css
        text/plain
        text/vcard
        text/vnd.rim.location.xloc
        text/vtt
        text/x-component
        text/x-cross-domain-policy
        text/xml;
}

gzip_comp_level 6 is the optimal balance (1 = fast/larger sizes, 9 = compact/slow).

Step 5: Enable brotli (if installed)

Brotli compresses 15-25% better than gzip for text. Supported by all modern browsers.

http {
    # === Brotli compression ===
    brotli on;
    brotli_comp_level 6;
    brotli_min_length 1024;
    brotli_types
        application/javascript
        application/json
        application/xml
        application/atom+xml
        application/rss+xml
        image/svg+xml
        text/css
        text/javascript
        text/plain
        text/xml;
}

If you see Content-Encoding: br in headers, brotli is active.

Step 6: Configure static file caching

http {
    # === File cache (file descriptors) ===
    open_file_cache max=200000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
}

And in your server { } blocks, add cache headers for statics:

server {
    # ...
    
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2|svg|webp)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
    
    location ~* \.(pdf|zip|tar|gz)$ {
        expires 7d;
        add_header Cache-Control "public";
    }
}

Step 7: TLS / HTTPS optimizations

http {
    # === SSL ===
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;  # In TLS 1.3, clients choose
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
    
    # === SSL session ===
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 4h;
    ssl_session_tickets off;
    
    # === OCSP stapling (verify cert without client round-trip) ===
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 8.8.8.8 valid=60s;
    resolver_timeout 5s;
    
    # === HSTS ===
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}

Step 8: Enable HTTP/2 and HTTP/3

In each server { listen 443 ... }:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    
    # HTTP/3 if Nginx 1.25+ with quic module
    # listen 443 quic reuseport;
    # add_header Alt-Svc 'h3=":443"; ma=86400';
    
    # ...
}

Verify with:

curl -I --http2 https://your-domain.com

You should see HTTP/2 200.

Step 9: Reverse proxy to a backend app (PHP-FPM, Node, etc.)

Specific tuning to reduce latency:

upstream backend {
    server 127.0.0.1:3000;
    
    # Keepalive to backend (very important!)
    keepalive 32;
    keepalive_timeout 60s;
    keepalive_requests 10000;
}

server {
    location / {
        proxy_pass http://backend;
        
        # Force HTTP/1.1 for keepalive
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        
        # Headers to forward
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Buffer response to avoid blocking backend
        proxy_buffering on;
        proxy_buffer_size 16k;
        proxy_buffers 32 16k;
        proxy_busy_buffers_size 32k;
        
        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

Step 10: Proxy caching (very powerful for slow apps)

http {
    # === Proxy cache ===
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:100m max_size=10g inactive=60m use_temp_path=off;
}

server {
    location / {
        proxy_cache app_cache;
        proxy_cache_revalidate on;
        proxy_cache_min_uses 1;
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_lock on;
        proxy_cache_valid 200 10m;
        proxy_cache_valid 404 1m;
        
        # Add header for debug
        add_header X-Cache-Status $upstream_cache_status;
        
        proxy_pass http://backend;
        # ... rest of config
    }
}

Test:

curl -I https://your-domain.com
# Look for "X-Cache-Status: HIT" after 2nd call

Step 11: Rate limiting (anti-flood)

http {
    # Per-IP limit
    limit_req_zone $binary_remote_addr zone=general:10m rate=30r/s;
    limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
}

server {
    # General: 30 req/s per IP, burst of 50
    limit_req zone=general burst=50 nodelay;
    
    location /login {
        # Login: 5 req/min per IP (anti brute-force)
        limit_req zone=login burst=2 nodelay;
        # ...
    }
}

Step 12: Test config and apply

sudo nginx -t
sudo systemctl reload nginx

Step 13: Benchmark

# Install wrk
sudo apt install -y wrk

# Test
wrk -t12 -c400 -d30s https://your-domain.com

Typical output:

Running 30s test @ https://your-domain.com
  12 threads and 400 connections
  Thread Stats   Avg      Stdev     Max
    Latency    45ms     20ms    300ms
    Req/Sec     1.5k    200      2k
  Requests/sec:  17500
  Transfer/sec:   30MB

Troubleshooting

"worker_connections exceeded"

Increase worker_connections or worker_rlimit_nofile.

"Too many open files" error

Service doesn't respect system limits. Add to /etc/systemd/system/nginx.service.d/override.conf:

[Service]
LimitNOFILE=65535
sudo systemctl daemon-reload
sudo systemctl restart nginx

High PHP latency

Often due to badly tuned PHP-FPM. Increase pm.max_children in the PHP-FPM pool:

sudo nano /etc/php/8.3/fpm/pool.d/www.conf
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20

Cache not working

Check permissions:

sudo chown -R www-data:www-data /var/cache/nginx

And that your requests don't contain cookies (otherwise cache is skipped by default).

Useful commands

# Test config
sudo nginx -t

# Reload (no downtime)
sudo systemctl reload nginx

# View active connections
ss -tn | grep :443 | wc -l

# Top IPs by requests (from access.log)
sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10

# Real-time status (requires stub_status module)
curl http://127.0.0.1/nginx_status

# Check if gzip/brotli works
curl -I -H "Accept-Encoding: gzip, br" https://your-domain.com

Conclusion

With these optimizations, your Nginx is ready for high load:

  • HTTP/2 + keepalive + brotli → -40 to -60% client bandwidth
  • Proxy cache → reduces backend load by 90%+
  • Rate limiting → application-level anti-flood protection

Going further:

  • Compile Nginx with ngx_brotli and ngx_lua from sources
  • Add Cloudflare in front to offload even more caching
  • Configure WAF ModSecurity for application security

Resources

Join our Discord community server

For any questions, suggestions, or just to chat with the community, join us on Discord!

900+Members