Nginx Reverse Proxy¶
This guide puts Nginx in front of Dreadnought to provide HTTPS access with a real domain name. Nginx acts as a reverse proxy, forwarding browser requests to the appropriate container.
Prerequisites¶
- Dreadnought running via Docker Compose (or systemd)
- A domain name with DNS pointing to your server
- Nginx installed (
sudo apt install nginx) - Certbot for Let's Encrypt certificates
Architecture¶
Internet (HTTPS)
│
▼
Nginx (ports 80 + 443)
│
├── ddns.yourdomain.com ──► http://127.0.0.1:8082 (web container)
│
└── ddns-api.yourdomain.com ──► http://127.0.0.1:8081 (api container)
You need two subdomains:
- Web UI: ddns.yourdomain.com (or whatever you prefer)
- API: ddns-api.yourdomain.com
Both should have DNS A records pointing to your server's public IP.
Step 1 — Install Nginx and Certbot¶
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
Step 2 — Set NEXT_PUBLIC_API_URL¶
Before deploying, update NEXT_PUBLIC_API_URL in docker-compose.yml to your public API domain:
web:
environment:
- NEXT_PUBLIC_API_URL=https://ddns-api.yourdomain.com
- NODE_ENV=production
Then rebuild:
docker compose up -d --build
Step 3 — Create Nginx Configuration¶
sudo nano /etc/nginx/sites-available/dreadnought
Paste the following (replace yourdomain.com with your actual domain):
# Redirect HTTP to HTTPS
server {
listen 80;
server_name ddns.yourdomain.com ddns-api.yourdomain.com;
return 301 https://$host$request_uri;
}
# Web UI (Frontend)
server {
listen 443 ssl http2;
server_name ddns.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/ddns.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ddns.yourdomain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Security headers
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header X-XSS-Protection "1; mode=block" always;
location / {
proxy_pass http://127.0.0.1:8082;
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;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
}
# API (Backend)
server {
listen 443 ssl http2;
server_name ddns-api.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/ddns-api.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ddns-api.yourdomain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
location / {
proxy_pass http://127.0.0.1:8081;
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;
}
}
Step 4 — Enable the Site and Get Certificates¶
# Enable the site
sudo ln -s /etc/nginx/sites-available/dreadnought /etc/nginx/sites-enabled/
# Test configuration syntax
sudo nginx -t
# Get TLS certificates for both domains
sudo certbot --nginx -d ddns.yourdomain.com -d ddns-api.yourdomain.com
# Reload Nginx
sudo systemctl reload nginx
Certbot will automatically modify your Nginx config to use the certificates and set up auto-renewal.
Step 5 — Verify Auto-Renewal¶
Certbot installs a systemd timer for automatic renewal. Verify it's active:
sudo systemctl status certbot.timer
# Test renewal (dry run — won't actually renew)
sudo certbot renew --dry-run
Step 6 — Block Direct Port Access¶
Optionally, block access to ports 8081 and 8082 on the public interface so all traffic must go through Nginx:
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw deny 8081/tcp
sudo ufw deny 8082/tcp
sudo ufw enable
Nginx on the Same Host as Cloudflare-Proxied Domain¶
If your domain is proxied through Cloudflare (orange cloud), Cloudflare adds its own TLS layer. In this case:
- Cloudflare connects to your Nginx using HTTPS
- Your Nginx cert still needs to be valid (use Let's Encrypt or Cloudflare Origin Certificate)
- Or set Cloudflare SSL/TLS mode to Flexible (Cloudflare uses HTTPS externally, HTTP internally) — but Full or Full(Strict) with a real cert is recommended
Single Subdomain Setup (API Under /api/)¶
If you only have one subdomain (e.g. ddns.yourdomain.com) and want to serve both the frontend and API from it:
server {
listen 443 ssl http2;
server_name ddns.yourdomain.com;
# ... ssl config ...
# API — proxy /api/* requests to the backend
location /api/ {
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;
}
location /health {
proxy_pass http://127.0.0.1:8081;
proxy_set_header Host $host;
}
# Frontend — everything else goes to Next.js
location / {
proxy_pass http://127.0.0.1:8082;
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;
}
}
Then set:
NEXT_PUBLIC_API_URL=https://ddns.yourdomain.com
Troubleshooting Nginx¶
502 Bad Gateway¶
The backend container isn't running or is on the wrong port.
docker compose ps # Verify containers are running
curl http://127.0.0.1:8081/health # Test API directly
Certificate errors¶
# Check cert expiry
sudo certbot certificates
# Force renewal
sudo certbot renew --force-renewal -d ddns.yourdomain.com
Nginx won't start¶
sudo nginx -t # Check syntax errors
sudo journalctl -u nginx -n 50 # Check logs