WEB SERVER โ Web Server Configuration & Administration
Last reviewed: 2026-06-16
Purpose: Comprehensive knowledge base covering web server configuration, administration, and operational patterns for Nginx, Caddy, and Apache.
Table of Contents
- 1. Web Server Overview
- 2. Nginx
- 2.1 Installation
- 2.2 Server Blocks (sites-available Pattern)
- 2.3 Reverse Proxy
- 2.4 Static File Serving & Caching Headers
- 2.5 Rate Limiting
- 2.6 Load Balancing Upstreams
- 2.7 SSL/TLS with Let's Encrypt
- 3. Caddy
- 3.1 Installation
- 3.2 Caddyfile Configuration
- 3.3 Reverse Proxy
- 3.4 Static File Serving & Caching
- 3.5 Rate Limiting
- 3.6 Load Balancing
- 3.7 Automatic SSL/TLS with Let's Encrypt
- 4. Apache
- 4.1 Installation
- 4.2 VirtualHosts (sites-available Pattern)
- 4.3 Reverse Proxy
- 4.4 SSL/TLS with Let's Encrypt
- 5. Comparison Table: Nginx vs Caddy vs Apache
1. Web Server Overview
Web servers accept HTTP(S) requests from clients and serve responses โ static files, proxied application traffic, or dynamically generated content. The three most common open-source web servers in modern DevOps environments are:
| Server | Language | Configuration Style | Key Strength |
|---|---|---|---|
| Nginx | C | Declarative directive blocks | High performance, low resource usage |
| Caddy | Go | Simple Caddyfile (or JSON API) | Automatic HTTPS, simplicity |
| Apache | C | Directive-based (.htaccess + main config) |
Rich module ecosystem, wide compatibility |
2. Nginx
2.1 Installation
# Debian / Ubuntu
sudo apt update && sudo apt install nginx -y
sudo systemctl enable --now nginx
# RHEL / Rocky / Alma / Fedora
sudo dnf install nginx -y
sudo systemctl enable --now nginx
Verify with:
Expected: HTTP/1.1 200 OK
2.2 Server Blocks (sites-available Pattern)
Nginx uses the sites-available / sites-enabled convention to manage virtual hosts cleanly:
/etc/nginx/
โโโ nginx.conf # Main configuration
โโโ sites-available/ # All server block configs (on disk)
โโโ sites-enabled/ # Symlinks to enabled configs (in use)
Creating a server block:
# /etc/nginx/sites-available/example.com
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
Enable it:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t # syntax check
sudo systemctl reload nginx
2.3 Reverse Proxy
Reverse proxying passes client requests to a backend application (e.g., Node.js, Python, Go).
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
WebSocket support requires the Upgrade and Connection headers shown above.
2.4 Static File Serving & Caching Headers
server {
listen 80;
server_name static.example.com;
root /var/www/static;
index index.html;
# Enable gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript image/svg+xml;
gzip_min_length 256;
# Cache static assets aggressively
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff2?|ttf|eot)$ {
expires 365d;
add_header Cache-Control "public, immutable";
access_log off;
}
# HTML files โ short cache
location ~* \.html?$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
# Deny access to hidden files
location ~ /\. {
deny all;
return 404;
}
}
2.5 Rate Limiting
Nginx rate limiting uses the limit_req_zone and limit_req directives.
# Define a shared memory zone (zone name, size, rate)
# In http block (nginx.conf or conf.d/):
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=30r/s;
# Apply in a server or location block:
server {
location /api/ {
limit_req zone=api_limit burst=10 nodelay;
proxy_pass http://backend;
}
location /login {
limit_req zone=login_limit burst=3 nodelay;
proxy_pass http://auth-backend;
}
}
burst=10โ allows up to 10 excess requests queued.nodelayโ process burst immediately instead of throttling.
2.6 Load Balancing Upstreams
Define an upstream group and reference it in proxy_pass:
# In http block:
upstream app_backend {
least_conn; # least connections strategy
# available methods: round-robin (default), least_conn, ip_hash, random
server 10.0.1.10:3000 weight=3;
server 10.0.1.11:3000 weight=2;
server 10.0.1.12:3000 backup; # backup only
}
server {
listen 80;
server_name loadbalanced.example.com;
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Health checks (Nginx Plus) or passive via max_fails and fail_timeout:
upstream app_backend {
server 10.0.1.10:3000 max_fails=3 fail_timeout=30s;
server 10.0.1.11:3000 max_fails=3 fail_timeout=30s;
}
2.7 SSL/TLS with Let's Encrypt
Use Certbot to obtain and renew certificates:
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.com
sudo certbot renew --dry-run # test auto-renewal
Certbot modifies your server block to add SSL directives automatically. The result looks like:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
# Modern SSL hardening
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# HSTS (HTTP Strict Transport Security)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
root /var/www/example.com;
index index.html;
}
# Redirect HTTP โ HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
Auto-renewal is handled by a systemd timer (installed by Certbot):
3. Caddy
Caddy is written in Go and automates HTTPS by default via Let's Encrypt / ZeroSSL. Configuration is minimal.
3.1 Installation
# Official method (one binary)
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
# Or direct download
curl -fsSL https://caddyserver.com/api/download -o /usr/local/bin/caddy
chmod +x /usr/local/bin/caddy
3.2 Caddyfile Configuration
Configuration lives in /etc/caddy/Caddyfile. The syntax is compact:
Caddy automatically provisions and renews TLS certificates โ no Certbot needed.
3.3 Reverse Proxy
With headers and health checks:
app.example.com {
reverse_proxy 127.0.0.1:3000 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
health_interval 10s
health_timeout 2s
health_path /health
}
}
WebSocket is transparent in Caddy โ no special directives required.
3.4 Static File Serving & Caching
static.example.com {
root * /var/www/static
file_server {
hide .git .env .ht*
}
encode gzip
# Cache control via header
header /static/* Cache-Control "public, immutable, max-age=31536000"
header /assets/* Cache-Control "public, immutable, max-age=31536000"
header /*.html Cache-Control "public, must-revalidate, max-age=3600"
}
3.5 Rate Limiting
Caddy v2 supports rate limiting via the rate_limit directive (available in the standard build):
api.example.com {
rate_limit {
zone dynamic {
key {remote_host}
events 30
window 1m
}
}
reverse_proxy 127.0.0.1:8080
}
3.6 Load Balancing
loadbalanced.example.com {
reverse_proxy {
to 10.0.1.10:3000 10.0.1.11:3000 10.0.1.12:3000
lb_policy least_conn
# Available: random, round_robin, least_conn, first, ip_hash, uri_hash
health_uri /health
health_interval 30s
}
}
3.7 Automatic SSL/TLS with Let's Encrypt
Caddy automatically obtains and renews certificates. Configuration is zero โ just specify the domain name:
Caddy listens on both 80 and 443, serves an HTTP-01 challenge on port 80, and upgrades to HTTPS automatically. For staging (to avoid rate limits during testing):
4. Apache
4.1 Installation
# Debian / Ubuntu
sudo apt update && sudo apt install apache2 -y
sudo systemctl enable --now apache2
# RHEL / Rocky / Alma / Fedora
sudo dnf install httpd -y
sudo systemctl enable --now httpd
4.2 VirtualHosts (sites-available Pattern)
Apache uses the same sites-available / sites-enabled convention:
VirtualHost example:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com
<Directory /var/www/example.com>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com_error.log
CustomLog ${APACHE_LOG_DIR}/example.com_access.log combined
</VirtualHost>
Enable:
4.3 Reverse Proxy
Requires mod_proxy and mod_proxy_http:
<VirtualHost *:80>
ServerName app.example.com
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
<Location />
Require all granted
</Location>
</VirtualHost>
With WebSocket:
<VirtualHost *:80>
ServerName ws.example.com
ProxyPass / ws://127.0.0.1:3000/
ProxyPassReverse / ws://127.0.0.1:3000/
</VirtualHost>
4.4 SSL/TLS with Let's Encrypt
sudo apt install certbot python3-certbot-apache -y
sudo certbot --apache -d example.com -d www.example.com
This generates a configuration block like:
<VirtualHost *:443>
ServerName example.com
DocumentRoot /var/www/example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
Redirect HTTP โ HTTPS:
5. Comparison Table: Nginx vs Caddy vs Apache
| Feature | Nginx | Caddy | Apache |
|---|---|---|---|
| Configuration style | Declarative block directives | Caddyfile (simple) or JSON API | XML-like <VirtualHost> blocks |
| Auto HTTPS | No (requires Certbot) | Yes โ built-in, automatic | No (requires Certbot) |
| SSL/TLS management | Manual or Certbot | Fully automatic (Let's Encrypt / ZeroSSL) | Manual or Certbot |
| Configuration reload | nginx -s reload (hot) |
caddy reload (hot) |
apache2ctl graceful (hot) |
| Static file perf | Excellent โ very low memory | Excellent โ Go runtime, fast | Good โ per-process overhead |
| Reverse proxy | Native, highly configurable | Native, simple syntax | Via mod_proxy |
| Rate limiting | Built-in (limit_req_zone) |
Built-in (rate_limit directive) |
Via mod_ratelimit or mod_qos |
| Load balancing | Built-in upstream module | Built-in reverse_proxy with policies |
Via mod_proxy_balancer |
| WebSocket support | Requires explicit Upgrade headers | Transparent โ no extra config | Requires mod_proxy_wstunnel |
| Dynamic modules | Loaded at compile or via dynamic mod | Built-in modules, plugins in Go | a2enmod / LoadModule runtime |
.htaccess (per-dir) |
Not supported (discouraged) | Not supported | Native โ full support |
| Performance under load | Event-driven, excellent | Go goroutines, excellent | Process/thread-based, higher memory |
| Learning curve | Moderate | Low | Moderate to high |
| Best for | High-traffic, microservices, reverse proxy | Simple setup, automatic TLS, developer velocity | Shared hosting, .htaccess-dependent apps |
Quick Reference
When to use which server
| Scenario | Recommended |
|---|---|
| High-traffic static file serving | Nginx or Caddy |
| Reverse proxy / API gateway | Nginx |
| Zero-fuss auto HTTPS | Caddy |
| Shared hosting with user-owned configs | Apache |
| Microservices with many upstreams | Nginx |
| Rapid development / prototyping | Caddy |
Legacy PHP app needing .htaccess overrides |
Apache |
Essential commands
| Action | Nginx | Caddy | Apache |
|---|---|---|---|
| Test config | nginx -t |
caddy validate |
apache2ctl configtest |
| Reload | systemctl reload nginx |
systemctl reload caddy |
systemctl reload apache2 |
| Restart | systemctl restart nginx |
systemctl restart caddy |
systemctl restart apache2 |
| View logs | journalctl -u nginx -f |
journalctl -u caddy -f |
journalctl -u apache2 -f |
| Enable site | ln -s ... sites-enabled/ |
Add to Caddyfile | a2ensite <site> |
| Disable site | rm sites-enabled/<site> |
Remove from Caddyfile | a2dissite <site> |
| SSL renewal test | certbot renew --dry-run |
Automatic (no action needed) | certbot renew --dry-run |
This article replaces the previous placeholder. For deep dives into Nginx-only or Caddy-only topics, see the dedicated CADDY module.