Set Up a Reverse Proxy with Nginx
Put multiple web services behind one IP with Nginx reverse proxy and automatic SSL certificates.
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:
| Type | Name | Value |
|---|---|---|
| A | cloud | YOUR.VPS.IP |
| A | blog | YOUR.VPS.IP |
| A | api | YOUR.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:
- Verify domain ownership
- Install the certificates
- Modify your Nginx configs to redirect HTTP to HTTPS
- Set up auto-renewal (runs twice daily via systemd timer)
Test auto-renewal:
sudo certbot renew --dry-run
Key Proxy Headers Explained
| Header | What It Does |
|---|---|
Host | Passes the original hostname to the backend. Without this, your app sees 127.0.0.1 instead of cloud.yourdomain.com |
X-Real-IP | Passes the visitor’s real IP address. Without this, your app sees all traffic coming from 127.0.0.1 |
X-Forwarded-For | Full chain of IPs if there are multiple proxies |
X-Forwarded-Proto | Tells the backend whether the original request was HTTP or HTTPS. Important for apps that generate URLs |
Upgrade / Connection | Enables 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
| Problem | Fix |
|---|---|
| 502 Bad Gateway | Backend service isn’t running. Check docker compose ps or systemctl status |
| 504 Gateway Timeout | Backend is too slow. Increase proxy_read_timeout |
| “Connection refused” in logs | Wrong port in proxy_pass. Verify the backend is listening on that port |
| SSL certificate not found | DNS hasn’t propagated yet. Wait and retry certbot |
| Mixed content warnings | Set X-Forwarded-Proto and configure your app to use HTTPS URLs |
What to Do Next
- Docker Basics for VPS Users - run your services in containers
- Hosting Nextcloud on GoZen - uses this reverse proxy setup
- Server Hardening Basics - secure the VPS behind the proxy
Last updated 07 Apr 2026, 00:00 +0200.