How to Set Up Nginx Proxy Manager (Reverse Proxy and HTTPS with a Web Interface)

Tutorial 28 built a reverse proxy by writing Nginx server blocks. Nginx Proxy Manager is the same Nginx with a web page in front: add a host, type the app's address, tick "Request a new SSL certificate", save. If you add apps often, or someone else in the house needs to, this is the version they can use.
Quick reference
| Task | What |
|---|---|
| Folder | ~/docker/npm/compose.yaml |
| Admin page | http://server-ip:81 |
| Default login | admin@example.com / changeme (change immediately) |
| Add an app | Hosts → Proxy Hosts → Add |
| HTTPS | SSL tab → Request a new SSL certificate |
| Wildcard | SSL Certificates → Add → Use a DNS challenge |
1. The compose file
Stop a host-installed Nginx or Caddy first — this needs ports 80 and 443.
mkdir -p ~/docker/npm && cd ~/docker/npm
nano compose.yaml
services:
npm:
image: jc21/nginx-proxy-manager:latest
container_name: npm
ports:
- "80:80"
- "443:443"
- "127.0.0.1:81:81"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
restart: unless-stopped
Port 81 is the admin page, bound to localhost — reach it over an SSH tunnel or from inside the VPN by opening it to your LAN instead ("81:81" plus a UFW rule from your subnet).
docker compose up -d
sudo ufw allow 80/tcp && sudo ufw allow 443/tcp
2. First login
Open http://192.168.1.50:81, log in with the default credentials, and it immediately asks you to set your own name, email and password.
3. Add a proxy host
Hosts → Proxy Hosts → Add Proxy Host:
- Domain Names: status.example.com
- Scheme: http, Forward Hostname / IP: 192.168.1.50 (or the container name if NPM shares its Docker network), Forward Port: 3001
- Tick Block Common Exploits and Websockets Support
- SSL tab: Request a new SSL certificate, tick Force SSL and HTTP/2, agree to the terms, Save
Thirty seconds later https://status.example.com works with a padlock. Repeat per app.
4. A wildcard certificate
One certificate for *.example.com saves a request per app and works for internal names that aren't reachable from the internet:
SSL Certificates → Add SSL Certificate → Let's Encrypt, domain names *.example.com and example.com, tick Use a DNS Challenge, pick your DNS provider (Cloudflare, DuckDNS and many others), paste the API token. Then in each proxy host's SSL tab, choose that certificate from the list instead of requesting a new one.
5. Access lists
Access Lists → Add: give it a name, add a username and password under Authorization, or allow only your LAN under Access (192.168.1.0/24). Then pick the list on any proxy host's Details tab. Handy for Portainer or the Syncthing UI.
Custom Nginx
Each host has an Advanced tab for raw Nginx config — client_max_body_size 10G; for Nextcloud, proxy_buffering off; for Jellyfin.
Troubleshooting
"Internal Error" when requesting a certificate. Port 80 isn't reachable from the internet, or DNS doesn't point at you yet. Check docker compose logs npm for the Let's Encrypt message.
502 Bad Gateway. NPM can't reach the forward address. From the server, curl http://192.168.1.50:3001. If the app is bound to 127.0.0.1, NPM in Docker can't see it — bind it to the LAN IP or put them on the same Docker network.
Lost the admin password. docker compose exec npm sqlite3 /data/database.sqlite "UPDATE user SET is_deleted=0" then reset via the default login route — or simply delete ./data and start over, re-adding hosts.
Admin page on 81 reachable from outside. Don't. Keep it on localhost or LAN only.
Wrapping up
One container, a form per app, a tick box for HTTPS, a wildcard certificate via DNS for everything internal. Everything Nginx can do, with the config written for you.
