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: addproxy_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;
}
}