// blog

How to Set Up Nginx as a Reverse Proxy for Docker Containers: Complete Guide

August 14, 2026 · by CLIQHOST

How to Set Up Nginx as a Reverse Proxy for Docker Containers: Complete Guide

Running multiple web applications in Docker containers on the same server is great — until you need to expose them all to the internet. Opening individual ports for each app is messy, insecure, and hard to manage. The industry-standard solution is Nginx as a reverse proxy: a single entry point that accepts all HTTP/HTTPS traffic and routes it to the correct container based on the domain name or URL path.

In this guide, you'll learn how to configure Nginx as a reverse proxy for Docker containers on a NVMe VPS or SSD VPS, step by step, with practical configuration examples and HTTPS setup.


Why Use a Reverse Proxy?

Without a reverse proxy, every Docker container needs to listen on a unique port (e.g. 3001, 3002, 8080). This leads to:

  • Ugly URLs like http://yourdomain.com:3001
  • Difficulty managing SSL certificates per service
  • Direct port exposure in your firewall rules

Nginx as a reverse proxy fixes all of this: it receives all traffic on ports 80 and 443, then forwards it internally to the right container — clean, secure, and scalable.


Prerequisites

Before you start, make sure you have:

  • A Linux VPS (Ubuntu 22.04 or Debian 12 recommended) — if you don't have one yet, check out the NVMe VPS plans from CLIQHOST
  • Docker and Docker Compose installed
  • Nginx installed on the host (not in a container, for this guide)
  • Root or sudo access
  • A domain pointing to your server's IP address
  • A valid SSL certificate (or Let's Encrypt)

Step 1: Install Nginx on the Server

If Nginx isn't installed yet:

sudo apt update
sudo apt install nginx -y
sudo systemctl enable nginx
sudo systemctl start nginx

Verify it's running:

sudo systemctl status nginx

Step 2: Launch Your Docker Containers

Let's say you have two applications:

  • App1 — a Node.js app listening on internal port 3000
  • App2 — a Python/Flask app listening on internal port 5000

Start the containers with ports bound to localhost only:

# App1
docker run -d --name app1 -p 127.0.0.1:3000:3000 myapp1:latest

# App2
docker run -d --name app2 -p 127.0.0.1:5000:5000 myapp2:latest

Important: By binding to 127.0.0.1:PORT, the container port is only reachable from the server itself — not from the outside world. Nginx (also on the server) will forward traffic to it.

Or using Docker Compose (docker-compose.yml):

version: '3.8'
services:
  app1:
    image: myapp1:latest
    ports:
      - "127.0.0.1:3000:3000"
    restart: unless-stopped

  app2:
    image: myapp2:latest
    ports:
      - "127.0.0.1:5000:5000"
    restart: unless-stopped
docker compose up -d

Step 3: Configure Nginx as a Reverse Proxy

Create a separate configuration file for each domain in /etc/nginx/sites-available/.

Config for App1 (app1.yourdomain.com)

sudo nano /etc/nginx/sites-available/app1.yourdomain.com
server {
    listen 80;
    server_name app1.yourdomain.com;

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

Config for App2 (app2.yourdomain.com)

sudo nano /etc/nginx/sites-available/app2.yourdomain.com
server {
    listen 80;
    server_name app2.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:5000;
        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;
    }
}

Enable both configurations:

sudo ln -s /etc/nginx/sites-available/app1.yourdomain.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/app2.yourdomain.com /etc/nginx/sites-enabled/

Test and reload Nginx:

sudo nginx -t
sudo systemctl reload nginx

Step 4: Add HTTPS with Let's Encrypt

Install Certbot and obtain free SSL certificates:

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d app1.yourdomain.com -d app2.yourdomain.com

Certbot will automatically update your Nginx files to add SSL blocks and redirect HTTP → HTTPS.

After completion, the config for app1.yourdomain.com will look like:

server {
    listen 443 ssl;
    server_name app1.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/app1.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app1.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server {
    listen 80;
    server_name app1.yourdomain.com;
    return 301 https://$host$request_uri;
}

For commercial or business projects, explore OV/EV SSL certificates from CLIQHOST for enhanced trust and validation.


Step 5: Docker Network Approach (Advanced)

A cleaner, more production-ready setup is to run Nginx inside a Docker container and communicate with other containers via an internal Docker network — no ports exposed on the host.

version: '3.8'
networks:
  webnet:
    driver: bridge

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

  app1:
    image: myapp1:latest
    expose:
      - "3000"
    networks:
      - webnet
    restart: unless-stopped

  app2:
    image: myapp2:latest
    expose:
      - "5000"
    networks:
      - webnet
    restart: unless-stopped

In this setup, proxy_pass in Nginx uses the service name instead of 127.0.0.1:

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

Docker's internal DNS automatically resolves app1 to the container's IP — elegant and secure.


Performance & Security Tips

Limit request body size

client_max_body_size 20M;

Set sensible timeouts

proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

Enable Gzip compression

gzip on;
gzip_types text/plain application/json application/javascript text/css;
gzip_min_length 1000;

Basic rate limiting (DDoS protection)

limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m;

location /api/ {
    limit_req zone=api burst=10 nodelay;
    proxy_pass http://app1:3000;
}

For advanced server hardening and ongoing maintenance, check out CLIQHOST's server management services — professional Linux administration handled for you.


Quick Troubleshooting

Problem Solution
502 Bad Gateway Docker container is not running or wrong port
404 Not Found server_name doesn't match the accessed domain
SSL_ERROR_RX_RECORD_TOO_LONG Nginx listens on 443 but no SSL block configured
Nginx won't start Run sudo nginx -t for detailed error output

Check Nginx logs:

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

Check container status:

docker ps
docker logs app1

Conclusion

Nginx as a reverse proxy for Docker containers is a powerful, flexible combination that lets you run dozens of applications on a single server — all secured with HTTPS, accessible via clean domain names, and isolated from one another.

To get the most out of this setup, you need a server with sufficient resources. Explore NVMe VPS plans or managed dedicated servers from CLIQHOST — reliable infrastructure with professional support included.

Have questions or need a custom configuration? Contact the CLIQHOST team — we're here to help.

SHARE
// what clients say

What Our Clients Say

Real reviews from customers who trust CLIQHOST for performance, reliability and expert technical support.

★★★★★

"We moved our online shop from a foreign host and the difference is night and day — pages load instantly and support replies in minutes, in Romanian."

AM
Andrei M.
eCommerce owner · Chișinău
★★★★★

"Migrated 12 client sites to CLIQHOST. Free migration, zero downtime, and the cPanel setup is exactly what my team needed. Highly recommend."

EV
Elena V.
Web agency · Bălți
★★★★★

"Our NVMe VPS handles traffic spikes without a sweat. Full root, local datacenter, and billing in MDL — everything we wanted from a provider."

DC
Dmitri C.
SaaS founder · Chișinău