High-Concurrency Nginx Tuning on Ubuntu: My Practice

This post shares my personal high-concurrency Nginx configuration on a 2-core 2 GB Ubuntu box. Other Linux systems may differ — take it as reference only.

This post shares my personal high-concurrency Nginx configuration on a 2-core 2 GB Ubuntu box. Other Linux systems may differ — take it as reference only.

The numbers

Mine is a personal 2-core 2 GB machine with a small disk. The request volume is so large that I stopped looking at access.log, so I can’t compute QPS/TPS properly, and since it’s serving live traffic I never ran a load test — I don’t know where the ceiling is. All I can give is the current state while serving normally:

  • Requests: ~1,368 per second on average; 80,000/min; 4.92 million/hour; 100 million/day; 3.4 billion/month
  • Concurrency: around 3,000 (netstat -apn|grep nginx|wc -l)
  • CPU: 50%–60%
  • HTTPS: off — with SSL enabled the CPU would max out

Why 1,300 requests/second but 3,000 concurrent connections? Because there’s a business service behind Nginx. The backend takes time; if it can’t chew through 1,300 requests per second, Nginx has to hold connections waiting for responses — so concurrency exceeds per-second request count.

Request volume

Concurrency

Environment

  • Hardware: shared cloud host, 2-core AMD EPYC, 2 GB RAM, 4 GB SWAP
  • OS: Ubuntu 22.04.3 LTS
  • Software: Nginx 1.18.0
  • SSL: off; HTTPS terminates at the CDN, origin pulls use plain HTTP

OS-level configuration

On Linux everything is a file, including TCP connections, so the OS limits must be raised first — Linux caps the maximum open files per process.

Open file limits

Edit /etc/security/limits.conf — one line per user config — and add:

www-data soft nofile 1048576
www-data hard nofile 1048576

I run nginx as www-data; adjust to your setup. This sets both the soft and hard limits.

Edit /etc/systemd/system.conf — modify or add DefaultLimitNOFILE:

DefaultLimitNOFILE=65535:524288

Edit /etc/sysctl.conf and add fs.file-max:

fs.file-max = 1048576
vm.max_map_count = 1048576

Edit /etc/default/nginx and add ULIMIT:

ULIMIT="-n 1048576"

Set the content of /proc/sys/vm/max_map_count to 1048576 — this adjusts the connection tracking module’s maximum. Run directly:

echo 1048576 > /proc/sys/vm/max_map_count

Nginx configuration

Edit the nginx config (mine is /etc/nginx/nginx.conf). Main changes:

  • worker_processes 2; — match your CPU thread count
  • worker_rlimit_nofile 65535; — max open files
  • worker_connections 65535; in events — clients served per worker
  • multi_accept on; in events — let each worker accept multiple connections
  • use epoll; in events — the preferred method on newer Linux kernels

Combined:

user www-data;
worker_processes 1;
worker_rlimit_nofile 65535;
events {
use epoll;
worker_connections 65535;
multi_accept on;
}

The http block settings depend on your actual backend — header sizes, body sizes and so on — so I won’t show them.

Verifying the configuration

Kernel parameter changes may require a reboot — recommended. If you can’t reboot, run:

sysctl -p

Run ulimit -n to see the current open-files limit. Then find the nginx PID with ps (mine was 601) and print its limits:

cat  /proc/601/limits

Check whether Max open files has actually changed.

Max open files