The Complete Nginx Guide: From Install to Production

8 min read
Intermediate Nginx Web Server Reverse Proxy DevOps Guide

Prerequisites

  • A Linux server (Ubuntu/Debian recommended)
  • A domain name (for SSL sections)

Quick Answer: Install: sudo apt install nginx -y. Config: /etc/nginx/sites-available/mysite. Test: nginx -t. Reload: systemctl reload nginx. SSL: certbot --nginx -d yourdomain.com. Reverse proxy: add proxy_pass http://localhost:3000; inside a location block.

Need a VPS? Vultr (free credit), DigitalOcean ($200 free credit), or RackNerd (cheap annual deals).

Nginx (pronounced "engine-x") serves over 30% of all websites. It handles static files, reverse proxying, load balancing, SSL termination, and caching — often all at once. This guide covers everything from first install to production-hardened configuration.


Part 1: Install and Control

# Install
sudo apt update && sudo apt install nginx -y

# Control
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx        # Full restart (drops active connections)
sudo systemctl reload nginx         # Graceful reload (no downtime)
sudo systemctl enable nginx         # Start on boot

# ALWAYS test before reload
sudo nginx -t

After installing, visit http://YOUR_SERVER_IP — you should see the Nginx welcome page.

File Locations

Path Purpose
/etc/nginx/nginx.conf Main configuration
/etc/nginx/sites-available/ Your site configs (inactive)
/etc/nginx/sites-enabled/ Active configs (symlinks)
/etc/nginx/snippets/ Reusable config fragments
/var/log/nginx/access.log Access log
/var/log/nginx/error.log Error log
/var/www/html/ Default web root

Enable / Disable Sites

# Enable
sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/

# Disable
sudo rm /etc/nginx/sites-enabled/mysite

# Remove default site (recommended)
sudo rm /etc/nginx/sites-enabled/default

Part 2: Server Blocks (Virtual Hosts)

Server blocks let you host multiple websites on one server, each with its own domain.

Static Website

Create /etc/nginx/sites-available/example.com:

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    # Cache static assets
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2|svg)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
}
# Create web root and enable
sudo mkdir -p /var/www/example.com
echo "<h1>Hello World</h1>" | sudo tee /var/www/example.com/index.html
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Multiple Sites on One Server

# Site 1: example.com
server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com;
    location / { try_files $uri $uri/ =404; }
}

# Site 2: blog.example.com
server {
    listen 80;
    server_name blog.example.com;
    root /var/www/blog;
    location / { try_files $uri $uri/ =404; }
}

# Site 3: api.example.com → Node.js app
server {
    listen 80;
    server_name api.example.com;
    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Part 3: Reverse Proxy

Nginx sits in front of your application and handles SSL, caching, and routing.

Basic Reverse Proxy

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://localhost:3000;
        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;
    }
}

Frontend + API on Same Domain

server {
    listen 80;
    server_name example.com;

    # Frontend (static files)
    location / {
        root /var/www/frontend;
        try_files $uri $uri/ /index.html;    # SPA support
    }

    # API proxy
    location /api/ {
        proxy_pass http://localhost:3000/;    # Trailing slash strips /api prefix
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

WebSocket Proxy

Required for Socket.IO, real-time apps, chat, proxy panels:

location /ws {
    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 86400;
}

Timeouts for Slow Backends

location / {
    proxy_pass http://localhost:3000;
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 300s;        # Increase for slow APIs
    client_max_body_size 100m;      # Max upload size
}

Full guide: Nginx Reverse Proxy Guide


Part 4: SSL/TLS (HTTPS)

Let's Encrypt with Certbot (Free SSL)

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.com

Certbot automatically:

  • Gets a certificate
  • Configures Nginx for HTTPS
  • Sets up auto-renewal
# Test renewal
sudo certbot renew --dry-run

# Check certificate
sudo certbot certificates

HTTP to HTTPS Redirect

Certbot adds this automatically, but if you need it manually:

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

Strong SSL Configuration

server {
    listen 443 ssl;
    http2 on;

> **Note:** `http2 on;` requires Nginx 1.25.1+. On older versions (e.g., Ubuntu 22.04 ships 1.18), use `listen 443 ssl http2;` on the listen line instead.

    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Strong protocols and ciphers
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    # Session caching (performance)
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
}

Full guide: SSL/TLS Explained


Part 5: Load Balancing

Distribute traffic across multiple backend servers:

upstream backend {
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Load Balancing Methods

# Round robin (default) — each server gets requests in turn
upstream backend {
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
}

# Least connections — sends to server with fewest active connections
upstream backend {
    least_conn;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
}

# IP hash — same client always goes to same server (sticky sessions)
upstream backend {
    ip_hash;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
}

# Weighted — server with weight 3 gets 3x the traffic
upstream backend {
    server 127.0.0.1:3001 weight=3;
    server 127.0.0.1:3002 weight=1;
}

# Health checks — mark server as down if it fails
upstream backend {
    server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3002 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3003 backup;    # Only used when others are down
}

Part 6: Caching

Static File Caching (Browser)

Tell browsers to cache static files:

location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2|svg|webp)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
    access_log off;
}

Proxy Cache (Server-Side)

Cache backend responses on the Nginx server:

# Define cache zone (in http block of nginx.conf)
proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=app_cache:10m max_size=1g inactive=60m;

server {
    listen 80;

    location / {
        proxy_pass http://localhost:3000;
        proxy_cache app_cache;
        proxy_cache_valid 200 10m;        # Cache 200 responses for 10 min
        proxy_cache_valid 404 1m;         # Cache 404 for 1 min
        add_header X-Cache-Status $upstream_cache_status;
    }

    # Don't cache API or dynamic routes
    location /api/ {
        proxy_pass http://localhost:3000;
        proxy_cache off;
    }
}

Gzip Compression

Compress text responses (saves bandwidth):

# In http block of nginx.conf
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript image/svg+xml;

Part 7: Security

Security Headers

Add to every server block:

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "0" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Rate Limiting

# Define limit zone (in http block)
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

server {
    # Apply to API endpoints
    location /api/ {
        limit_req zone=api burst=20 nodelay;
        proxy_pass http://localhost:3000;
    }
}

Block Bad Bots

# Block by user agent
if ($http_user_agent ~* (SemrushBot|AhrefsBot|MJ12bot|DotBot)) {
    return 403;
}

Block IPs

# Block specific IPs
deny 1.2.3.4;
deny 10.0.0.0/8;
allow all;

# Or: Allow only specific IPs
allow 10.0.0.5;
deny all;

Password Protection

sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd admin
location /admin {
    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

Hide Nginx Version

# In http block
server_tokens off;

Part 8: Performance Tuning

Worker Configuration

In /etc/nginx/nginx.conf:

# Main context
worker_processes auto;              # Match CPU cores

# Events block
events {
    worker_connections 1024;        # Per worker
    multi_accept on;
}

HTTP Optimizations

# In http block
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 100m;

# Buffer sizes (tune for your workload)
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;

Connection Pooling (Upstream)

upstream backend {
    server 127.0.0.1:3000;
    keepalive 32;                   # Keep connections alive to backend
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";  # Required for keepalive
    }
}

Part 9: Logging

Custom Log Format

# In http block
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" '
                '"$http_user_agent" $request_time';

Per-Site Logs

server {
    access_log /var/log/nginx/example-access.log main;
    error_log /var/log/nginx/example-error.log warn;
}

Disable Logging for Static Files

location ~* \.(jpg|css|js|ico|woff2)$ {
    access_log off;
}

Disable Logging Entirely

access_log off;
error_log /dev/null;

Part 10: Custom Error Pages

error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;

location = /404.html {
    root /var/www/errors;
    internal;
}

location = /50x.html {
    root /var/www/errors;
    internal;
}

Part 11: Redirects

# HTTP to HTTPS
return 301 https://$host$request_uri;

# Non-www to www
server {
    listen 80;
    server_name example.com;
    return 301 https://www.example.com$request_uri;
}

# Redirect specific path
location /old-page {
    return 301 /new-page;
}

# Regex redirect
rewrite ^/blog/(.*)$ /articles/$1 permanent;

Part 12: Troubleshooting

Test Config

sudo nginx -t
# Shows syntax errors with line numbers

Common Errors

Error Fix
502 Bad Gateway Backend not running. Check: `ss -tlnp \ grep PORT`
504 Gateway Timeout Backend too slow. Increase proxy_read_timeout
403 Forbidden Permission issue. Check file ownership: chown -R www-data:www-data /var/www/
413 Request Entity Too Large Increase client_max_body_size
Address already in use Another process on port 80. Check: `ss -tlnp \ grep :80`
Config test fails Check error message for line number and fix

Debug Commands

# Test config
sudo nginx -t

# Full config dump
sudo nginx -T

# Check what's listening
sudo ss -tlnp | grep :80
sudo ss -tlnp | grep :443

# Watch errors
sudo tail -f /var/log/nginx/error.log

# Watch access
sudo tail -f /var/log/nginx/access.log

# Check Nginx processes
ps aux | grep nginx

Nginx Config for Common Scenarios

Proxy Panel (3X-UI) Behind Nginx

server {
    listen 80;
    server_name panel.example.com;

    location / {
        proxy_pass http://localhost:2053;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

WordPress

server {
    listen 80;
    server_name blog.example.com;
    root /var/www/wordpress;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 30d;
        access_log off;
    }
}

Related Guides