Skip to content

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