Web Server Performance Tuning: Complete Optimization Guide
In this tutorial, you'll learn about Web Server Performance Tuning: Complete Optimization Guide. We cover key concepts, practical examples, and best practices.
Web server performance tuning optimizes the configuration of your web server to handle more concurrent connections, reduce latency, and improve throughput. A poorly tuned server can handle only a fraction of the traffic that a properly tuned server can, even on identical hardware.
In this tutorial, you will learn to tune NGINX and Apache worker processes, configure keepalive connections for maximum throughput, enable and optimize gzip compression, implement caching strategies, tune kernel parameters for web server workloads, and benchmark your server with tools like Apache Bench (ab) and wrk. DodaTech applies these tuning techniques to serve millions of requests per day across Doda Browser update infrastructure and Durga Antivirus Pro signature distribution servers.
What You'll Learn
By the end of this guide, you will tune NGINX and Apache to handle maximum concurrent connections, configure caching and compression for optimal throughput, tune Linux kernel parameters for web server workloads, and benchmark your server to measure improvements.
Why Performance Tuning Matters
Default web server configurations are conservative. They prioritize compatibility over performance. Tuning can double or triple your server's throughput without any hardware changes. This means lower latency for users, fewer servers needed, and reduced infrastructure costs. Every Web Servers administrator and Linux engineer should know how to tune servers for production workloads.
Performance Tuning Learning Path
flowchart LR
A[Worker Processes] --> B[Keepalive and Connections]
B --> C[Compression and Caching]
C --> D[Kernel Tuning]
D --> E[Benchmarking]
E --> F{You Are Here}
style F fill:#f90,color:#fff
NGINX Worker Process Tuning
NGINX uses an event-driven model. The number of worker processes should match CPU cores:
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_requests 1000;
keepalive_timeout 65;
client_body_buffer_size 128k;
client_max_body_size 10m;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
output_buffers 32 32k;
postpone_output 1460;
open_file_cache max=2000 inactive=20s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors off;
gzip on;
gzip_comp_level 3;
gzip_min_length 1000;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
}
Calculating optimal worker_connections
# Formula: worker_connections = max_clients / worker_processes
# For 10000 max clients on a 4-core server:
# worker_connections = 10000 / 4 = 2500
# Check current system limits
ulimit -n
Expected output
65535
Apache MPM Tuning
Apache offers three Multi-Processing Modules. Choose the right one and tune it:
MPM Event (Recommended for modern workloads)
<IfModule mpm_event_module>
StartServers 3
MinSpareThreads 25
MaxSpareThreads 75
ThreadLimit 64
ThreadsPerChild 25
MaxRequestWorkers 400
MaxConnectionsPerChild 10000
</IfModule>
MPM Prefork (For PHP compatibility, higher memory)
<IfModule mpm_prefork_module>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxRequestWorkers 150
MaxConnectionsPerChild 1000
</IfModule>
Enable the optimal MPM
# Check current MPM
sudo apachectl -M | grep mpm
# Disable prefork, enable event
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo systemctl restart apache2
Expected output
mpm_event_module (static)
Compression and Caching
Gzip tuning (NGINX)
gzip on;
gzip_comp_level 3;
gzip_min_length 1000;
gzip_proxied any;
gzip_vary on;
gzip_types
text/plain
text/css
application/json
application/javascript
text/xml
application/xml
image/svg+xml;
Cache control headers
# Static assets with versioning
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# HTML pages
location / {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
# API responses (no cache)
location /api/ {
add_header Cache-Control "no-store, no-cache, must-revalidate";
proxy_pass http://backend;
}
Proxy caching (NGINX)
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:10m max_size=1g inactive=60m use_temp_path=off;
server {
location / {
proxy_cache app_cache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
proxy_cache_background_update on;
proxy_cache_lock on;
proxy_pass http://backend;
add_header X-Cache-Status $upstream_cache_status;
}
}
Expected cache header output
curl -I https://dodatech.com/assets/style.css
# cache-control: public, immutable
# x-cache-status: HIT
curl -I https://dodatech.com/api/products
# cache-control: no-store, no-cache, must-revalidate
Linux Kernel Tuning for Web Servers
# /etc/sysctl.d/99-web-server.conf
# Increase connection backlog
net.core.somaxconn = 65535
# Increase network buffer sizes
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
# TCP buffer auto-tuning
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
# Enable TCP Fast Open
net.ipv4.tcp_fastopen = 3
# Reuse TIME_WAIT sockets for new connections
net.ipv4.tcp_tw_reuse = 1
# Increase local port range
net.ipv4.ip_local_port_range = 1024 65535
# TCP keepalive
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 8
# Reduce SYN backlog threshold
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_synack_retries = 2
# Enable BBR congestion control
net.ipv4.tcp_congestion_control = bbr
# Increase file descriptor limit
fs.file-max = 2097152
Apply kernel settings:
sudo sysctl -p /etc/sysctl.d/99-web-server.conf
# Increase system-wide file descriptor limit
sudo bash -c 'echo "* soft nofile 65535" >> /etc/security/limits.conf'
sudo bash -c 'echo "* hard nofile 65535" >> /etc/security/limits.conf'
Benchmarking with Apache Bench (ab)
# Install Apache Bench
sudo apt install apache2-utils -y
# Basic benchmark: 1000 requests, 100 concurrent
ab -n 1000 -c 100 https://dodatech.com/
# With keepalive
ab -n 1000 -c 100 -k https://dodatech.com/
Expected benchmark output
Benchmarking dodatech.com (be patient)
Completed 1000 requests
Connection Times (ms)
min mean[+/-sd] median max
Connect: 5 8 2.3 7 24
Processing: 12 18 5.1 16 45
Waiting: 8 12 3.2 11 32
Total: 17 26 6.8 23 69
Requests per second: 3846.15 [#/sec] (mean)
Benchmarking with wrk (Modern Alternative)
# Install wrk
sudo apt install wrk -y
# Basic benchmark
wrk -t4 -c100 -d30s https://dodatech.com/
# With keepalive (default in HTTP/1.1)
wrk -t4 -c200 -d30s --latency https://dodatech.com/api/health
Expected output
Running 30s test @ https://dodatech.com
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 12.34ms 5.67ms 89.00ms 78.23%
Req/Sec 2.15k 123.45 2.89k 72.50%
Latency Distribution
50% 11.00ms
75% 15.00ms
90% 20.00ms
99% 35.00ms
256000 requests in 30.00s, 1.24GB read
Requests/sec: 8533.33
Common Errors
1. Worker_connections Too Low
NGINX refuses connections when all workers are busy. Increase worker_connections or worker_processes. Monitor with curl -s http://localhost/nginx_status.
2. Apache Hitting MaxRequestWorkers
When all Apache workers are busy, new connections queue and eventually time out. Increase MaxRequestWorkers but watch for memory exhaustion (each prefork process uses 20-50 MB).
3. Gzip CPU Overhead Exceeds Benefit
Setting gzip_comp_level above 4 provides minimal extra compression with significant CPU cost. Level 3 is optimal for most content. For API responses, consider level 1.
4. Cache Not Hitting
The cache key does not match or the cache is too small. Check the proxy_cache_key format and increase keys_zone size. Use add_header X-Cache-Status to debug.
5. Kernel Buffer Size Too Large
Setting rmem_max and wmem_max too high can waste memory. Set values based on your workload's typical transfer sizes. 128 MB is safe for most web servers.
Practice Questions
1. What does worker_processes auto do in NGINX?
It automatically sets the number of worker processes to match the number of CPU cores. This ensures optimal CPU utilization without oversubscribing.
2. Why is the event MPM preferred over prefork for Apache? The event MPM uses a thread-based model with an event loop, handling thousands of connections with fewer processes. Prefork creates a process per connection, consuming significantly more memory.
3. What is the purpose of the sendfile directive in NGINX?
sendfile copies data directly from the file system to the network socket, bypassing user-space buffers. This reduces CPU usage and improves throughput for static file serving.
4. Challenge: Benchmark and optimize a web server
Run a benchmark against a web server before and after tuning:
- Measure requests per second with ab (1000 requests, 100 concurrent)
- Apply NGINX optimizations (worker_processes, sendfile, gzip, caching)
- Tune kernel parameters (TCP Fast Open, BBR, buffer sizes)
- Remeasure and calculate the improvement percentage
- Document which change had the biggest impact
Mini Project: Maximum Throughput Tuning
Tune a web server for maximum throughput and benchmark the results:
- Configure NGINX with optimal worker_processes and worker_connections for a 4-core server
- Enable sendfile, tcp_nopush, and tcp_nodelay
- Configure gzip at level 3 for text-based content
- Set up proxy caching with a 1 GB cache zone
- Apply kernel tuning parameters for high-throughput web serving
- Benchmark with wrk (4 threads, 200 connections, 60 seconds)
- Benchmark with ab (10000 requests, 500 concurrent)
- Compare results with the default configuration
# Before tuning: capture baseline
ab -n 1000 -c 100 https://dodatech.com/ > baseline.txt
# After tuning: capture optimized
ab -n 1000 -c 100 https://dodatech.com/ > optimized.txt
# Calculate improvement
grep "Requests per second" baseline.txt
grep "Requests per second" optimized.txt
This tuning methodology is applied to every production server at DodaTech to ensure Doda Browser update distribution and Durga Antivirus Pro signature delivery meet their performance SLAs.
FAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro