Nginx Overview
Nginx (pronounced “engine-x”) is a web server and reverse proxy server known for its high concurrent connection handling. While Apache creates a process/thread per request, Nginx uses an event-driven asynchronous architecture that handles tens of thousands of simultaneous connections with minimal memory.
Its primary use cases include:
| Use Case | Description |
|---|---|
| Static file serving | Directly serves HTML, CSS, JS, images, etc. |
| Reverse proxy | Relays requests in front of backend servers (Node.js, Django, etc.) |
| Load balancer | Distributes traffic across multiple backend servers |
| SSL termination | Handles HTTPS encryption/decryption at the Nginx level |
| Caching | Caches static resources and proxy responses for improved performance |
Installation and Basic Configuration
Installation instructions for Ubuntu/Debian.
# Install Nginx
sudo apt update && sudo apt install -y nginx
# Start service and enable auto-start on boot
sudo systemctl enable --now nginx
# Verify installation
nginx -v
# nginx version: nginx/1.24.0
# Test configuration file syntax
sudo nginx -t
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
# Apply configuration (zero-downtime reload)
sudo nginx -s reload
Nginx configuration file structure:
| Path | Role |
|---|---|
/etc/nginx/nginx.conf | Global settings (worker processes, events, etc.) |
/etc/nginx/sites-available/ | Per-site config files (inactive) |
/etc/nginx/sites-enabled/ | Activated sites (symlinks) |
/etc/nginx/conf.d/ | Additional config files (*.conf auto-loaded) |
Reverse Proxy Configuration
Configuration for placing backend servers like Node.js (port 3000) or Django (port 8000) behind Nginx.
# /etc/nginx/sites-available/my-app.conf
# Reverse proxy configuration for a Node.js app
# Define upstream servers (enables load balancing)
upstream backend {
# Round-robin distribution (default)
server 127.0.0.1:3000;
server 127.0.0.1:3001;
# Number of keep-alive connections
keepalive 32;
}
server {
listen 80;
server_name example.com www.example.com;
# Access and error log paths
access_log /var/log/nginx/my-app-access.log;
error_log /var/log/nginx/my-app-error.log;
# Maximum client request size (for file uploads, etc.)
client_max_body_size 50M;
# Serve static files directly with Nginx
location /static/ {
alias /opt/my-app/public/;
# 30-day cache
expires 30d;
add_header Cache-Control "public, immutable";
}
# Proxy API and dynamic requests to backend
location / {
proxy_pass http://backend;
# Forward original client information
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 timeout settings
proxy_connect_timeout 60s;
proxy_read_timeout 120s;
proxy_send_timeout 60s;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Activate the site and apply.
# Activate site (create symlink)
sudo ln -s /etc/nginx/sites-available/my-app.conf /etc/nginx/sites-enabled/
# Disable default site (if needed)
sudo rm /etc/nginx/sites-enabled/default
# Test config then apply
sudo nginx -t && sudo nginx -s reload
proxy_set_header X-Real-IP is essential for the backend to see the actual client IP. Without this header, all requests will show 127.0.0.1 as the IP.
Let’s Encrypt SSL Setup
Obtain a free SSL certificate with Certbot and apply it to Nginx.
# Install Certbot
sudo apt install -y certbot python3-certbot-nginx
# Issue SSL certificate + auto-configure Nginx
sudo certbot --nginx -d example.com -d www.example.com
# Enter email → agree to terms → choose HTTP→HTTPS redirect
# Test automatic certificate renewal
sudo certbot renew --dry-run
# Check certificate expiry date
sudo certbot certificates
# Certificate Name: example.com
# Expiry Date: 2026-05-19 (VALID: 89 days)
Certbot automatically modifies the Nginx configuration, but for manual setup, use the following format.
# /etc/nginx/sites-available/my-app.conf
# SSL configuration
# HTTP → HTTPS redirect
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# SSL certificate paths
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# SSL security settings (Mozilla Modern recommended)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# HSTS (6 months)
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains" always;
# OCSP Stapling (improves certificate validation speed)
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
location / {
proxy_pass http://backend;
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;
}
}
Static File Caching and gzip
Improve response times with static resource caching and gzip compression.
# /etc/nginx/conf.d/optimization.conf
# Performance optimization settings
# gzip compression settings
gzip on;
gzip_vary on;
gzip_proxied any;
# Compression level (1-9, higher = more CPU usage)
gzip_comp_level 6;
# Don't compress files smaller than 1KB
gzip_min_length 1024;
# MIME types to compress
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml;
# File caching (by extension)
server {
# ... existing configuration ...
# Images, fonts — 1 year cache
location ~* \.(jpg|jpeg|png|gif|ico|svg|woff2|woff|ttf)$ {
expires 1y;
add_header Cache-Control "public, immutable";
# Disable access log (when static file logging is unnecessary)
access_log off;
}
# CSS, JS — 30 day cache
location ~* \.(css|js)$ {
expires 30d;
add_header Cache-Control "public";
}
# HTML — no caching (always serve latest version)
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
}
Useful Configuration Patterns
Rate Limiting
# Global setting (in the http block of /etc/nginx/nginx.conf)
# Limit to 10 requests per second per IP (zone memory 10MB)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
location /api/ {
# burst=20: allow up to 20 burst requests (queued)
# nodelay: process immediately without queuing
limit_req zone=api_limit burst=20 nodelay;
# Return 429 when limit is exceeded
limit_req_status 429;
proxy_pass http://backend;
}
}
Security Headers
# Security-related response headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
Practical Tips
- Always test with
nginx -tbefore changing configuration. If there’s a syntax error,reloadwill keep Nginx running with the existing config, butrestartmay cause a service outage. - Zero-downtime application:
nginx -s reloadapplies new configuration while maintaining existing connections. Unlikerestart, there is no downtime. - Log rotation: Log rotation is automatically configured via
/etc/logrotate.d/nginx. Adjust the frequency for high-traffic services. - Upstream health checks: In open-source Nginx, simple health checks can be implemented with
max_fails=3 fail_timeout=30soptions. Nginx Plus supports active health checks. - Debugging: Enabling debug-level logging with
error_log /var/log/nginx/debug.log debug;is useful for diagnosing reverse proxy issues. Be sure to disable it in production.