One Gateway to Rule Them All: Nginx Proxy Manager on Proxmox
The Homelab IP Nightmare
Picture this: Your homelab is growing. You've got Jellyfin at 192.168.1.14:8096. Technitium at 192.168.1.11:5380. NPM's own UI at 192.168.1.12:81. Proxmox itself at 192.168.1.1:8006.
Now try telling a family member how to reach the media server. Or opening it on your phone. Or remembering which port that one service uses two months from now.
Translation: Raw IPs and ports don't scale past three services. You need a real front door.
That front door is a reverse proxy. It listens on ports 80 and 443, reads the domain name in each incoming request, and forwards it to the correct backend. Instead of 192.168.1.14:8096, you type jellyfin.home.lan. Same service. Zero mental overhead.
Bonus round: since every request now flows through one place, that same place can handle SSL, block common exploits, and gate certain services behind an access list. One tool. Multiple wins.
Picking the Right Tool
Four names come up in every "which reverse proxy?" thread: Nginx Proxy Manager, Traefik, Caddy, and HAProxy. Each has strong opinions and a devoted fanbase. Here's how they actually stack up for a homelab.
| Tool | Web UI | Best For | Downside |
|---|---|---|---|
| NPM | Yes, clean | Homelabs, learning | Limited to what the UI exposes |
| Traefik | Read-only | Docker/Kubernetes stacks | Config via labels, steep curve |
| Caddy | None (CLI only) | Simple auto-HTTPS setups | No UI, config-file only |
| HAProxy | None built-in | Enterprise load balancing | Overkill for a homelab |
I went with NPM. The GUI means I can add a service in 30 seconds without touching a config file, and it runs happily in an LXC with 512 MB of RAM. For a homelab that's supposed to help me learn, not swallow my weekends, that's the sweet spot.
My Network Layout
Before diving in, some context on why the IPs look the way they do. My Proxmox host sits on an HP EliteDesk with a Ryzen 5 PRO 2400GE and 8 GB of RAM, and it bridges two networks:
- vmbr0 — bridged to the Livebox on
192.168.2.0/24 - vmbr1 — internal-only bridge on
192.168.1.0/24where all my services live - iptables rules on the host route traffic between the two
Keeping services on an isolated internal bridge means devices on the LAN can only reach them through the Proxmox host and its firewall rules. It's one small step toward network segmentation, and it makes future traffic filtering trivial.
Step 1: Spin Up the Container
NPM doesn't need a full VM. An LXC container with Docker inside is faster, lighter, and boots in under a second. From the Proxmox shell:
# Grab a Debian 12 template
pveam download local debian-12-standard_12.7-1_amd64.tar.zst
# Create the container (nesting=1 is required for Docker)
pct create 100 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
--hostname delta \
--memory 512 \
--cores 1 \
--net0 name=eth0,bridge=vmbr1,ip=192.168.1.12/24,gw=192.168.1.1 \
--storage local-lvm \
--rootfs local-lvm:4 \
--unprivileged 1 \
--features nesting=1 \
--start 1
I named it delta because I'm running out of NATO alphabet slots and the Greek one felt less overused. 512 MB is plenty — NPM idles at around 150 MB in my setup.
Step 2: Docker + NPM
Inside the container, install Docker via the official convenience script and deploy NPM with a docker-compose file:
# Enter the container
pct enter 100
# Install Docker
apt update && apt upgrade -y
apt install -y curl ca-certificates gnupg
curl -fsSL https://get.docker.com | sh
# Set up the project directory
mkdir -p /opt/npm && cd /opt/npm
Then the compose file itself:
# /opt/npm/docker-compose.yml
version: "3"
services:
npm:
image: jc21/nginx-proxy-manager:latest
container_name: npm
restart: unless-stopped
ports:
- "80:80" # HTTP
- "443:443" # HTTPS
- "81:81" # Admin UI
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
environment:
DB_SQLITE_FILE: "/data/database.sqlite"
# Launch it
docker compose up -d
Point a browser at http://192.168.1.12:81 and log in with admin@example.com / changeme. NPM forces a password change immediately, which is the correct move.
Step 3: Bridging the Two Networks
My devices sit on 192.168.2.0/24, but NPM is on 192.168.1.12. Without a bridge between them, my laptop can't reach NPM at all. That's where iptables DNAT comes in — the Proxmox host catches traffic on ports 80/443 and rewrites the destination to NPM.
# On the Proxmox host — redirect HTTP/HTTPS to NPM
iptables -t nat -A PREROUTING -i vmbr0 -p tcp --dport 80 \
-j DNAT --to-destination 192.168.1.12:80
iptables -t nat -A PREROUTING -i vmbr0 -p tcp --dport 443 \
-j DNAT --to-destination 192.168.1.12:443
# Persist across reboots
apt install -y iptables-persistent
netfilter-persistent save
Step 4: DNS with Technitium
A reverse proxy needs a DNS server pointing every domain to it. I'm running Technitium in another LXC (192.168.1.11), with a wildcard record for *.home.lan:
*.home.lan → 192.168.2.100 (Proxmox host, vmbr0 side)
↓ iptables DNAT
192.168.1.12 (NPM, vmbr1 side)
↓ matches Host header
192.168.1.14:8096 (Jellyfin backend)
The wildcard is the whole trick. Every subdomain of home.lan resolves to the same IP, and NPM picks the right backend from the Host header. Add a new service? Just create a proxy host in NPM — no DNS change needed.
Step 5: Wiring Up a Service
Every service follows the same routine: deploy the container, note the IP and port, add a proxy host in NPM. Here's my Jellyfin example, end to end.
Open NPM → Hosts → Proxy Hosts → Add Proxy Host
Nothing tricky. The "Add" button is in the top right.
Fill in the Details tab
Four fields do the heavy lifting:
- Domain Names:
zeta.home.lan - Scheme:
http(NPM handles the TLS) - Forward Hostname/IP:
192.168.1.14 - Forward Port:
8096
Turn on Block Common Exploits for free hardening, and Websockets Support because Jellyfin needs it for playback controls.
Save and test
Open https://zeta.home.lan. Jellyfin appears. That's the entire workflow for every future service.
HTTPS Without Let's Encrypt
Let's Encrypt won't issue certificates for private TLDs like home.lan — the CA has no way to validate a domain that only exists on your LAN. So for local-only services I use mkcert, which spins up a personal certificate authority and issues certs trusted by every device that installs its root CA.
# On my PC (Windows)
winget install FiloSottile.mkcert
# Install the root CA in the Windows trust store
mkcert -install
# Generate a wildcard cert for the whole zone
mkcert "*.home.lan" home.lan
Two files pop out: _wildcard.home.lan+1.pem (the certificate) and _wildcard.home.lan+1-key.pem (the key). Upload both in NPM under SSL Certificates → Add SSL Certificate → Custom, then on each Proxy Host go to the SSL tab, pick the wildcard cert, and enable Force SSL.
For phones and other devices, copy the root CA file from mkcert -CAROOT and install it as a trusted certificate. After that, every service in the homelab shows a green padlock. No warnings, no clicking through scary red screens.
What I Actually Use It For
Here's what's currently sitting behind NPM in my setup. Each one is just a Proxy Host entry — five fields, thirty seconds.
Jellyfin. Movies, series, and my music library, streamed to any device on the LAN.
Technitium DNS admin panel. DNS + ad-blocking config, all in one place.
NPM itself. Yes, the reverse proxy proxies its own admin UI. Turtles all the way down.
Proxmox web UI. HTTPS + Websockets on, so the console works flawlessly.
Uptime Kuma. Monitors every other service and pings me when something goes down.
Lessons Learned
I started by adding an A record per service. Then I discovered specific records override the wildcard, and they were pointing to the wrong IP. One * record is enough — let NPM do the routing.
Windows had four DNS servers listed and mine was third in line. It was only ever consulted when the first two timed out. Force the DNS manually on the interface until the setup is stable.
Keeping services on vmbr1 costs nothing but a few iptables rules, and it means an exposed service can't be reached without going through the host's firewall first.
mkcert takes 5 minutes to set up and eliminates browser warnings forever. There's zero excuse to run internal services over plain HTTP once you know the trick.
What's Next
NPM is now the single entry point to my homelab, and the pattern for adding services is bulletproof. A few things on the roadmap:
Access Lists — restrict the admin UIs (NPM, Proxmox, Technitium) to my LAN only, so a rogue Proxy Host misconfig can't accidentally expose them.
Wireguard VPN — reach my homelab from outside the network without opening any ports on the Livebox. Combined with the existing DNS setup, my domains should Just Work over the tunnel.
Authelia or Authentik — add SSO in front of the services that don't have their own auth. Because typing passwords into a dozen dashboards gets old.
Building Your Own?
The whole setup took an afternoon, most of it spent chasing DNS quirks. If you've got a Proxmox box gathering dust and more than two services running, NPM is a very cheap upgrade to your quality of life.