Quick Answer: Create
/etc/nginx/sites-available/myappwithproxy_pass http://localhost:3000;inside alocation /block. Symlink to sites-enabled. Runnginx -t && systemctl reload nginx. Add SSL withcertbot --nginx -d yourdomain.com.
What is a Reverse Proxy?
Without reverse proxy:
User → :3000 → Your App
With reverse proxy:
User → :443 (Nginx) → :3000 (Your App)
Nginx sits in front of your application and handles:
- SSL/TLS termination — HTTPS without your app dealing with certificates
- Port routing — serve on port 80/443 instead of 3000
- Multiple apps on one server — route by domain or path
- Static file serving — Nginx serves static files much faster than your app
- Load balancing — distribute traffic across multiple backend instances
- Security — hide your app behind Nginx, add headers, rate limit
Basic Reverse Proxy
Step 1: Create the Config
Create /etc/nginx/sites-available/myapp:
server {
listen 80;
server_name yourdomain.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;
}
}
Step 2: Enable the Site
ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default # Remove default site
nginx -t # Test config
systemctl reload nginx # Apply
Step 3: Add SSL (Let's Encrypt)
apt install certbot python3-certbot-nginx -y
certbot --nginx -d yourdomain.com
Certbot automatically modifies your Nginx config to add SSL. Auto-renewal is set up automatically.
Your site is now live at https://yourdomain.com proxying to your app on port 3000.
Common Backend Configurations
Node.js / Express
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
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;
}
}
Python (Gunicorn / Uvicorn)
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
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;
}
# Serve static files directly (faster than Python)
location /static/ {
alias /opt/myapp/static/;
expires 30d;
}
}
Docker Container
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://localhost:8080; # Docker container port
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 (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/; # Note trailing slash
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
WebSocket Support
Required for Socket.IO, real-time chat, live updates:
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_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400; # Keep connection open for 24h
}
Multiple Apps on One Server
By Subdomain
# api.example.com → Node.js on 3000
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;
}
}
# dashboard.example.com → Python on 8000
server {
listen 80;
server_name dashboard.example.com;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
By Path
server {
listen 80;
server_name example.com;
location /app1/ {
proxy_pass http://localhost:3001/;
}
location /app2/ {
proxy_pass http://localhost:3002/;
}
}
Load Balancing
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:
upstream backend {
# Round robin (default)
server 127.0.0.1:3001;
server 127.0.0.1:3002;
# Least connections
least_conn;
# IP hash (sticky sessions)
ip_hash;
# Weighted
server 127.0.0.1:3001 weight=3; # Gets 3x traffic
server 127.0.0.1:3002 weight=1;
}
Security Headers
Add these to every reverse proxy config:
# Inside server block
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" 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 the http block (not inside a server block))
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://localhost:3000/;
}
}
Caching
# Define cache (in http block)
proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=app_cache:10m max_size=1g inactive=60m;
server {
location / {
proxy_pass http://localhost:3000;
proxy_cache app_cache;
proxy_cache_valid 200 10m;
proxy_cache_valid 404 1m;
add_header X-Cache-Status $upstream_cache_status;
}
# Don't cache API responses
location /api/ {
proxy_pass http://localhost:3000;
proxy_cache off;
}
}
Timeouts
location / {
proxy_pass http://localhost:3000;
proxy_connect_timeout 60s; # Time to connect to backend
proxy_send_timeout 60s; # Time to send request to backend
proxy_read_timeout 300s; # Time to wait for response (increase for slow APIs)
# For file uploads
client_max_body_size 100m;
}
Troubleshooting
| Problem | Fix | |
|---|---|---|
| 502 Bad Gateway | Backend is not running. Check: `ss -tlnp \ | grep 3000` |
| 504 Gateway Timeout | Backend too slow. Increase proxy_read_timeout |
|
| Connection refused | Wrong port or backend not listening on 127.0.0.1 | |
| Real IP not showing | Add proxy_set_header X-Real-IP $remote_addr; |
|
| CORS errors | Add CORS headers in Nginx or backend | |
| WebSocket not working | Missing Upgrade and Connection headers |
|
| SSL not working | Run certbot --nginx -d yourdomain.com |
# Debug checklist
nginx -t # Test config syntax
systemctl status nginx # Is Nginx running?
ss -tlnp | grep :3000 # Is backend running?
curl http://localhost:3000 # Can Nginx reach backend?
tail -f /var/log/nginx/error.log # Check errors