Nginx Proxy Manager is a web UI in front of Nginx: create a hostname, point it at a container, tick "Request a new SSL certificate", done. For a home server with fifteen services it replaces an afternoon of hand-editing config files with a form.
What it is
A management layer over Nginx with automatic Let's Encrypt certificates, access lists, streams and redirects, plus a Docker-aware service list. Every self-hosted app that says "put it behind a reverse proxy" ends up here.
Before you start
- Ports 80 and 443 must be free on the host, and DNS must already point at it. Let's Encrypt HTTP-01 validation needs to reach port 80 from the internet.
- It mounts the Docker socket to read container names and IPs. That's a privilege you should be comfortable granting — it is effectively root on the host.
- Every app it proxies should bind its host port to
127.0.0.1. If an app listens on0.0.0.0it's directly reachable, and the proxy's certificate and access controls mean nothing.
1 — Create the directory
mkdir -p ~/services/nginx-proxy-manager/{data,db}
cd ~/services/nginx-proxy-manager
2 — Write the compose file
services:
app:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "81:81"
volumes:
- ./data:/data
- ./db:/database
- /var/run/docker.sock:/var/run/docker.sock:ro
healthcheck:
test: ["CMD", "/bin/check-health"]
interval: 30s
timeout: 5s
retries: 3
3 — Start it
docker compose up -d
docker compose logs -f app | grep -i "listen\|error"
4 — First-run setup
- Open
http://yourhost:81and create the admin account. The defaults areadmin@example.com/changeme— change them before anything else. - Add a proxy host. Scheme
http, forward to the container name and port (e.g.nextcloudand80). Add both hostnames (docs.example.comandwww.example.com). - SSL tab → Request a new SSL certificate → tick "Force SSL", "HTTP/2" and "HSTS". Save. The certificate appears in about ten seconds if DNS is right.
- Advanced tab → add the WebSocket support snippet if your app needs it (Mattermost, n8n, Uptime Kuma). It's a custom location block with the
Upgrade/Connectionheaders.
5 — The bit everyone misses
# give the proxy a stable LAN address for upstreams
# and set the container name as the forward hostname, not localhost
127.0.0.1 with the app's published port. A 502 Bad Gateway here is 90% of the time a networking mismatch, not an app problem.