NGINX Reverse Proxy: Complete Setup Guide
An NGINX reverse proxy sits between clients and your backend servers, handling incoming requests and routing them to the appropriate service — adding load balancing, SSL termination, caching, and security that each individual backend doesn’t need to implement separately.
What You’ll Learn
- What a reverse proxy is and why you need one
- Basic NGINX reverse proxy configuration for a Node.js or Python app
- Load balancing across multiple backend servers
- SSL termination, caching, WebSocket proxying, security headers, and rate limiting
Why a Reverse Proxy Matters
A reverse proxy decouples your backend from the outside world. It terminates SSL (so backends don’t need certificates), balances traffic across instances, caches responses to reduce load, protects against slow clients, and adds a single point for security policies. Every production deployment should have one.
Doda Browser uses NGINX as a reverse proxy to route API traffic, serve static assets, and rate-limit public endpoints.
Learning Path
flowchart LR
A[Web Server Basics] --> B[Reverse Proxy<br/>You are here]
B --> C[Load Balancing]
B --> D[SSL Termination]
C --> E[Production Deployment]
D --> E
style B fill:#f90,color:#fff
Basic Reverse Proxy Configuration
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name myapp.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;
}
}Enable and test:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxExpected nginx -t output:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successfulLoad Balancing
Distribute traffic across multiple backend servers:
upstream backend_servers {
least_conn; # Send to server with fewest connections
server 10.0.1.1:3000 weight=3;
server 10.0.1.2:3000 weight=2;
server 10.0.1.3:3000 backup; # Only used if others are down
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://backend_servers;
proxy_next_upstream error timeout http_500;
}
}Load balancing methods:
- Round-robin (default): Distributes evenly
- least_conn: Sends to the least busy server
- ip_hash: Same client goes to same server (session persistence)
SSL Termination
Offload SSL encryption from your backends:
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://localhost:3000;
proxy_set_header X-Forwarded-Proto https;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}Caching
Cache responses to reduce backend load:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mycache:10m max_size=1g;
server {
location /api/ {
proxy_cache mycache;
proxy_cache_valid 200 5m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://backend;
}
location /static/ {
proxy_cache mycache;
proxy_cache_valid 200 24h;
proxy_pass http://static-server;
}
}Check cache effectiveness with the X-Cache-Status header: HIT means served from cache, MISS means fetched from backend.
WebSocket Proxying
Proxying WebSocket connections requires connection upgrade headers:
location /ws/ {
proxy_pass http://websocket-backend:8080;
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; # 24 hours — WebSockets are long-lived
}Security Headers
Add security headers to protect against common attacks:
server {
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "0" always; # Deprecated but harmless
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'" always;
}Rate Limiting
Protect your backends from abuse:
# Define a rate limit zone: $10 per request, burst up to 20
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://backend;
}
location /auth/ {
limit_req zone=api burst=5 nodelay; # Stricter for auth
proxy_pass http://auth-backend;
}
}When rate limit is exceeded, NGINX returns 503 Service Unavailable. Monitor this in your logs.
Common Errors
1. Missing X-Forwarded Headers
Backend servers see NGINX’s IP instead of the real client IP. Always forward X-Real-IP and X-Forwarded-For.
2. Incorrect SSL Certificate Path
NGINX won’t start if certificate paths are wrong. Always test with sudo nginx -t.
3. Not Setting proxy_http_version 1.1
NGINX defaults to HTTP/1.0 for proxied requests. WebSockets and keep-alive require HTTP/1.1.
4. Cache Poisoning
Improper cache configuration can serve stale or unauthorized data. Use proxy_cache_bypass and proxy_no_cache for authenticated content.
5. Timeouts on Slow Backends
Default proxy_read_timeout is 60 seconds. Increase for long-running requests.
6. Rate Limiting Too Aggressively
Setting 10r/s when your backend handles 100r/s causes unnecessary 503s. Set limits based on actual capacity.
Practice Questions
What does an NGINX reverse proxy do? Sits between clients and backends, routing requests, terminating SSL, balancing load, caching, and adding security.
What header must be forwarded so backends see the real client IP?
X-Real-IPorX-Forwarded-For.What’s the difference between
least_connandip_hashload balancing?least_connsends to the least busy server.ip_hashsends the same client to the same server for session persistence.What does
proxy_cache_valid 200 5mmean? Cache successful (200) responses for 5 minutes.How do you check NGINX configuration for syntax errors? Run
sudo nginx -t.
Challenge: Set up an NGINX reverse proxy in front of a simple Node.js or Python app. Configure SSL with a self-signed certificate, enable caching for static files, set up rate limiting at 5 requests per second, and add all security headers. Test each feature.
FAQ
What’s Next
| Tutorial | What You’ll Learn |
|---|---|
| NGINX Web Server Guide | Serving static files with NGINX |
| SSL/TLS Certificates with Let's Encrypt | Setting up free SSL certs for your proxy |
| Docker and NGINX | Running NGINX as a Docker container |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-19.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro