A reverse proxy sits in front of your web applications and routes requests to the right service based on the domain name. One VPS, one IP, multiple services, each on its own domain with HTTPS.

Without a reverse proxy, you’d need a different port for each service (site.com:8080, site.com:3000). With one, everything runs on port 443 with proper SSL.

When You Need This

  • Running multiple Docker containers (Nextcloud, a blog, an API) on the same VPS
  • Each service needs its own domain or subdomain
  • You want automatic HTTPS for everything

Architecture

  Visitors
  │
  ├── cloud.yourdomain.com ──→ Nginx ──→ Nextcloud (port 8080)
  ├── blog.yourdomain.com  ──→ Nginx ──→ WordPress (port 8081)
  └── api.yourdomain.com   ──→ Nginx ──→ Node.js (port 3000)
  

Nginx listens on ports 80 (HTTP) and 443 (HTTPS). Based on the incoming hostname, it forwards the request to the correct backend service.

Step 1: Install Nginx and Certbot

  sudo apt update
sudo apt install nginx certbot python3-certbot-nginx -y
  

Start and enable Nginx:

  sudo systemctl enable nginx
sudo systemctl start nginx
  

Step 2: Point DNS

For each service, create an A record pointing to your VPS IP:

TypeNameValue
AcloudYOUR.VPS.IP
AblogYOUR.VPS.IP
AapiYOUR.VPS.IP

Verify propagation at GoZen DNS Inspector.

Step 3: Create Proxy Configurations

Service 1: Nextcloud (port 8080)

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

    client_max_body_size 10G;

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

Service 2: WordPress (port 8081)

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

    client_max_body_size 64M;

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

Service 3: Node.js API (port 3000)

  sudo nano /etc/nginx/sites-available/api.yourdomain.com
  
  server {
    listen 80;
    server_name api.yourdomain.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;

        # WebSocket support (if needed by your API)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
  

Step 4: Enable the Sites

  sudo ln -s /etc/nginx/sites-available/cloud.yourdomain.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/blog.yourdomain.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/api.yourdomain.com /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# Reload
sudo systemctl reload nginx
  

Step 5: Get SSL Certificates

Certbot grabs Let’s Encrypt certificates and auto-configures Nginx for HTTPS:

  # Get certificates for all domains at once
sudo certbot --nginx -d cloud.yourdomain.com -d blog.yourdomain.com -d api.yourdomain.com
  

Certbot will:

  1. Verify domain ownership
  2. Install the certificates
  3. Modify your Nginx configs to redirect HTTP to HTTPS
  4. Set up auto-renewal (runs twice daily via systemd timer)

Test auto-renewal:

  sudo certbot renew --dry-run
  

Key Proxy Headers Explained

HeaderWhat It Does
HostPasses the original hostname to the backend. Without this, your app sees 127.0.0.1 instead of cloud.yourdomain.com
X-Real-IPPasses the visitor’s real IP address. Without this, your app sees all traffic coming from 127.0.0.1
X-Forwarded-ForFull chain of IPs if there are multiple proxies
X-Forwarded-ProtoTells the backend whether the original request was HTTP or HTTPS. Important for apps that generate URLs
Upgrade / ConnectionEnables WebSocket connections through the proxy

Common Adjustments

Large File Uploads

The default client_max_body_size in Nginx is 1MB. For Nextcloud or file upload services, increase it:

  client_max_body_size 10G;
  

Timeouts for Long Requests

If backend services take a long time to respond (large file processing, AI inference):

  proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
  

Rate Limiting

Protect APIs from abuse:

  # In the http block (nginx.conf)
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

# In your server block
location /api/ {
    limit_req zone=api burst=20 nodelay;
    proxy_pass http://127.0.0.1:3000;
}
  

Troubleshooting

ProblemFix
502 Bad GatewayBackend service isn’t running. Check docker compose ps or systemctl status
504 Gateway TimeoutBackend is too slow. Increase proxy_read_timeout
“Connection refused” in logsWrong port in proxy_pass. Verify the backend is listening on that port
SSL certificate not foundDNS hasn’t propagated yet. Wait and retry certbot
Mixed content warningsSet X-Forwarded-Proto and configure your app to use HTTPS URLs

What to Do Next

Last updated 07 Apr 2026, 00:00 +0200. history

Was this page helpful?