Nginx Proxy Manager: one front door for every service

May 4, 2026

networkingreverse-proxyservice-spotlight

Picture it: you’ve been self-hosting for a while. Immich is running on one machine, Portainer on another, your notification service on a third. Each one answers on a different port — :8080, :9000, :3000. You’ve got a mental map of which number goes where, and that map is slowly becoming a liability. You type http://192.168.x.x:8080 into the browser, and the browser reminds you — loudly — that this connection is Not Secure.

There’s a better way, and it fits inside a single lightweight VM.

What a reverse proxy actually does

A reverse proxy sits in front of your other services and acts as a traffic director. Instead of typing an IP address and port, you type immich.yourdomain.com — and the proxy figures out which backend service you mean, forwards your request to it, and hands the response back. From your browser’s perspective, it talked to one clean address. From the service’s perspective, a trusted neighbor forwarded traffic its way.

The “reverse” part distinguishes it from a forward proxy (which sits in front of clients and makes their traffic look like it comes from one place). A reverse proxy sits in front of servers and makes your cluster of services look like a single, coherent website.

Beyond routing, a reverse proxy also handles TLS termination — meaning it’s the one that speaks HTTPS to the outside world and deals with certificates. Your backend services can speak plain HTTP to each other on your internal network, and the proxy handles the encryption at the edge.

The problem it solves

Without a reverse proxy, a growing homelab looks like this:

  • Services scattered across multiple IPs and ports
  • Every service either runs unencrypted or manages its own certificate independently
  • No central place to enforce HTTPS, add basic auth, or control access
  • Browser address bars full of port numbers and “Not Secure” warnings

With a reverse proxy in place, the picture changes:

  • Every service gets a real hostname (service.yourdomain.com)
  • One wildcard SSL certificate covers everything — renewed automatically
  • You enforce HTTPS once, at the edge, instead of service-by-service
  • You have a single place to add access controls, rate limiting, or allowlists

This matters even if your services are internal-only. Valid HTTPS certificates eliminate browser warnings, enable HTTP/2, and let you use features (like modern browser APIs) that require secure origins.

What the cloud equivalent looks like

If you were running this in a cloud environment, you’d reach for something like an AWS Application Load Balancer, an Azure Application Gateway, or Google Cloud’s external HTTPS load balancer. These services do roughly the same thing — route traffic to backend services, terminate TLS, optionally add WAF rules — and they integrate with the cloud provider’s certificate management.

They’re polished, reliable, and deeply integrated with the surrounding ecosystem. They’re also priced per hour, per connection, and per rule processed. For a homelab with a dozen services, that adds up to a bill that doesn’t feel proportional to what you’re getting.

Cloudflare Tunnels occupy a middle ground worth mentioning: zero-cost at the free tier, no port forwarding required, and Cloudflare handles the TLS. The trade-off is that all your traffic flows through Cloudflare’s network, which matters for privacy-sensitive services or anything that needs low latency. Tunnels work well as a companion to a local proxy — expose the services you want public through Cloudflare, and keep internal services behind a self-hosted proxy.

The self-hosted options

When you go looking for a reverse proxy to run yourself, four names come up most often.

Raw nginx is the foundation everything else is built on. Nginx is fast, well-documented, and capable of nearly anything. It’s also entirely config-file-driven — which means you write location blocks and upstream directives by hand. For someone who already thinks in nginx config, this is fine. For someone who just wants to point a hostname at a service, it’s friction.

Caddy takes the opposite approach. Its Caddyfile format is terse and human-readable, and it handles HTTPS automatically by default — if you give it a domain, it fetches a Let’s Encrypt certificate without you asking. Caddy is an excellent choice, especially if you’re comfortable with a config file and want something lightweight. It has a smaller community than nginx, but it’s mature and actively maintained.

Traefik is popular in Docker-heavy environments because it can discover your containers automatically. You add labels to your Docker Compose services and Traefik figures out the routing without you creating proxy rules manually. If your whole lab runs in containers and you want a “configure it once and forget it” approach, Traefik has a strong case. The trade-off is complexity — the mental model requires understanding middlewares, entrypoints, and routers, and the dashboard, while useful, isn’t a full management UI.

Nginx Proxy Manager (NPM) is what this lab runs, and the reason is straightforward: it’s nginx under the hood, so it inherits all the performance and compatibility of a proven engine, but it wraps the configuration in a web UI that anyone can operate without memorizing nginx syntax. You click a few fields, choose a domain name, pick the backend address, attach a certificate, and enable HTTPS. Done.

NPM isn’t the most powerful option if you want fine-grained programmatic control or container autodiscovery, but for a lab that mixes VMs, containers, and physical devices — where services aren’t all running under one Docker orchestrator — the UI-first approach fits naturally.

How it works in practice

NPM is typically deployed as a pair of Docker containers: the NPM service itself and a small MariaDB database that stores your proxy configuration. The whole stack is small — a couple of vCPUs and a modest amount of RAM is genuinely sufficient. It runs quietly and needs almost no day-to-day attention once it’s configured.

Proxy hosts are the core concept. Each proxy host maps a domain name to a backend service. You configure:

  • The domain (e.g., monitoring.yourdomain.com)
  • The backend’s IP and port
  • Which SSL certificate to use
  • Whether to force HTTPS, enable HSTS, support WebSockets, or cache assets

Most services need thirty seconds of configuration. WebSocket-heavy apps (chat, live dashboards) need the “Websockets Support” checkbox. Services with large OIDC redirect headers — like an SSO provider — may need a small nginx buffer size tweak in the Advanced tab to avoid 502 errors on login. More on that in the gotcha section below.

SSL certificates are managed in a dedicated section. The recommended approach for a homelab is a single wildcard certificate covering your domain, obtained via DNS challenge rather than HTTP challenge. DNS challenge means Let’s Encrypt verifies your domain by checking a TXT record you create, which works even for services that aren’t publicly reachable. If your DNS is hosted on Cloudflare, NPM has native support: you paste in an API token with DNS-edit permission, specify your domain, and NPM creates and renews the certificate automatically. Let’s Encrypt certificates renew automatically roughly 30 days before expiry — once it’s set up, you largely forget about it.

Access lists are NPM’s built-in mechanism for HTTP basic auth and IP allowlisting. They’re useful for services that have no native authentication, or as an extra layer in front of something sensitive. For more sophisticated SSO, NPM is typically paired with a separate identity provider (like Authentik or Authelia) rather than relied on alone.

One gotcha worth knowing

If you proxy an OIDC identity provider through NPM — something like an SSO system — you may hit a 502 Bad Gateway error right after a user authenticates. The error appears in the proxy host’s error log as:

upstream sent too big header while reading response header from upstream

This happens because OIDC redirect responses embed a JWT in the Location header, and nginx’s default proxy buffer size is too small for it. The fix is to add buffer directives to the Advanced configuration for the identity provider’s proxy host:

proxy_buffer_size 16k;
proxy_buffers 4 16k;
proxy_busy_buffers_size 32k;

This is a known nginx gotcha that NPM doesn’t automatically handle. Once you know to look for it, it’s a quick fix — but it can be confusing to debug if you don’t know where to look.

Who should bother

NPM makes sense if:

  • You’re running multiple services and want clean service.yourdomain.com addresses instead of a zoo of port numbers
  • You want automated HTTPS with no certificate management overhead
  • You prefer a web UI over editing config files
  • Your services are on different hosts or aren’t all running under a single Docker orchestrator

You might choose something else if:

  • You’re running everything in Docker Compose and want automatic container discovery — Traefik is a better fit
  • You’re already comfortable writing nginx config and don’t want the overhead of a UI wrapper
  • You only have one or two services and port numbers don’t bother you

For Sun’s homelab, NPM is classified as a foundation service — one of the first things deployed, because nearly everything else sits behind it. Getting it right early means every subsequent service gets HTTPS and a clean address automatically.

The shape of the thing

Here’s the rough mental model:

Browser → yourdomain.com → NPM (handles TLS, routes by hostname)
                              ├─▶ service-a (internal IP, HTTP fine here)
                              ├─▶ service-b
                              └─▶ service-c

Traffic from outside hits NPM, TLS is terminated, and the decrypted request is forwarded to whatever backend serves that hostname. Services on the same network talk over plain HTTP without anyone needing to manage certificates at each endpoint. It’s a clean separation of concerns: the proxy handles the public face, the backends handle the application logic.

Backups are worth mentioning briefly: NPM stores its configuration and certificates in Docker volumes. A proper backup includes both the database and the Let’s Encrypt volume so you can restore to a fresh instance without re-requesting certificates. Pair this with whatever backup automation you already have running.

Updating and maintaining it

One of NPM’s practical advantages is that updates are low-drama. Because it runs as a Docker container, updating is a docker compose pull followed by docker compose up -d. The new image replaces the old one, your configuration (stored in named volumes) survives unchanged, and the proxy is back up in seconds. There’s no package manager conflict, no config file migration to worry about.

Certificate renewal is equally hands-off. Let’s Encrypt certificates expire after 90 days, and NPM automatically renews them 30 days before expiry. You can verify expiry dates in the SSL Certificates section of the UI, and manually trigger a renewal if you want to confirm the automation is working. In practice, once it’s configured correctly, you’ll forget certificates are a thing that needs maintenance at all.

The one operational task worth building a habit around is backing up the Docker volumes that hold NPM’s data and certificates. The configuration is easy to re-enter, but a fresh certificate request counts against Let’s Encrypt’s rate limits, and some DNS API configurations take time to set up. A periodic backup of the data volume — even just a weekly compressed tarball to your backup destination — means recovery from a failed host is a restore plus a container start, not a rebuild from scratch.

Closing thought

A reverse proxy is one of those pieces of infrastructure that pays for itself immediately and keeps paying. The overhead is low — a lightweight VM or a small container host — and the return is clean URLs, valid HTTPS, and a single place to manage access to your entire service catalog.

If you’re at the stage where you’ve got four or five services running and you’re tired of remembering which port goes where, this is the right next piece to add. NPM’s UI lowers the bar enough that you can have a proxy host configured and serving HTTPS in the time it takes to make a cup of coffee.

Once it’s running, you’ll wonder how you managed without it.

← all posts

Comments

No comments yet — be the first.

Leave a comment

Moderated before it appears.
Theme
Font