Why Managing Multiple Self-Hosted Services Gets Complicated Fast
Running more than a handful of self-hosted services on a single server quickly turns into a port management nightmare. You end up with URLs like 192.168.1.100:8080, 192.168.1.100:8096, and 192.168.1.100:9000 – each one impossible to remember and impossible to share cleanly. A reverse proxy solves this by sitting in front of all your services and routing traffic based on the domain name, so jellyfin.yourdomain.com goes to port 8096 and portainer.yourdomain.com goes to port 9000, all through a single entry point on port 80 or 443.
Nginx Proxy Manager, commonly called NPM, makes that setup approachable without ever touching a config file. It wraps Nginx in a web-based GUI with built-in Let’s Encrypt SSL certificate management, access lists, and redirect rules. For home lab users and small self-hosters who want professional-grade proxying without a steep learning curve, it has become a go-to tool. This guide walks through the full setup from installation to your first working proxy host.

What You Need Before You Start
The requirements are deliberately minimal. You need a Linux server – a VPS, a Raspberry Pi, or a local machine – with Docker and Docker Compose installed. A registered domain name is necessary if you want SSL certificates and clean subdomains, though you can still use NPM on a local network with self-signed certificates. Port 80 and 443 must be open on your firewall or router, and if you are exposing services to the internet, those ports need to be forwarded to the server running NPM.
DNS setup matters more than most guides acknowledge upfront. For each subdomain you plan to proxy, you need an A record pointing to your server’s public IP address. If your IP changes, a dynamic DNS service like DuckDNS or Cloudflare with their API can handle updates automatically. Without correct DNS, Let’s Encrypt cannot verify domain ownership and certificate generation will fail every time.
Installing Nginx Proxy Manager with Docker Compose
Create a new directory for NPM, something like /opt/nginx-proxy-manager, and inside it create a docker-compose.yml file. The official compose configuration references two services: the NPM application itself and a MariaDB database instance. The app container needs port mappings for 80, 443, and the admin interface port 81. Volumes connect the database data, NPM configuration, and the Let’s Encrypt certificate store to persistent directories on the host so nothing is lost on container restarts.
A working compose file looks like this. Under the services key, define app using the image jc21/nginx-proxy-manager:latest. Map ports 80:80, 443:443, and 81:81. Set the environment variables DB_MYSQL_HOST, DB_MYSQL_NAME, DB_MYSQL_USER, and DB_MYSQL_PASSWORD to match your database container settings. Add volumes for ./data:/data and ./letsencrypt:/etc/letsencrypt. The database service uses the image jc21/mariadb-aria:latest with the corresponding MYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_USER, and MYSQL_PASSWORD environment variables set to match what the app expects.
Run docker compose up -d from the directory, then give it about a minute to initialize. Access the admin panel by navigating to http://your-server-ip:81 in a browser. The default login credentials are admin@example.com for the email and changeme for the password. NPM will immediately prompt you to update both – do this before anything else.
If you already run other Docker services on the same host, those containers need to share a Docker network with NPM for proxying to work without exposing every service port to the host. Create a shared external network called something like proxy using docker network create proxy, add that network to your NPM compose file under both the networks key and the app service, then add the same external network reference to each service you want to proxy. NPM will then be able to reach them by container name rather than IP address.

Creating Your First Proxy Host
Inside the NPM dashboard, navigate to Proxy Hosts and click Add Proxy Host. The domain name field takes the full subdomain you set up in DNS, like app.yourdomain.com. The forward hostname is either the container name (if using a shared Docker network) or the IP address of the machine running the target service. The forward port is whatever port that service runs on internally.
Enable the Block Common Exploits toggle and, unless you have a specific reason not to, enable Websockets Support as well – many modern web apps break without it and there is no downside to leaving it on. Once the basic details are saved, go to the SSL tab within the same proxy host and select Request a new SSL Certificate from the dropdown. Check Force SSL and HTTP/2 Support, enter an email address for Let’s Encrypt expiry notices, agree to the terms, and save. NPM handles the ACME challenge and certificate installation automatically, and the host entry will show a green padlock within seconds if DNS is resolving correctly.
Access Lists, Redirects, and Advanced Configuration
Not every proxied service should be publicly accessible. NPM’s access list feature lets you restrict a proxy host to specific IP ranges or require HTTP basic authentication before the service loads. Under the Access Lists section in the sidebar, create a new list, add allowed source IP addresses or CIDR ranges, and optionally add username and password pairs for basic auth. Assign that list to a proxy host from its settings panel under the Advanced tab or from the main proxy host edit screen.
Redirects handle the common case of pointing a bare domain or old URL to a new location. The Redirection Hosts section works identically to proxy hosts but forwards the client’s browser to a different URL rather than proxying traffic internally. This is useful for redirecting yourdomain.com to www.yourdomain.com, or routing a legacy subdomain to a new service address without breaking bookmarks.
For anything NPM’s GUI doesn’t expose directly, each proxy host has an Advanced tab accepting raw Nginx configuration directives. Custom headers, rate limiting rules, upstream timeout adjustments, and cache control directives all go here. The syntax is standard Nginx config, dropped into the location block NPM generates for that host. This escape hatch covers edge cases like proxying services that require specific X-Forwarded-For headers to behave correctly, which is common with applications that do IP-based access logging or geoblocking.
If you are building out a larger self-hosted stack – say, adding file sync with Seafile as a self-hosted Dropbox alternative – NPM handles the subdomain and SSL for that too, the same way it handles any other service. Every new container gets its own proxy host entry with its own certificate, all managed from one dashboard rather than scattered across individual service configurations.

Keeping NPM Running and Up to Date
Certificate renewals happen automatically. NPM checks expiry dates and triggers Let’s Encrypt renewals well before the 90-day expiry, so there is no manual renewal step. The one thing that can break renewal is a firewall rule or ISP block that appears after the original certificate was issued – if port 80 stops being accessible, the HTTP-01 ACME challenge fails silently until the certificate expires. Checking the Certificates section of the dashboard occasionally confirms all certs are showing healthy renewal dates.
Updating NPM itself means pulling the new image and restarting the stack. Run docker compose pull in the NPM directory followed by docker compose up -d. The data and certificate volumes persist across the update so no configuration is lost. Major version upgrades occasionally include database migrations that run automatically on first start, which is why checking the GitHub release notes before a major version jump is worth the two minutes it takes – some releases include breaking changes to environment variable names or volume paths that can catch you off guard.
Frequently Asked Questions
Do I need a domain name to use Nginx Proxy Manager?
A domain is required for Let’s Encrypt SSL certificates. For local network use only, NPM works with IP addresses and self-signed certificates.
Can Nginx Proxy Manager proxy services running on different machines?
Yes. Set the forward hostname to the IP address of the target machine and the forward port to whichever port the service runs on.





