Web Server Performance Tuning
A well-tuned web server handles more requests with fewer resources, reduces latency, and improves user experience. This guide covers worker configuration, compression, caching, CDN integration, and benchmarking techniques.
Worker Configuration (Nginx)
Worker Processes
Set worker_processes to match the number of CPU cores:
# /etc/nginx/nginx.conf
worker_processes auto; # auto-detects CPU count
Worker Connections
Each worker handles multiple simultaneous connections:
events {
worker_connections 4096;
multi_accept on;
use epoll; # Linux optimal event method
}
The theoretical maximum connections = worker_processes * worker_connections.
For a 4-core server with 4096 connections per worker, that is 16,384
simultaneous connections.
Keep-Alive
Persistent connections avoid the overhead of TCP and TLS handshakes for subsequent requests:
keepalive_timeout 65;
keepalive_requests 1000;
For upstream connections (proxy mode):
upstream backend {
server 10.0.1.10:8000;
keepalive 32; # connection pool per worker
}
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
Gzip Compression
Compress text-based responses to reduce bandwidth by 60-80%:
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 4; # 1-9, higher = more CPU
gzip_min_length 256;
gzip_types
text/plain
text/css
text/javascript
application/json
application/javascript
application/xml
image/svg+xml
font/woff2;
Brotli Compression
Brotli achieves better compression ratios than gzip for text content. Install the module first:
sudo apt install libnginx-mod-brotli # Debian/Ubuntu
brotli on;
brotli_comp_level 4;
brotli_types text/plain text/css application/json application/javascript
text/javascript application/xml image/svg+xml;
Browsers that support Brotli send Accept-Encoding: br. Nginx
automatically falls back to gzip for older clients.
Cache-Control and Expires Headers
Tell browsers and CDNs how long to cache assets:
# Static assets -- cache for 30 days
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2|ttf)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# HTML pages -- revalidate every time
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache";
}
# API responses -- no caching
location /api/ {
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
CDN Integration
Place a CDN (Cloudflare, CloudFront, Fastly) in front of your origin server to cache assets at edge locations worldwide:
- Configure your DNS to point to the CDN instead of your origin.
- Set proper Cache-Control headers on your origin so the CDN knows what to cache.
- Use cache-busting (hashed filenames like
app.a1b2c3.js) so you can set long expiry times without stale content. - Purge the CDN cache when deploying new versions:
# Cloudflare example
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache" -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" --data '{"purge_everything":true}'
Benchmarking
Always measure before and after tuning. Use these tools to generate load:
Apache Bench (ab)
# 1000 requests, 50 concurrent
ab -n 1000 -c 50 https://example.com/
wrk
# 12 threads, 400 connections, 30-second test
wrk -t12 -c400 -d30s https://example.com/
siege
# 50 concurrent users, 60-second test
siege -c50 -t60s https://example.com/
Key metrics to watch: - Requests per second -- your throughput ceiling. - Latency (p50, p95, p99) -- how users experience speed. - Error rate -- 5xx errors under load indicate bottlenecks.
Connection Tuning (OS Level)
The OS network stack can be a bottleneck at scale. Tune these sysctl values:
# /etc/sysctl.d/99-web-tuning.conf
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
Apply with sudo sysctl --system.
Return to the Web Servers hub or continue to Reverse Proxy & Load Balancing and Web Security.