Hosting Multiple Websites with Docker and Nginx Reverse Proxy Behind Cloudflare

When hosting multiple websites on a single server, exposing Docker containers directly (e.g., on :3000) can leave your system vulnerable and cluttered. The cleaner and more secure approach is to hide applications behind Nginx reverse proxy, route traffic using Cloudflare, and block direct IP access so this setup ensures:

  • One public IP can serve multiple domains
  • Each domain routes to the correct containerized app
  • Ports like 3000, 5000, etc. are never exposed to the public
  • Traffic is secured and manageable

Step 1: Cloudflare Setup

  1. Add your domains to Cloudflare.
  2. Point DNS records (A or CNAME) to your server’s IP and enable Proxy (orange cloud).
  3. In SSL/TLS settings, use Flexible if your server serves plain HTTP, or Full (strict) if you install certs later.

Step 2: Firewall Configuration

Allow only required ports:

sudo apt install ufw -y
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable

This ensures only SSH (22), HTTP (80), and HTTPS (443) are open.

Step 3: Block Direct Container Access

Docker often bypasses UFW, exposing container ports. Secure them at the Linux level:

sudo iptables -I DOCKER-USER -p tcp --dport 22 -j ACCEPT
sudo iptables -I DOCKER-USER -p tcp --dport 80 -j ACCEPT
sudo iptables -I DOCKER-USER -p tcp --dport 443 -j ACCEPT
sudo iptables -I DOCKER-USER ! -s 127.0.0.1 -p tcp -j DROP

This ensures only Nginx (on localhost) can reach containers like :3000.

Step 4: Install and Configure Nginx

Install Nginx:

sudo apt install nginx -y
sudo rm -f /etc/nginx/sites-enabled/default

Create a catch-all to block raw IP requests:

server {
  listen 80 default_server;
  server_name _;
  return 444;
}

Step 5: Reverse Proxy for Multiple Sites

Example: siteA.com → container on 127.0.0.1:3000.

server {
  listen 80;
  server_name siteA.com www.siteA.com;
  
  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-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

For siteB.com → container on 127.0.0.1:4000:

server {
  listen 80;
  server_name siteB.com www.siteB.com;
  
  location / {
    proxy_pass http://127.0.0.1:4000;
    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;
  }
}

Apply changes:

sudo nginx -t
sudo systemctl reload nginx

Step 6: Optional — Restrict Access to Cloudflare Only

Add Cloudflare IP ranges to your Nginx server block:

allow 173.245.48.0/20;
allow 103.21.244.0/22;
# ... (all ranges from https://www.cloudflare.com/ips/)
deny all;

This ensures requests bypassing Cloudflare are blocked.

Summary

By combining Docker, Nginx reverse proxy, and Cloudflare, you can:

Serve multiple domains from one server

Keep internal container ports hidden

Block raw IP access

Control traffic cleanly through Cloudflare

This setup improves security, scalability, and makes your hosting environment production-ready.