Out of the box, Nginx is remarkably fast. Even with basic default settings, it easily outperforms Apache on static file delivery.
However, default Nginx installations are configured conservatively to ensure compatibility with minimal virtual machines (often defaulting to worker_processes 1; and worker_connections 512;).
When a growing website in Pakistan runs a viral marketing campaign, flash sale, or news event, concurrent traffic surges from hundreds to tens of thousands of simultaneous TCP connections.
Under default settings, the server hits a wall:
[alert] 12450#12450: *45210 512 worker_connections are not enough while connecting to upstream
Incoming connection attempts are rejected, browsers hang with connection reset errors, and server logs fill with worker exhaustion alerts.
To unlock the true concurrency potential of your hardware, you must align Nginx’s asynchronous event-driven core with the underlying Linux kernel architecture.
In this technical masterclass, we explore how to tune worker_processes, configure worker_connections, enable epoll and multi_accept, and eliminate file descriptor bottlenecks on Cloud VPS and Dedicated Servers in Pakistan.
Understanding the Nginx Master-Worker Process Architecture
Nginx uses a non-blocking, event-driven model:
[Nginx Master Process (Root)]
│ (Reads config, binds ports, manages workers)
├── Worker Process 1 (CPU Core 0) ──► Non-blocking epoll loop (Handles 10,000 sockets)
├── Worker Process 2 (CPU Core 1) ──► Non-blocking epoll loop (Handles 10,000 sockets)
├── Worker Process 3 (CPU Core 2) ──► Non-blocking epoll loop (Handles 10,000 sockets)
└── Worker Process 4 (CPU Core 3) ──► Non-blocking epoll loop (Handles 10,000 sockets)
- Master Process: Runs with elevated privileges, reads configuration files, binds to network ports (80/443), and oversees worker lifecycles.
- Worker Processes: Run unprivileged (as
www-dataornginx). Each worker runs a continuousepollevent loop, multiplexing thousands of concurrent connections over a single CPU thread without creating expensive operating system threads or context switches.
Step 1: Tuning worker_processes and CPU Affinity
Never guess the number of worker processes; bind them directly to the number of physical CPU cores:
# Automatically spawn one worker process per available CPU core
worker_processes auto;
# Bind worker processes to specific CPU cores to eliminate L1/L2 cache thrashing
worker_cpu_affinity auto;
Binding workers with worker_cpu_affinity auto ensures each worker process stays pinned to its designated CPU core, maximizing CPU cache hit ratios and reducing inter-core memory latency.
Step 2: Calculating Maximum Connections (worker_connections)
The worker_connections directive defines the maximum number of simultaneous connections an individual worker process can keep open:
$$\text{Max Clients (Static Files)} = \text{worker_processes} \times \text{worker_connections}$$
$$\text{Max Clients (Reverse Proxy)} = \frac{\text{worker_processes} \times \text{worker_connections}}{2}$$
(Note: In reverse proxy mode, Nginx requires two socket connections per client: one to the client, and one to the upstream backend!)
In /etc/nginx/nginx.conf:
events {
# Increase maximum open connections per worker
worker_connections 8192;
# Use Linux high-performance epoll event multiplexer
use epoll;
# Allow a worker to accept all incoming connections in the listen queue at once
multi_accept on;
}
use epoll;: Instructs Nginx to use the Linux kernel’s (O(1))epollsystem call instead of slow (O(N))selectorpoll.multi_accept on;: Directs worker processes to accept all waiting connections in the TCP backlog queue simultaneously, rather than processing them one by one.
Step 3: Removing the Linux File Descriptor Limit (nofile)
In Linux, everything is a file—including network TCP sockets. If Nginx tries to open 8,192 connections per worker, but the operating system caps open files at the default limit of 1,024, Nginx will fail with:
[error] socket() failed (24: Too many open files).
1. Set worker_rlimit_nofile in Nginx
Add this at the top of /etc/nginx/nginx.conf (outside the events and http blocks):
# Must be greater than (worker_processes * worker_connections * 2)
worker_rlimit_nofile 65535;
2. Update Systemd Service Limits
On modern Linux distributions (Ubuntu, AlmaLinux, Debian), Systemd limits take precedence over /etc/security/limits.conf:
# Create systemd override for nginx
sudo mkdir -p /etc/systemd/system/nginx.service.d/
cat << 'EOF' | sudo tee /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=65535
EOF
# Reload systemd daemon
sudo systemctl daemon-reload
sudo systemctl restart nginx
Verify that Nginx’s active process limit reflects the change:
cat /proc/$(pgrep -f "nginx: worker" | head -n 1)/limits | grep "Max open files"
Output:
Max open files 65535 65535 files
Step 4: Socket and TCP Stack Optimizations
Add these TCP socket directives inside the http block of nginx.conf:
http {
# Zero-copy data transfer: copies data directly between file and socket in kernel space
sendfile on;
# Send full TCP packets (reduces network overhead)
tcp_nopush on;
# Disable Nagle's algorithm for sub-millisecond response latency
tcp_nodelay on;
# Keep-alive timeout for browser connections
keepalive_timeout 65;
keepalive_requests 1000;
# Hide Nginx version header for security
server_tokens off;
}
Stress Testing the Tuned Stack
Verify your new configuration using a benchmarking utility like wrk from an external client:
wrk -t8 -c2000 -d30s https://yourdomain.pk/
With epoll, multi_accept, and elevated file descriptor limits active, your Nginx server will sustain tens of thousands of concurrent requests with sub-10ms response latency on Dedicated Servers.
Scale Your Linux Fleet with NextGen Bare Metal
Deliver ultra-fast web performance under heavy traffic spikes. NextGen Dedicated Servers and Cloud VPS in Pakistan feature pure NVMe arrays, high-frequency AMD EPYC processors, and unmetered 10Gbps connectivity.
