Nginx Configuration Guide — Reverse Proxy, SSL, and Caching

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 CaseDescription
Static file servingDirectly serves HTML, CSS, JS, images, etc.
Reverse proxyRelays requests in front of backend servers (Node.js, Django, etc.)
Load balancerDistributes traffic across multiple backend servers
SSL terminationHandles HTTPS encryption/decryption at the Nginx level
CachingCaches 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:

PathRole
/etc/nginx/nginx.confGlobal 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 -t before changing configuration. If there’s a syntax error, reload will keep Nginx running with the existing config, but restart may cause a service outage.
  • Zero-downtime application: nginx -s reload applies new configuration while maintaining existing connections. Unlike restart, 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=30s options. 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.

Was this article helpful?