DevOps 6 min read

Nginx Reverse Proxy with Docker: Complete Setup Guide

Configure Nginx as a reverse proxy with Docker. Learn SSL setup, load balancing, caching, and production deployment with practical examples.

MR

Moshiour Rahman

Advertisement

What is a Reverse Proxy?

A reverse proxy sits in front of your web servers and forwards client requests to the appropriate backend server. Nginx is one of the most popular choices for this task.

Benefits

FeatureBenefit
Load balancingDistribute traffic across servers
SSL terminationHandle HTTPS in one place
CachingImprove performance
SecurityHide backend servers
CompressionReduce bandwidth

Basic Nginx Setup with Docker

Simple Reverse Proxy

# docker-compose.yml
version: '3.8'

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    networks:
      - webnet

  app:
    image: your-app:latest
    expose:
      - "3000"
    networks:
      - webnet

networks:
  webnet:
# nginx.conf
events {
    worker_connections 1024;
}

http {
    upstream backend {
        server app:3000;
    }

    server {
        listen 80;
        server_name localhost;

        location / {
            proxy_pass http://backend;
            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;
            proxy_cache_bypass $http_upgrade;
        }
    }
}

Multi-Service Configuration

# docker-compose.yml
version: '3.8'

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
    depends_on:
      - frontend
      - api
      - admin
    networks:
      - webnet

  frontend:
    build: ./frontend
    expose:
      - "3000"
    networks:
      - webnet

  api:
    build: ./api
    expose:
      - "4000"
    environment:
      - DATABASE_URL=postgresql://postgres:password@db:5432/myapp
    networks:
      - webnet

  admin:
    build: ./admin
    expose:
      - "5000"
    networks:
      - webnet

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - webnet

networks:
  webnet:

volumes:
  postgres_data:
# nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml application/json application/javascript
               application/xml application/xml+rss text/javascript;

    include /etc/nginx/conf.d/*.conf;
}
# nginx/conf.d/default.conf
upstream frontend {
    server frontend:3000;
}

upstream api {
    server api:4000;
}

upstream admin {
    server admin:5000;
}

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

    # Redirect to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    # SSL Configuration
    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;

    # Modern SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    # HSTS
    add_header Strict-Transport-Security "max-age=63072000" always;

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

    # Frontend
    location / {
        proxy_pass http://frontend;
        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;
    }

    # API
    location /api/ {
        rewrite ^/api/(.*) /$1 break;
        proxy_pass http://api;
        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;

        # Timeouts for API
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # Admin panel
    location /admin/ {
        proxy_pass http://admin/;
        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;
    }

    # Static files caching
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|pdf|txt)$ {
        expires 7d;
        add_header Cache-Control "public, immutable";
    }

    # Health check endpoint
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}

Load Balancing

Round Robin (Default)

upstream backend {
    server app1:3000;
    server app2:3000;
    server app3:3000;
}

Weighted Load Balancing

upstream backend {
    server app1:3000 weight=5;  # Gets 5x more traffic
    server app2:3000 weight=3;
    server app3:3000 weight=1;
}

Least Connections

upstream backend {
    least_conn;
    server app1:3000;
    server app2:3000;
    server app3:3000;
}

IP Hash (Session Persistence)

upstream backend {
    ip_hash;
    server app1:3000;
    server app2:3000;
    server app3:3000;
}

Health Checks

upstream backend {
    server app1:3000 max_fails=3 fail_timeout=30s;
    server app2:3000 max_fails=3 fail_timeout=30s;
    server app3:3000 backup;  # Only used when others fail
}

SSL with Let’s Encrypt

Using Certbot

# docker-compose.yml
version: '3.8'

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./certbot/conf:/etc/letsencrypt:ro
      - ./certbot/www:/var/www/certbot:ro
    networks:
      - webnet

  certbot:
    image: certbot/certbot
    volumes:
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

networks:
  webnet:
# Initial config for certificate generation
server {
    listen 80;
    server_name example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

Generate certificate:

docker compose run --rm certbot certonly --webroot \
    -w /var/www/certbot \
    -d example.com \
    -d www.example.com \
    --email your@email.com \
    --agree-tos \
    --no-eff-email

Caching

Proxy Cache

http {
    # Cache configuration
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m
                     max_size=1g inactive=60m use_temp_path=off;

    server {
        location /api/ {
            proxy_pass http://api;

            # Enable caching
            proxy_cache my_cache;
            proxy_cache_valid 200 10m;
            proxy_cache_valid 404 1m;
            proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
            proxy_cache_background_update on;
            proxy_cache_lock on;

            # Cache key
            proxy_cache_key $scheme$proxy_host$request_uri;

            # Add cache status header
            add_header X-Cache-Status $upstream_cache_status;
        }
    }
}

Browser Caching

location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 30d;
    add_header Cache-Control "public, no-transform";
}

location ~* \.(pdf|txt|xml)$ {
    expires 7d;
    add_header Cache-Control "public";
}

# No cache for HTML
location ~* \.html$ {
    expires -1;
    add_header Cache-Control "no-store, no-cache, must-revalidate";
}

Rate Limiting

http {
    # Define rate limit zones
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/s;
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    server {
        # API rate limiting
        location /api/ {
            limit_req zone=api_limit burst=20 nodelay;
            limit_conn conn_limit 10;
            proxy_pass http://api;
        }

        # Strict rate limiting for login
        location /api/auth/login {
            limit_req zone=login_limit burst=5;
            proxy_pass http://api;
        }
    }
}

WebSocket Support

location /ws/ {
    proxy_pass http://websocket_backend;
    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;

    # WebSocket timeouts
    proxy_read_timeout 86400s;
    proxy_send_timeout 86400s;
}

Useful Commands

# Test configuration
docker exec nginx nginx -t

# Reload configuration
docker exec nginx nginx -s reload

# View logs
docker logs nginx -f

# Check access logs
docker exec nginx tail -f /var/log/nginx/access.log

# Check error logs
docker exec nginx tail -f /var/log/nginx/error.log

Summary

FeatureConfiguration
Reverse proxyproxy_pass
Load balancingupstream block
SSLssl_certificate
Cachingproxy_cache
Rate limitinglimit_req_zone
WebSocketUpgrade headers

Nginx is a powerful, efficient reverse proxy that handles high traffic with minimal resources.

Advertisement

MR

Moshiour Rahman

Software Architect & AI Engineer

Share:
MR

Moshiour Rahman

Software Architect & AI Engineer

Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.

Related Articles

Comments

Comments are powered by GitHub Discussions.

Configure Giscus at giscus.app to enable comments.