Chuyển đến nội dung chính

Lesson 4: Reverse Proxy

A lesson on Reverse Proxy in Nginx — concepts, proxy_pass configuration, proxy headers, upstream servers, and health checks. Guide to setting up Nginx as a reverse proxy for backend applications like Node.js, Python, and PHP. Includes best practices and troubleshooting.

🔒 DevSecOps — Lesson 4 Lesson 4: Reverse Proxy

Nginx from Basics to Advanced

Part 2: Reverse Proxy & Load Balancing

xdev.asia

1. Reverse Proxy Concepts

1.1. What is a Reverse Proxy?

A reverse proxy is a server that sits between clients and backend servers. It receives requests from clients, forwards them to backend servers, and returns the response to the client.

The difference:

Forward Proxy (Client-side):
Client → Forward Proxy → Internet → Server
(Hides the client)

Reverse Proxy (Server-side): Client → Reverse Proxy → Backend Server (Hides the server)

Illustration:

┌─────────┐         ┌──────────────┐         ┌──────────────┐
│         │         │              │         │              │
│ Client  │────────▶│    Nginx     │────────▶│   Backend    │
│         │         │ Reverse Proxy│         │   Server     │
│         │◀────────│              │◀────────│              │
└─────────┘         └──────────────┘         └──────────────┘

1.2. Why Use a Reverse Proxy?

1. Load Balancing:

  • Distribute traffic across multiple backend servers
  • Increase throughput and reliability

2. SSL/TLS Termination:

  • Nginx handles SSL encryption/decryption
  • Backend servers don't need to worry about HTTPS

3. Caching:

  • Cache static content and API responses
  • Reduce load on backend servers

4. Security:

  • Hide backend server infrastructure
  • Protection layer (rate limiting, firewall)
  • Centralized authentication

5. Compression:

  • Gzip compression for responses
  • Reduce bandwidth usage

6. Static File Serving:

  • Nginx serves static files directly
  • Backend only handles dynamic content

7. Multiple Backends:

  • Route requests to different applications
  • Microservices architecture

1.3. Common Use Cases

1. Single Page Applications (SPA):
/          → React/Vue/Angular app
/api/*     → Backend API server

  1. Microservices: /users/* → User service /orders/* → Order service /payments/* → Payment service

  2. Multiple Applications: site.com → Main website blog.site.com → WordPress blog api.site.com → API server

  3. Legacy + New: /old/* → Legacy PHP application /new/* → New Node.js application


2. Basic proxy_pass Configuration

2.1. proxy_pass Syntax

location /path/ {
proxy_pass http://backend_server;
}

Simple example:

server {
listen 80;
server_name example.com;

location / { # Forward all requests to backend proxy_pass http://localhost:3000; } }

2.2. proxy_pass with URI

Method 1: No trailing slash

location /api {
proxy_pass http://localhost:3000;
}

Request: /api/users

Proxied to: http://localhost:3000/api/users

(keeps /api prefix)

Method 2: With trailing slash

location /api/ {
proxy_pass http://localhost:3000/;
}

Request: /api/users

Proxied to: http://localhost:3000/users

(strips /api prefix)

Method 3: With specific path

location /api/ {
proxy_pass http://localhost:3000/v1/;
}

Request: /api/users

Proxied to: http://localhost:3000/v1/users

(replaces /api with /v1)

Detailed example:

server {
listen 80;
server_name example.com;

# Proxy root
location / {
    proxy_pass http://localhost:3000;
}

# Proxy API (strip /api prefix)
location /api/ {
    proxy_pass http://localhost:4000/;
}

# Proxy admin (keep /admin prefix)
location /admin {
    proxy_pass http://localhost:5000;
}

# Proxy with exact match
location = /health {
    proxy_pass http://localhost:3000/healthcheck;
}

}

2.3. Proxy Multiple Backends

server {
listen 80;
server_name example.com;

# Frontend SPA
location / {
    root /var/www/html;
    try_files $uri $uri/ /index.html;
}

# API Backend
location /api/ {
    proxy_pass http://localhost:3000/;
}

# Auth Service
location /auth/ {
    proxy_pass http://localhost:4000/;
}

# WebSocket Server
location /ws/ {
    proxy_pass http://localhost:5000/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

# Static Assets (CDN)
location /static/ {
    proxy_pass http://cdn.example.com/;
}

}

2.4. Proxy with Variables

server {
listen 80;
server_name example.com;

# Proxy based on subdomain
location / {
    proxy_pass http://$http_host$request_uri;
}

# Proxy with custom variable
set $backend "localhost:3000";
location /api/ {
    proxy_pass http://$backend/;
}

# Conditional proxy
location /dynamic/ {
    if ($arg_version = "v2") {
        proxy_pass http://localhost:4000/;
    }
    proxy_pass http://localhost:3000/;
}

}

2.5. Proxy Timeouts

location /api/ {
proxy_pass http://localhost:3000/;

# Timeout settings
proxy_connect_timeout 60s;      # Timeout connecting to upstream
proxy_send_timeout 60s;         # Timeout sending request
proxy_read_timeout 60s;         # Timeout reading response

# Buffer settings
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;

}


3. Proxy Headers

Headers are essential so backend servers know information about the original request.

3.1. Essential Proxy Headers

location / {
proxy_pass http://localhost:3000;

# Host header
proxy_set_header Host $host;

# Real client IP
proxy_set_header X-Real-IP $remote_addr;

# Chain of proxies
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# Protocol (http/https)
proxy_set_header X-Forwarded-Proto $scheme;

# Original host
proxy_set_header X-Forwarded-Host $host;

# Port
proxy_set_header X-Forwarded-Port $server_port;

}

3.2. Header Explanations

Host:

proxy_set_header Host $host;

$host = domain name from request

Example: example.com

Backend receives: Host: example.com

X-Real-IP:

proxy_set_header X-Real-IP $remote_addr;

$remote_addr = IP of the client connecting directly to Nginx

Example: 192.168.1.100

Backend receives: X-Real-IP: 192.168.1.100

X-Forwarded-For:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Appends client IP to any existing X-Forwarded-For header

Example:

Request 1: Client → Nginx → Backend

X-Forwarded-For: 192.168.1.100

Request 2: Client → CDN → Nginx → Backend

X-Forwarded-For: 192.168.1.100, 10.0.0.50

$proxy_add_x_forwarded_for preserves the chain of proxies

X-Forwarded-Proto:

proxy_set_header X-Forwarded-Proto $scheme;

$scheme = http or https

Backend knows whether original request was HTTP or HTTPS

Important for redirect logic

3.3. Complete Header Configuration

http {
# Define header template
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;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

server {
    listen 80;
    server_name example.com;
    
    location / {
        proxy_pass http://localhost:3000;
        # Headers inherited from http context
    }
}

}

3.4. Custom Headers

location /api/ {
proxy_pass http://localhost:3000/;

# Standard headers
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;

# Custom headers
proxy_set_header X-Request-ID $request_id;
proxy_set_header X-Server-Name $hostname;
proxy_set_header X-Forwarded-User $remote_user;

# Remove headers
proxy_set_header Authorization "";  # Remove auth header

# Add custom values
proxy_set_header X-API-Version "v1";
proxy_set_header X-Environment "production";

}

3.5. Headers for WebSocket

location /ws/ {
proxy_pass http://localhost:3000/;

# WebSocket specific headers
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

# Standard headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# WebSocket timeouts
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;

}

3.6. Preserve Original Headers

location / {
proxy_pass http://localhost:3000;

# Pass all original headers
proxy_pass_request_headers on;

# Specific headers
proxy_set_header Accept-Encoding $http_accept_encoding;
proxy_set_header Accept-Language $http_accept_language;
proxy_set_header Cookie $http_cookie;
proxy_set_header Referer $http_referer;
proxy_set_header User-Agent $http_user_agent;

}

3.7. Security Headers

location / {
proxy_pass http://localhost:3000;

# Standard proxy headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# Hide Nginx version
proxy_hide_header X-Powered-By;
proxy_hide_header Server;

# Add security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;

}


4. Upstream Servers and Load Balancing

4.1. Basic Upstream Block

# Define upstream
upstream backend {
server localhost:3000;
server localhost:3001;
server localhost:3002;
}

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;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

}

4.2. Load Balancing Methods

1. Round Robin (default):

upstream backend {
# Round-robin: each server in turn
server localhost:3000;
server localhost:3001;
server localhost:3002;
}

Request 1 → 3000

Request 2 → 3001

Request 3 → 3002

Request 4 → 3000 (repeats)

2. Least Connections:

upstream backend {
least_conn;  # Server with fewest connections

server localhost:3000;
server localhost:3001;
server localhost:3002;

}

Good for long-lived connections

Distributes load evenly

3. IP Hash (Sticky Sessions):

upstream backend {
ip_hash;  # Same client → same server

server localhost:3000;
server localhost:3001;
server localhost:3002;

}

Client 192.168.1.100 → always routes to server 3000

Client 192.168.1.101 → always routes to server 3001

Good for session-based applications

4. Hash (Generic):

upstream backend {
hash $request_uri consistent;  # Hash by URI

server localhost:3000;
server localhost:3001;
server localhost:3002;

}

Same URI → same server

Good for caching

5. Random:

upstream backend {
random;  # Random server

server localhost:3000;
server localhost:3001;
server localhost:3002;

}

4.3. Server Weights

upstream backend {
# Servers with higher weight receive more requests
server localhost:3000 weight=3;  # 60% traffic
server localhost:3001 weight=1;  # 20% traffic
server localhost:3002 weight=1;  # 20% traffic
}

Total weight = 5

Server 3000: 3/5 = 60%

Server 3001: 1/5 = 20%

Server 3002: 1/5 = 20%

Use case for weights:

upstream backend {
# Production servers
server prod1.example.com weight=5;
server prod2.example.com weight=5;

# Canary deployment - 10% traffic
server canary.example.com weight=1;

}

4.4. Backup Servers

upstream backend {
server localhost:3000;
server localhost:3001;
server localhost:3002 backup;  # Only used when primary servers are down
}

3002 only receives traffic when 3000 and 3001 are both unavailable

4.5. Server Parameters

upstream backend {
server localhost:3000 weight=5 max_fails=3 fail_timeout=30s;
server localhost:3001 weight=5 max_fails=3 fail_timeout=30s;
server localhost:3002 backup;
server localhost:3003 down;  # Temporarily disabled
}

Parameters:

weight=N - Weight (default 1)

max_fails=N - Number of failures before marking down (default 1)

fail_timeout=T - Timeout duration (default 10s)

backup - Backup server

down - Temporarily disabled

4.6. Advanced Upstream Configuration

upstream backend {
least_conn;  # Load balancing method

# Server configuration
server srv1.example.com:8080 weight=3 max_fails=2 fail_timeout=30s;
server srv2.example.com:8080 weight=3 max_fails=2 fail_timeout=30s;
server srv3.example.com:8080 weight=2 max_fails=2 fail_timeout=30s;
server srv4.example.com:8080 backup;

# Keepalive connections
keepalive 32;  # Keep 32 idle connections to upstream
keepalive_timeout 60s;
keepalive_requests 100;

}

server { listen 80;

location / {
    proxy_pass http://backend;
    
    # HTTP version for keepalive
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    
    # Standard headers
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

}

4.7. Multiple Upstreams

# API Backend
upstream api_backend {
least_conn;
server api1.example.com:3000;
server api2.example.com:3000;
server api3.example.com:3000;
}

Auth Service

upstream auth_backend { server auth1.example.com:4000; server auth2.example.com:4000; }

WebSocket Service

upstream websocket_backend { ip_hash; # Sticky sessions for WebSocket server ws1.example.com:5000; server ws2.example.com:5000; }

server { listen 80; server_name example.com;

location /api/ {
    proxy_pass http://api_backend/;
}

location /auth/ {
    proxy_pass http://auth_backend/;
}

location /ws/ {
    proxy_pass http://websocket_backend/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

}


5. Basic Health Checks

5.1. Passive Health Checks

Nginx automatically detects failed servers based on their responses.

upstream backend {
server localhost:3000 max_fails=3 fail_timeout=30s;
server localhost:3001 max_fails=3 fail_timeout=30s;
server localhost:3002 max_fails=3 fail_timeout=30s;
}

max_fails=3: After 3 consecutive failures

fail_timeout=30s: Server is marked down for 30 seconds

After 30s, Nginx retries the server

How it works:

1. Request sent to localhost:3000
2. Server returns 502, 503, 504 or times out → fail count = 1
3. Next request goes to 3000
4. Server fails again → fail count = 2
5. Next request goes to 3000
6. Server fails third time → fail count = 3 → Server marked DOWN
7. Traffic is routed to 3001 and 3002
8. After 30 seconds, Nginx retries 3000
9. If 3000 responds OK → fail count resets, server UP

5.2. Active Health Checks (Nginx Plus)

Nginx Plus supports active health checks (not available in the open-source version).

# Nginx Plus only
upstream backend {
zone backend 64k;
server localhost:3000;
server localhost:3001;
server localhost:3002;
}

server { listen 80;

location / {
    proxy_pass http://backend;
    health_check interval=5s fails=3 passes=2 uri=/health;
}

}

interval=5s: Check every 5 seconds

fails=3: Mark down after 3 failures

passes=2: Mark up after 2 successes

uri=/health: Endpoint to check

5.3. Custom Health Check Endpoint

Backend implementation (Node.js example):

// health.js
const express = require('express');
const app = express();

app.get('/health', (req, res) => { // Check database connection // Check dependencies // Check memory usage, etc.

const health = {
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    memory: process.memoryUsage()
};

res.status(200).json(health);

});

app.listen(3000);

Nginx configuration:

upstream backend {
server localhost:3000 max_fails=3 fail_timeout=30s;
server localhost:3001 max_fails=3 fail_timeout=30s;
}

server { listen 80;

location / {
    proxy_pass http://backend;
}

# Health check endpoint (not public)
location /health {
    access_log off;
    proxy_pass http://backend;
    
    # Only allow from localhost
    allow 127.0.0.1;
    deny all;
}

}

5.4. External Health Checks

Use an external script to monitor and update upstreams.

Monitor script:

#!/bin/bash

health_check.sh

UPSTREAM_SERVERS=( "localhost:3000" "localhost:3001" "localhost:3002" )

HEALTH_ENDPOINT="/health"

for server in "${UPSTREAM_SERVERS[@]}"; do response=$(curl -s -o /dev/null -w "%{http_code}" "http://$server$HEALTH_ENDPOINT")

if [ "$response" = "200" ]; then
    echo "$(date) - $server is healthy"
else
    echo "$(date) - $server is down (HTTP $response)"
    # Send alert
    # Update upstream config
    # Reload Nginx
fi

done

Crontab:

# Run health check every minute

          • /usr/local/bin/health_check.sh >> /var/log/health_check.log 2>&1

5.5. Monitoring with Stub Status

server {
listen 8080;
server_name localhost;





location /nginx_status { stub_status; access_log off; allow 127.0.0.1; deny all; } }

Check status:

curl http://localhost:8080/nginx_status

Output:

Active connections: 291

server accepts handled requests

16630948 16630948 31070465

Reading: 6 Writing: 179 Waiting: 106

5.6. Health Check with Scripts

Python health check:

#!/usr/bin/env python3

health_monitor.py

import requests import time import smtplib from email.message import EmailMessage

BACKENDS = [ 'http://localhost:3000/health', 'http://localhost:3001/health', 'http://localhost:3002/health', ]

def check_health(url): try: response = requests.get(url, timeout=5) return response.status_code == 200 except: return False

def send_alert(backend, status): msg = EmailMessage() msg['Subject'] = f'Backend Alert: {backend}' msg['From'] = '[email protected]' msg['To'] = '[email protected]' msg.set_content(f'Backend {backend} is {status}')

with smtplib.SMTP('localhost') as s:
    s.send_message(msg)

def main(): while True: for backend in BACKENDS: if not check_health(backend): print(f'{backend} is DOWN') send_alert(backend, 'DOWN') else: print(f'{backend} is UP')

    time.sleep(60)  # Check every minute

if name == 'main': main()


6. Real-world Examples

6.1. Node.js Application

Backend (app.js):

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) => { res.json({ message: 'Hello from Node.js', server: localhost:${PORT}, headers: req.headers }); });

app.get('/api/users', (req, res) => { res.json([ { id: 1, name: 'User 1' }, { id: 2, name: 'User 2' } ]); });

app.get('/health', (req, res) => { res.status(200).json({ status: 'ok' }); });

app.listen(PORT, () => { console.log(Server running on port ${PORT}); });

Nginx configuration:

upstream nodejs_backend {
least_conn;
server localhost:3000 max_fails=3 fail_timeout=30s;
server localhost:3001 max_fails=3 fail_timeout=30s;
server localhost:3002 max_fails=3 fail_timeout=30s;
keepalive 32;
}

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

access_log /var/log/nginx/nodejs.access.log;
error_log /var/log/nginx/nodejs.error.log;

location / {
    proxy_pass http://nodejs_backend;
    
    # HTTP version
    proxy_http_version 1.1;
    
    # Headers
    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;
    proxy_set_header Connection "";
    
    # Timeouts
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;
    
    # Buffering
    proxy_buffering on;
    proxy_buffer_size 4k;
    proxy_buffers 8 4k;
}

location /health {
    access_log off;
    proxy_pass http://nodejs_backend/health;
}

}

6.2. Python Flask/Django Application

Backend (app.py):

from flask import Flask, jsonify, request
import os

app = Flask(name) PORT = int(os.environ.get('PORT', 5000))

@app.route('/') def home(): return jsonify({ 'message': 'Hello from Python', 'server': f'localhost:{PORT}', 'headers': dict(request.headers) })

@app.route('/api/data') def get_data(): return jsonify([ {'id': 1, 'value': 'Data 1'}, {'id': 2, 'value': 'Data 2'} ])

@app.route('/health') def health(): return jsonify({'status': 'ok'}), 200

if name == 'main': app.run(host='0.0.0.0', port=PORT)

Nginx configuration:

upstream python_backend {
server localhost:5000;
server localhost:5001;
server localhost:5002;
}

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

client_max_body_size 10M;

location / {
    proxy_pass http://python_backend;
    
    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 apps can be slow
    proxy_read_timeout 300s;
    proxy_connect_timeout 300s;
    proxy_send_timeout 300s;
}

}

6.3. PHP Application with PHP-FPM

Nginx configuration:

upstream php_backend {
server unix:/var/run/php/php8.1-fpm.sock;
# or
# server localhost:9000;
}

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

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

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_pass php_backend;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    
    # Headers
    fastcgi_param HTTP_X_REAL_IP $remote_addr;
    fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
    fastcgi_param HTTP_X_FORWARDED_PROTO $scheme;
}

location ~ /\.ht {
    deny all;
}

}

6.4. Microservices Architecture

# User Service
upstream user_service {
server user1.internal:8001;
server user2.internal:8001;
}

Order Service

upstream order_service { server order1.internal:8002; server order2.internal:8002; }

Payment Service

upstream payment_service { server payment1.internal:8003; server payment2.internal:8003; }

Product Service

upstream product_service { server product1.internal:8004; server product2.internal:8004; }

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

# Shared headers
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;
proxy_set_header X-Request-ID $request_id;

location /api/users/ {
    proxy_pass http://user_service/;
}

location /api/orders/ {
    proxy_pass http://order_service/;
}

location /api/payments/ {
    proxy_pass http://payment_service/;
}

location /api/products/ {
    proxy_pass http://product_service/;
}

}


7. Practice Exercises

Exercise 1: Basic Reverse Proxy

  1. Create a simple Node.js/Python server on port 3000
  2. Configure Nginx as a reverse proxy
  3. Test and verify that headers are passed correctly

Exercise 2: Multiple Backends

  1. Run 3 instances of an application on ports 3000, 3001, 3002
  2. Set up an upstream with round-robin
  3. Test load balancing (check logs to see how requests are distributed)

Exercise 3: Sticky Sessions

  1. Set up an upstream with ip_hash
  2. Test that the same client always reaches the same backend
  3. Compare with round-robin

Exercise 4: Health Checks

  1. Configure passive health checks with max_fails and fail_timeout
  2. Stop one backend server
  3. Verify Nginx automatically routes traffic to healthy servers
  4. Start the server again and verify traffic returns

Exercise 5: Microservices

  1. Create 2-3 simple APIs (can be mocks)
  2. Set up Nginx to route requests based on URL path:
    • /api/users → User service
    • /api/products → Product service
  3. Test routing

Exercise 6: WebSocket Proxy

  1. Create a simple WebSocket server
  2. Configure Nginx as a WebSocket proxy
  3. Test the connection and message passing

8. Troubleshooting

8.1. Common Issues

1. 502 Bad Gateway:

# Cause: Backend is not running or not reachable

Check backend

curl http://localhost:3000

Check Nginx error log

sudo tail -f /var/log/nginx/error.log

Check firewall

sudo ufw status

2. 504 Gateway Timeout:

# Cause: Backend takes too long to respond

Fix: Increase timeout

location / { proxy_pass http://backend; proxy_read_timeout 300s; proxy_connect_timeout 300s; }

3. Headers not being passed:

# Verify headers at backend

Log request headers

Nginx config

proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

4. Large uploads fail:

# Increase client_max_body_size
http {
client_max_body_size 100M;
}

5. WebSocket connection fails:

# Upgrade headers are required
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

8.2. Debug Commands

# Test upstream connectivity
curl -v http://localhost:3000

Check Nginx configuration

sudo nginx -t

Reload Nginx

sudo systemctl reload nginx

Watch error log in real-time

sudo tail -f /var/log/nginx/error.log

Check upstream status (if stub_status is enabled)

curl http://localhost/nginx_status

Test with specific headers

curl -H "Host: example.com" http://localhost

Test proxy headers

curl -H "X-Forwarded-For: 1.2.3.4" http://localhost


9. Best Practices

9.1. Configuration

  1. Use upstream blocks:
# Good
upstream backend {
server localhost:3000;
}

Not recommended (no failover or load balancing)

location / { proxy_pass http://localhost:3000; }

  1. Set appropriate timeouts:
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
  1. Enable keepalive:
upstream backend {
server localhost:3000;
keepalive 32;
}

location / { proxy_http_version 1.1; proxy_set_header Connection ""; }

  1. Use health checks:
server localhost:3000 max_fails=3 fail_timeout=30s;

9.2. Security

  1. Don't expose internal structure:
proxy_hide_header X-Powered-By;
  1. Limit request size:
client_max_body_size 10M;
  1. Rate limiting:
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

location /api/ { limit_req zone=api burst=20; }

9.3. Performance

  1. Buffer configuration:
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
  1. Caching:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m;

location / { proxy_cache my_cache; proxy_cache_valid 200 10m; }


Summary

In this lesson, you learned:

  • ✅ Reverse proxy concepts and use cases
  • ✅ proxy_pass configuration and routing
  • ✅ Proxy headers and X-Forwarded headers
  • ✅ Upstream servers and load balancing
  • ✅ Health checks and monitoring
  • ✅ Real-world examples with Node.js, Python, and PHP

Next lesson: We will dive deeper into Load Balancing — algorithms, strategies, and advanced configurations.