
Beginner's Guide to Creating a Reverse Proxy with Nginx
Imagine you're running multiple applications on your server - a Node.js API, a Python Django app, and a WordPress site. Managing different ports, SSL certificates, and security settings for each one sounds like a nightmare, right?
This is exactly where reverse proxies shine. They act like a smart traffic controller for your server, directing incoming requests to the right application while handling common tasks like SSL termination, load balancing, and caching.
I used to think reverse proxies were complex enterprise tools, but after setting one up for my first project, I realized they're incredibly powerful and surprisingly straightforward to configure. They've become an essential part of every web architecture I build.
In this guide, I'll walk you through creating your first reverse proxy with Nginx. We'll start with the basics and gradually build up to more advanced configurations. Let's make your server more organized and efficient! 🚀
What Exactly is a Reverse Proxy?
Before diving in, let's understand what we're building:
A reverse proxy sits in front of your web applications and forwards client requests to them. Think of it like a receptionist for your office building - visitors come to the main entrance (your reverse proxy), and the receptionist directs them to the right office (your application).
Key benefits:
- Single point of entry: One domain, one SSL certificate
- Load balancing: Distribute traffic across multiple servers
- SSL termination: Handle HTTPS in one place
- Caching: Speed up responses with built-in caching
- Security: Hide internal application details
When Should You Use a Reverse Proxy?
Reverse proxies are perfect for:
- Running multiple applications on different ports
- Microservices architecture
- Load balancing across multiple servers
- Adding SSL/TLS to applications that don't support it natively
- Caching static content to reduce server load
- Implementing security rules and rate limiting
Prerequisites: Getting Started
Before we begin, make sure you have:
- Ubuntu Server (18.04, 20.04, or 22.04)
- Nginx installed:
sudo apt install nginx -y
- SSH access with sudo privileges
- At least one web application running on a different port (we'll create test examples)
Step 1: Basic Reverse Proxy Configuration
Let's start with the simplest reverse proxy setup. Imagine you have a Node.js application running on port 3000.
Create a Basic Configuration
sudo nano /etc/nginx/sites-available/my-reverse-proxy
Add Basic Proxy Configuration
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;
}
}
Enable Your Configuration
sudo ln -s /etc/nginx/sites-available/my-reverse-proxy /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
What's happening here?
proxy_pass
: Tells Nginx where to forward requestsproxy_set_header
: Passes important information about the original request to your application
Step 2: Testing Your Reverse Proxy
Let's create a simple test application to verify our reverse proxy works.
Create a Simple Test Server
Here's a quick Node.js test server (save as test-server.js
):
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.json({
message: 'Hello from the backend server!',
timestamp: new Date().toISOString(),
headers: req.headers
});
});
app.listen(port, () => {
console.log(`Test server running on port ${port}`);
});
Or a simple Python server (save as test-server.py
):
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
from datetime import datetime
class SimpleHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
response = {
'message': 'Hello from the Python backend!',
'timestamp': datetime.now().isoformat(),
'path': self.path
}
self.wfile.write(json.dumps(response).encode())
if __name__ == '__main__':
server = HTTPServer(('localhost', 3000), SimpleHandler)
print('Test server running on port 3000')
server.serve_forever()
Run Your Test Server
# For Node.js
node test-server.js
# For Python
python3 test-server.py
Now visit http://yourdomain.com
in your browser. You should see the JSON response from your test server!
Step 3: Advanced Configuration with SSL
Let's make our reverse proxy production-ready with HTTPS and better headers.
Install SSL Certificate
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com
Enhanced Configuration with SSL
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Proxy Configuration
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;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Buffer settings
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
}
Step 4: Multiple Applications on One Domain
One of the most powerful features of reverse proxies is serving multiple applications from different paths.
Path-Based Routing
server {
listen 443 ssl http2;
server_name yourdomain.com;
# SSL configuration (same as above)
# API routes to Node.js application
location /api/ {
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;
}
# Admin panel to Python application
location /admin/ {
proxy_pass http://localhost:8080/;
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 files served directly
location /static/ {
alias /var/www/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Everything else to main application
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 5: Load Balancing Configuration
Running multiple instances of your application? Let's set up load balancing.
Upstream Configuration
# Define upstream servers
upstream my_app_servers {
server localhost:3000;
server localhost:3001;
server localhost:3002;
# Load balancing method (default: round-robin)
# Other options: least_conn, ip_hash
}
server {
listen 443 ssl http2;
server_name yourdomain.com;
# SSL configuration
location / {
proxy_pass http://my_app_servers;
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;
# Health check settings
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 10s;
}
}
Step 6: Caching Configuration
Add caching to reduce load on your backend servers.
Enable Proxy Caching
Add this to your http
block in /etc/nginx/nginx.conf
:
# Proxy cache settings
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g
inactive=60m use_temp_path=off;
Apply Caching to Your Location
location / {
proxy_cache my_cache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_key "$scheme$request_method$host$request_uri";
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;
# Cache bypass for logged-in users
proxy_cache_bypass $cookie_nocache $arg_nocache;
proxy_no_cache $cookie_nocache $arg_nocache;
}
Common Use Cases and Examples
WordPress with Node.js API
server {
listen 443 ssl http2;
server_name yourdomain.com;
# WordPress at root
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
}
# API routes to Node.js
location /api/ {
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;
}
}
React Frontend with Express Backend
server {
listen 443 ssl http2;
server_name yourdomain.com;
# Static React build files
location / {
root /var/www/react-app/build;
try_files $uri $uri/ /index.html;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# API routes to Express backend
location /api/ {
proxy_pass http://localhost:3001/;
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;
}
}
Troubleshooting Common Issues
"502 Bad Gateway" Error
Cause: Backend server isn't responding or proxy configuration is wrong.
Solution:
# Check if backend is running
curl http://localhost:3000
# Check Nginx error logs
sudo tail -f /var/log/nginx/error.log
# Test proxy configuration
sudo nginx -t
CORS Issues
Cause: Backend application expecting direct requests, not proxied ones.
Solution: Add CORS headers in your backend or use Nginx:
location /api/ {
proxy_pass http://localhost:3000/;
# Add CORS headers
add_header 'Access-Control-Allow-Origin' '$http_origin' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
}
WebSocket Support
If you need to proxy WebSocket connections:
location /socket.io/ {
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_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Best Practices and Security Tips
- Always use HTTPS in production environments
- Hide server versions:
server_tokens off;
in nginx.conf - Implement rate limiting to prevent abuse
- Monitor logs regularly for unusual activity
- Use health checks for load-balanced setups
- Keep Nginx updated for security patches
- Test configurations before applying them with
nginx -t
Performance Optimization Tips
Enable HTTP/2
listen 443 ssl http2;
Gzip Compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
Connection Pooling
upstream backend {
server localhost:3000;
keepalive 32;
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
Final Thoughts
Reverse proxies are incredibly powerful tools that can dramatically improve your web architecture. They provide a clean separation between your public-facing server and your backend applications, making your infrastructure more organized, secure, and scalable.
What we've covered today is just the beginning. As you get more comfortable with reverse proxies, you can explore advanced features like:
- Advanced load balancing algorithms
- Dynamic configuration with API backends
- Advanced caching strategies
- Security rule engines
- Real-time monitoring and metrics
Start simple, test thoroughly, and gradually add complexity as you understand how everything works together. Your future self will thank you for the clean, maintainable architecture you've built.
Happy proxying! 🚀 Your applications have never been more organized.