Nginx Proxy Manager (NPM) + Docker — Cheat Sheet¶
A practical reference for wiring up NPM to proxy any Docker container, based on real troubleshooting.
1. The Golden Rule¶
NPM must be able to reach your backend container over the network, by name, on its internal port.
Not the host IP. Not the published/mapped port. The container name and the port the app listens on inside the container.
Why: if NPM and your backend container are on different Docker networks (which happens by default — each docker-compose.yml project gets its own isolated network), traffic from NPM has to go out to the host and back in, which is fragile and often silently blocked by firewalls (ufw, DOCKER-USER iptables chain, etc). Putting them on the same Docker network avoids that hop entirely.
2. One-Time Setup: Shared Network¶
Create one Docker network that NPM and every backend service will join:
Use one consistent name across your whole homelab (proxy-net, web, whatever) — it's the thread that ties every stack to NPM.
3. Compose File Pattern¶
NPM's docker-compose.yml¶
Any backend service's docker-compose.yml¶
Apply with:
(run in each project's folder — NPM's and every backend's)
Tip: once a service is on
proxy-net, you generally don't need to publish its port to the host at all (ports:section) unless something other than NPM needs to reach it directly. Less exposed surface = better.
4. Configuring the Proxy Host in NPM¶
In NPM's UI → Proxy Hosts → Add Proxy Host:
| Field | Value |
|---|---|
| Domain Names | yourapp.yourdomain.com |
| Scheme | http — almost always, since most containers don't serve TLS internally |
| Forward Hostname/IP | container name (e.g. vaultwarden), not the host IP |
| Forward Port | the port the app listens on inside the container (check the docs/image, not the host-published port) |
| Websockets Support | enable if the app uses websockets (Vaultwarden, many chat/collab apps, etc.) |
| Block Common Exploits | usually fine to enable |
| SSL tab | request a Let's Encrypt cert, force SSL, enable HTTP/2 |
5. Finding a Container's Internal Port¶
Don't guess — check:
Shows internal_port/tcp -> host_ip:host_port. The internal_port (left side) is what you put in NPM's Forward Port field — not the host-side number.
If nothing's published and you're not sure, check the image's documentation/Dockerfile for EXPOSE, or:
6. Diagnostic Toolkit¶
When a proxy host isn't working, isolate the problem layer by layer:
Reading the symptoms¶
| Symptom | Likely cause |
|---|---|
curl from inside NPM just hangs on "Trying..." |
Packet reaching nowhere — different Docker network, or firewall silently dropping it |
curl gives immediate "Connection refused" |
Reached the right host, but nothing is listening on that port — wrong port number |
| NPM shows 502 Bad Gateway | NPM reached something but got an invalid/empty response — usually wrong internal port, or scheme mismatch (https configured against a plain http backend) |
Works via curl from the host but not from NPM container |
Cross-network/firewall issue — the fix in this cheat sheet (shared proxy-net) |
| Browser shows cert errors but backend response otherwise fine | SSL/cert issue on NPM's side, unrelated to the forwarding path — check the SSL tab in NPM |
7. Quick Migration Checklist (existing stacks → shared network)¶
For every container currently proxied by NPM via host IP:
- [ ] Add
proxy-net(external) to that service's compose file - [ ] Add
proxy-net(external) to NPM's compose file (only needs doing once) - [ ]
docker compose up -din that service's folder - [ ] Confirm with
docker inspect <container> --format '{{json .NetworkSettings.Networks}}'that it's now onproxy-net - [ ] In NPM, change Forward Hostname/IP from the host IP to the container name
- [ ] Change Forward Port from the host-published port to the container's internal port
- [ ] Test with
curlfrom inside the NPM container before testing in the browser - [ ] Save the proxy host, confirm it loads correctly