Scaling Nginx as an API Gateway: Tuning Sysctl and Configs for Sub-Millisecond Latency
Let’s be honest: your API is slow, and it’s likely not because of your PHP or Python code. If you are serving mobile apps or rich AJAX-heavy clients, the real killer is connection overhead. In the world of high-frequency REST requests, the default Linux network stack is conservative, outdated, and frankly, insufficient for modern traffic.
I recently audited a setup for a client launching a large-scale e-commerce backend here in Oslo. They were throwing money at bigger servers, yet their 99th percentile latency remained stuck at 200ms. The culprit? TCP handshake overhead and file descriptor exhaustion.
In this guide, we are going to rip apart the default configurations. We will tune the Linux kernel 3.x stack, optimize Nginx 1.2 (or the 1.3 development branch if you are brave), and discuss why hardware architecture—specifically virtualization type—is the foundation you cannot tune your way out of.
1. The OS Layer: Stop Running Out of Sockets
Before touching Nginx, look at your kernel. By default, Linux is configured as a polite desktop workstation, not a high-throughput server. When your API handles thousands of small requests per second, you run out of ephemeral ports. You’ll see TIME_WAIT spikes in netstat.
Here is the /etc/sysctl.conf configuration we deploy on high-load CoolVDS instances to allow the kernel to recycle connections faster.
# /etc/sysctl.conf configuration for API Gateways
# Maximize the number of open file descriptors
fs.file-max = 2097152
# Increase the range of ephemeral ports
net.ipv4.ip_local_port_range = 1024 65535
# Allow reuse of sockets in TIME_WAIT state for new connections
net.ipv4.tcp_tw_reuse = 1
# Note: tcp_tw_recycle is dangerous if you use NAT, stick to reuse.
# Increase the backlog for incoming connections
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
# Increase TCP buffer sizes for 10GbE networks (if you are lucky enough to have one)
net.ipv4.tcp_rmem = 4096 87380 33554432
net.ipv4.tcp_wmem = 4096 65536 33554432
Apply these with sysctl -p. If you don't do this, Nginx will simply choke on the OS, regardless of its own config.
2. Nginx Tuning: The Worker Model
Nginx is event-driven, which makes it superior to Apache's prefork model for API gateways. However, default configs are often too timid. We need to match the worker_processes to the CPU cores provided by your VPS.
The Worker Configuration
Open your nginx.conf. We need to bump the file limits per worker and ensure the event model is using epoll (standard on Linux).
user www-data;
worker_processes auto; # Automatically detects cores (Newer Nginx versions) or set to specific number
pid /var/run/nginx.pid;
# Essential: Allow Nginx to open more files than the default 1024
worker_rlimit_nofile 100000;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
Upstream Keepalive: The Secret Sauce
This is where most setups fail. Nginx can speak HTTP/1.1 to the client, but often drops to HTTP/1.0 when talking to your backend (Node.js, PHP-FPM, or Python). This forces a new TCP handshake for every single internal request. This adds milliseconds of latency that compounds massively at scale.
You must configure the upstream block to keep connections open to your backend.
http {
# ... other http settings ...
upstream backend_api {
server 127.0.0.1:8080;
# Keep 64 idle connections to the upstream server
keepalive 64;
}
server {
listen 80;
server_name api.yourdomain.no;
location / {
proxy_pass http://backend_api;
# REQUIRED for keepalive to work
proxy_http_version 1.1;
proxy_set_header Connection "";
# Forwarding headers for logging/geo-ip
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
Pro Tip: If you are serving clients in Norway, ensure your server is physically located in Oslo or nearby. Physics is undefeated. The round-trip time (RTT) from Oslo to a server in Amsterdam is ~20ms. To a server in Oslo (like the ones connected to NIX), it's <5ms. That 15ms difference is perceptible when your app makes 10 sequential API calls.
3. Caching at the Edge
The fastest API request is the one that never hits your application logic. For read-heavy endpoints (like product catalogs or news feeds), implement micro-caching directly in Nginx.
# Define cache path
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m use_temp_path=off;
server {
# ...
location /api/products {
proxy_cache my_cache;
proxy_cache_valid 200 302 1m; # Cache successful responses for 1 minute
proxy_cache_valid 404 1m;
# Allow bypassing cache for debugging
proxy_cache_bypass $http_x_update_cache;
proxy_pass http://backend_api;
}
}
Even a 10-second cache can save your database from melting during a traffic spike.
4. The Hardware Reality: Why Virtualization Matters
You can apply every tweak above, but if your underlying disk I/O is fighting with 50 other "neighbors" on a crowded server, your API will stutter. This is the phenomenon of "I/O wait," and it kills consistent performance.
In 2013, many hosting providers still use OpenVZ or Virtuozzo. These are container technologies (similar to chroot on steroids). They share the host kernel. If one neighbor gets DDoS'd or runs a heavy MySQL query, your sysctl settings don't matter. You suffer.
This is why we standardized on KVM at CoolVDS.
| Feature | OpenVZ / Containers | CoolVDS (KVM) |
|---|---|---|
| Kernel | Shared (No custom modules) | Dedicated (Tune it how you want) |
| Resource Isolation | Soft limits (Overselling common) | Hard limits (RAM/CPU is yours) |
| Storage | Often standard HDD | Pure SSD Arrays |
For an API Gateway, I/O consistency is more important than raw burst speed. You need predictable latency. We use Pure SSD storage arrays (Enterprise grade) which provide massive IOPS compared to the spinning rust SAS drives common in budget hosting. When your Nginx logs are writing thousands of lines per second, you cannot afford to wait for a mechanical arm to move.
5. Compliance and Data Sovereignty
Operating in Norway means respecting the Personopplysningsloven (Personal Data Act). While we don't have the complexity of the proposed EU data reforms yet, data location is critical. Storing user data on servers physically located in Norway (under Norwegian jurisdiction) simplifies compliance significantly compared to relying on the US-EU Safe Harbor framework, which is coming under increasing scrutiny.
Final Check
Before you deploy, run a simple load test using ab (Apache Bench) or wrk if you have it compiled:
ab -k -c 100 -n 10000 http://127.0.0.1/api/test
If you aren't seeing thousands of requests per second on a simple endpoint, re-check your sysctl limits. Performance tuning is an iterative process, not a one-time switch.
Need a sandbox to test these configs? Don't risk your production environment. Spin up a KVM-based SSD instance on CoolVDS in Oslo. You get full root access, your own kernel, and the stability your API demands.