Purpose
Handles a SSL (HTTPS) and points subdomains (e.g. recipes.sydneysu.dev) to the right container automatically.
Sequence Diagram
sequenceDiagram autonumber actor User as User Browser participant DNS as Cloudflare participant VPS as DigitalOcean VPS (NPM) participant App as App Containers Note over User, DNS: User visits recipes.sydneysu.dev User->>DNS: 1. Fetch IP for Subdomain DNS-->>User: 2. Return Droplet IP rect rgb(40, 44, 52) Note right of VPS: Traffic hits Port 443 (HTTPS) User->>VPS: 3. Request + Host Header VPS->>App: 4. Internal Proxy to Port 9000 App-->>VPS: 5. App Response VPS-->>User: 6. Encrypted Data end Note over User, VPS: User tries Admin Panel (vps.sydneysu.dev) User-XVPS: 7. Direct Port 81 access (BLOCKED by UFW) User->>VPS: 8. Request via Port 443 VPS->>VPS: 9. Internal Proxy to Admin (Port 81) VPS-->>User: 10. Secure Admin Access
NPM vs. Alternatives
Fundamental Difference
- Cloudflare (DNS): Tells the internet’s traffic which IP address to go to
- NPM (Proxy Manager): Directs traffic to the correct Docker container once they arrive
| Feature | Cloudflare (DNS Only) | NPM (Reverse Proxy) |
|---|---|---|
| Routing | Can only send traffic to one IP. | Can route traffic to unlimited apps on different internal ports. |
| User Experience | Requires “Ugly URLs” like site.com:9000. | Enables “Clean URLs” like recipes.sydneysu.dev. |
| SSL (Security) | Encrypts traffic to the “edge” (Cloudflare). | Encrypts traffic all the way to your Droplet (needed for .dev). |
| Port Management | All ports would have to be open to the world. | Closes all ports except 80/443; everything else stays hidden. |
The “One IP” Problem
Since the DigitalOcean Droplet only has one public IP address, Cloudflare can’t distinguish between apps. If you point vps.sydneysu.dev and recipes.sydneysu.dev both to the droplet IP, the Droplet wouldn’t know which request belongs to which container. NPM reads the “Host Header” to make that decision.
Port Hiding & Security
Docker apps usually run on “high ports” (9000, 8080, etc.).
- Without NPM: Would have to open every single one of those ports in the Firewall (UFW), increasing “Attack Surface.”
- With NPM: Only open Port 443. NPM sits on that port and “hands off” traffic internally via the Loopback (
127.0.0.1), keeping your app ports invisible to the public internet.
Automated SSL for .dev
The .dev domain has HSTS (Strict HTTPS) hard-coded into browsers. NPM provides a centralized dashboard to request, renew, and apply Let’s Encrypt certificates to all subdomains with one click, rather than configuring SSL inside every individual Docker container.
Steps
1. Create a dedicated directory for the project
mkdir ~/nginx-proxy-manage
cd ~/nginx-proxy-manager2. Create docker-compose.yml
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
- '80:80' # Public HTTP
- '81:81' # Admin Web Port (Temporary)
- '443:443' # Public HTTPS
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt3. Launch
docker compose up -d4. Setup Network & DNS
- Cloudflare:
- Add a DNS Record:
- Type:
A - Name:
vps - IPv4 Address:
<droplet ipv4 address> DNS Onlyproxy status to allow non-standard port traffic (Port 81)
- Type:
- Add a DNS Record:
- Firewall:
- Opened ports for the web and admin panel
sudo ufw allow 80/tcpsudo ufw allow 81/tcpsudo ufw allow 443/tcp
5. Setup Security
To secure the admin panel, route it through itself to enable SSL and then lock the public door
- Create a new Nginx admin account at
http://<droplet-ip>:81 - Add a new proxy host
- Domain:
vps.sydneysu.dev - Forward IP:
127.0.0.1(Loopback) - Forward Port:
81 - Block common Exploits
- SSL: Request new Let’s Encrypt certificate + Force SSL
- Domain:
- Hardening:
- Once
https://vps.sydneysu.devis verified, close the insecure public port sudo ufw deny 81/tcp
- Once
6. Edit docker-compose.yml
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
- '80:80'
- '127.0.0.1:81:81' # only listen to internal requests
- '443:443'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencryptTechnical Summary
- Reverse Proxy: NPM sits on ports 80/443 and directs traffic based on the subdomain
- Loopback (
127.0.0.1): Used to allow internal communication between the Proxy and the Admin panel, bypassing the external firewall - HSTS Awareness: Since
.devdomains require HTTPS, the proxy is essential for accessing internal HTTP services