Nginx Proxy Manager: Reverse Proxy + SSL in Minutes
How I use Nginx Proxy Manager to front all my self-hosted services with Let's Encrypt SSL and subdomain routing — no manual nginx config files.
Running 20+ Docker services on a home server means you quickly tire of remembering port numbers. Nginx Proxy Manager (NPM) solves this with a web GUI that provisions subdomains, terminates SSL, and proxies traffic to internal containers — all without writing a single nginx config file by hand.
The problem it solves
Without a reverse proxy, accessing services means hitting IP:PORT directly. That means no SSL, no meaningful hostnames, and opening a separate port for every service. Instead, NPM sits in front of everything on ports 80 and 443, routes requests by subdomain, and handles TLS termination using Let's Encrypt certificates it manages automatically.
# docker-compose.yml for NPM
services:
npm:
image: jc21/nginx-proxy-manager:latest
ports:
- "80:80"
- "443:443"
- "81:81" # admin UI
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencryptSetting up a new proxy host
In the NPM GUI, you create a 'Proxy Host': set the public subdomain (immich.domain.com), the internal hostname (the Docker container name or IP), and the internal port. Flip on 'SSL' and it requests a Let's Encrypt certificate and auto-renews it. The whole process takes under two minutes per service.
- ▸Wildcard certificates via DNS challenge mean one cert covers all subdomains
- ▸Access lists let you restrict services to local network only
- ▸HTTP/2 and WebSocket support work out of the box
- ▸Logs per-proxy-host make debugging 502s straightforward
DNS setup
For internal-only services, I use a local DNS override in Pi-hole that points *.internal.domain.com to the NPM server IP. For publicly accessible services, real DNS A records point to the external IP with port forwarding on the router.