Proxmox Reverse Proxy: Nginx Proxy Manager & Caddy

17 min read
Reverse proxy for your homelab: Nginx Proxy Manager or Caddy in a Proxmox LXC, routing ha, vault and grafana subdomains

The Homelab Bench is reader-supported. As an Amazon Associate we earn from qualifying purchases; this article contains affiliate links.

Running five or ten self-hosted services on Proxmox is great — until every bookmark is a different IP address and port number you can never remember. A reverse proxy solves that by sitting in front of everything, accepting traffic on ports 80 and 443, and forwarding it to the right service based on the hostname. By the end of this guide you will have a working reverse proxy running in a dedicated Proxmox LXC container — with your choice of Nginx Proxy Manager (a point-and-click web UI) or Caddy (a short text config that handles HTTPS automatically). Both give you clean URLs, free Let’s Encrypt certificates, and no cloud dependency.

Current as of November 2026: Proxmox VE 9.2, Nginx Proxy Manager 2.16, Caddy 2.11, Debian 13 (Trixie) containers.

What a reverse proxy actually does (and why you want one)

Without a reverse proxy, reaching your Home Assistant instance means typing something like http://192.168.1.50:8123. Add Vaultwarden, Grafana, and a media server and you now have four different ports to remember. A reverse proxy lets you use subdomains instead — https://ha.yourdomain.com, https://vault.yourdomain.com — all arriving on the standard HTTPS port 443. The proxy reads the hostname on each request and routes it internally to the right service and port.

Diagram: a browser request for ha, vault or grafana subdomains passes through the router on ports 80 and 443 to a reverse proxy LXC, which forwards each hostname to the right internal IP and port
Every request comes in through one door. The proxy terminates HTTPS and hands each hostname to the right backend.

Crucially, only the reverse proxy needs to be reachable from outside your network (or from other VLANs inside it). Every other service stays behind your firewall, with no extra ports forwarded. For a Home Assistant setup that also talks to IoT devices on a separate VLAN — covered in our IoT VLAN for Home Assistant guide — this single-entry-point model is a real security improvement.

Choosing between Nginx Proxy Manager and Caddy

Both tools do the same core job. The choice comes down to how you prefer to work:

  • Nginx Proxy Manager (NPM) gives you a full web UI. You click through forms to add proxy hosts, request Let’s Encrypt certificates, and manage access lists. No config files to edit. It is the gentler on-ramp if you would rather stay out of the terminal.
  • Caddy is configured through a single text file called the Caddyfile. Give it a public hostname and it obtains and renews a Let’s Encrypt certificate automatically — you write a hostname and a backend address and HTTPS just happens. The trade-off is that you edit a file rather than click a UI, which also makes your whole config easy to back up and copy.

This guide walks through both. Follow whichever part matches your preference; you do not need to do both.

Prerequisites before you start

  • A running Proxmox VE host. Screenshots are from Proxmox VE 9.2; the steps are the same on 8.x.
  • A domain name you control, with the ability to add DNS records (an A record pointing at your public IP, or a local DNS entry for internal-only setups).
  • Access to your router’s admin panel — you will forward ports 80 and 443 to the reverse proxy if you want public certificates using the standard HTTP challenge. (There is a no-open-ports alternative further down.)
  • A fixed IP address for the proxy container, set either in Proxmox or via a DHCP reservation in your router.

If you are new to LXC containers in Proxmox, start with creating your first VM and LXC container; the LXC cloning and templating guide is a good follow-on.

A quick tour of the Proxmox UI for first-timers

Log in to Proxmox at https://your-proxmox-ip:8006. The resource tree on the left lists your node (usually pve) with its VMs and containers underneath. Click the node name and its toolbar appears at the top right of the main panel: the Shell button there opens a root terminal directly on the Proxmox host, which is where you run the community install scripts in this guide. The Create CT button in the very top bar starts the wizard for a new LXC container. You can also SSH into the host instead of using Shell; both reach the same place.

Proxmox VE 9.2 node view showing the Shell button in the node toolbar and the Create CT button in the top header
With the node selected: Shell (node toolbar) opens a host terminal; Create CT (top bar) starts the container wizard.

Part 1 — Nginx Proxy Manager on Proxmox

Step 1: Create the NPM LXC using the community script

The Proxmox VE Community Scripts project maintains a one-line installer that creates an unprivileged Debian 13 LXC and installs Nginx Proxy Manager inside it (natively, not in Docker). Open a shell on your Proxmox host (click your node → Shell) and run:

bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/nginxproxymanager.sh)"

The script opens with a menu. Default Install gives you a container with 2 CPU cores, 2 GB RAM and an 8 GB disk; Advanced Install walks you through each setting, including the container ID, resources and network, if you want to set a static IP up front.

Proxmox community script options menu with Default Install highlighted
The community script’s opening menu. Default Install is fine for a reverse proxy.

The install builds OpenResty and the NPM web interface from source, so expect it to take around ten minutes on a small host. When it finishes, the script prints the admin URL — http://<container-ip>:81. Note that IP down.

Nginx Proxy Manager community script finished successfully, printing the admin URL on port 81
A successful run ends with the admin URL on port 81 (blurred here).

The default install uses DHCP. The proxy needs an address that never changes, because your router will forward traffic to it. Either set a DHCP reservation in your router for the container’s MAC address, or switch it to a static address in Proxmox: select the container → Network → double-click net0, choose Static and enter the address in CIDR notation plus your router as the gateway.

Proxmox LXC network device edit dialog with IPv4 set to Static, 192.168.1.51/24 and gateway 192.168.1.1
Container → Network → edit net0: switch IPv4 to Static and enter the address with its /24 suffix.

Step 2: Forward ports on your router

In your router’s port-forwarding section, create two rules pointing at the proxy container’s IP:

  • External port 80 → container IP, port 80
  • External port 443 → container IP, port 443

Port 80 is used by the Let’s Encrypt HTTP-01 challenge that proves you control the domain. Port 443 carries your actual HTTPS traffic. Never forward port 81 — that is the admin interface.

Step 3: Create your admin account

Open http://<your-npm-container-ip>:81 in a browser. Current versions of NPM (2.13 and later) no longer ship with a default login — the old [email protected] / changeme pair you will see in older guides is gone. Instead, a first-run setup wizard asks you to create the admin user.

Nginx Proxy Manager first-run setup wizard asking for full name, email address and a new password
NPM 2.16’s first-run wizard: name, email and password for the admin account.

Use a real email address here. NPM now uses your account’s email as the Let’s Encrypt contact address for every certificate you request, rather than asking for it per certificate. Do this straight after the install finishes: until the account exists, anyone on your network who reaches port 81 first could create it.

Step 4: Understanding the NPM dashboard

Once logged in, the menu along the top is where everything lives:

Nginx Proxy Manager 2.16 dashboard with the Hosts, Access Lists, Certificates and Settings menu
The NPM dashboard on a fresh install.
  • Hosts → Proxy Hosts — where you define each service you want to expose. Each entry maps an incoming hostname to a backend IP and port. The same menu holds Redirection Hosts, Streams (TCP/UDP forwarding, not needed for web services) and 404 Hosts.
  • Certificates — where your Let’s Encrypt certificates are listed. NPM renews them automatically.
  • Access Lists — optional IP allowlisting or HTTP basic auth you can attach to any proxy host.
  • Logs and Audit Logs — handy when a host misbehaves or you want to see what changed.

Step 5: Add your first proxy host

  1. Go to Hosts → Proxy Hosts and click Add Proxy Host.
  2. In Domain Names, type the subdomain you want to use — for example ha.yourdomain.com — and press Enter to add it.
  3. Set Scheme to http (unless the backend itself only speaks HTTPS).
  4. In Forward Hostname / IP, enter the internal IP of the service — for example your Home Assistant VM.
  5. In Forward Port, enter the port it listens on — 8123 for Home Assistant.
  6. Turn on Websockets Support. Home Assistant and many other dashboards use WebSockets for live updates and misbehave without it.
  7. Click Save.
Nginx Proxy Manager Add Proxy Host dialog with ha.yourdomain.com forwarded to 192.168.1.50 port 8123 and Websockets Support on
The Details tab for a Home Assistant host: hostname, backend IP and port, and Websockets Support on.

Step 6: Request a Let’s Encrypt certificate

Before requesting a certificate, make sure the subdomain’s DNS record points at your public IP — Let’s Encrypt has to reach your proxy through it. Then:

  1. Open the proxy host for editing (or do this while creating it) and switch to the SSL tab.
  2. From the SSL Certificate dropdown, choose Request a new Certificate.
  3. Turn on Force SSL so plain HTTP visits are redirected to HTTPS. HTTP/2 Support is safe to enable too.
  4. Click Save. NPM contacts Let’s Encrypt, completes the HTTP-01 challenge on port 80 and stores the certificate. This usually takes well under a minute.
Nginx Proxy Manager SSL tab with Request a new Certificate selected and Force SSL and HTTP/2 Support turned on
The SSL tab: Request a new Certificate, with Force SSL and HTTP/2 Support on.

When it succeeds, the host shows as online with a certificate attached, and https://ha.yourdomain.com loads with a valid padlock. NPM renews the certificate automatically before it expires.

Step 7: Tell Home Assistant to trust the proxy

Home Assistant rejects requests forwarded by a proxy it does not know about — you will see a 400: Bad Request page until you fix this. Edit configuration.yaml in your Home Assistant config directory and add (or extend) the http: section:

http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 192.168.1.51  # replace with your proxy container's IP

Save, then restart Home Assistant from Settings → ⋮ (top right) → Restart Home Assistant. Test the HTTPS URL again. If you still get a 400, the IP in trusted_proxies does not match the address the proxy connects from — check the container’s IP in Proxmox. If you run Home Assistant OS in a Proxmox VM, our Home Assistant Proxmox VM guide covers where the config lives, and the backup and restore guide shows how to take a backup before editing it.


Part 2 — Caddy on Proxmox

Caddy’s headline feature is automatic HTTPS: any site block with a public hostname gets a Let’s Encrypt certificate, renewals and an HTTP-to-HTTPS redirect with no extra configuration. For a homelab with a lot of services, that removes a lot of repetitive clicking.

Step 1: Create the Caddy LXC

You have two options.

Quick route — community script. The same Community Scripts project has a Caddy installer. From the Proxmox host shell:

bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/caddy.sh)"

It asks whether you want a Debian 13 or Alpine container, then creates it (Debian defaults: 1 core, 512 MB RAM, 6 GB disk) with Caddy already installed. If you use this, skip to Step 3.

Manual route — build the container yourself. Worth doing once so you know what the scripts are doing for you. First make sure you have a Debian 13 template: select your local storage → CT Templates → Templates, pick debian-13-standard and click Download.

Proxmox CT Templates download list with the debian-13-standard template selected
local storage → CT Templates → Templates: download debian-13-standard once and reuse it for every container.

Then click Create CT in the top bar and work through the wizard:

  • General: a hostname such as caddy, a root password, and leave Unprivileged container ticked.
  • Template: the debian-13-standard template you just downloaded.
  • Disks: 4 GB is plenty.
  • CPU: 1 core. Memory: 512 MB RAM, 512 MB swap.
  • Network: choose Static and enter an address in CIDR notation, for example 192.168.1.51/24, with your router’s IP as the gateway.
  • Confirm: tick Start after created and click Finish.
Proxmox Create LXC Container wizard, General tab, hostname caddy with Unprivileged container ticked
Create CT wizard, General tab: give the container a hostname and keep it unprivileged.

Once it is running, select the container and open Console (or SSH in) to get a shell inside it.

Step 2: Install Caddy from the official repository

Caddy publishes official Debian packages. Inside the container, run the following. The minimal Debian template does not include gpg, so it is added to the first line:

apt update && apt install -y debian-keyring debian-archive-keyring apt-transport-https curl gnupg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
chmod o+r /etc/apt/sources.list.d/caddy-stable.list
apt update && apt install -y caddy

The package installs Caddy as a systemd service and starts it straight away. Check it:

systemctl status caddy

You should see active (running). It starts automatically whenever the container boots.

Step 3: Forward ports on your router

Exactly as in the NPM section — forward external ports 80 and 443 to the Caddy container’s IP. Only one machine can receive a given external port, so if you built both an NPM and a Caddy container, forward to one of them only.

Step 4: Write your Caddyfile

Caddy’s configuration lives at /etc/caddy/Caddyfile:

nano /etc/caddy/Caddyfile

Replace the placeholder content with one block per service:

ha.yourdomain.com {
    reverse_proxy 192.168.1.50:8123
}

vault.yourdomain.com {
    reverse_proxy 192.168.1.52:8080
}

That is the whole configuration. For each hostname that has a public DNS record pointing at your IP, Caddy obtains a Let’s Encrypt certificate, renews it, and redirects HTTP to HTTPS. WebSockets are proxied automatically, so Home Assistant works without any extra lines, and Caddy sets the X-Forwarded-For header that Home Assistant needs.

For a LAN-only name that Let’s Encrypt cannot validate, tell Caddy to use its own built-in certificate authority instead:

ha.home.arpa {
    tls internal
    reverse_proxy 192.168.1.50:8123
}

tls internal issues a certificate signed by Caddy’s local root CA. Browsers will warn about it until you install that root certificate on your devices, and you will need a local DNS record (in AdGuard Home, Pi-hole or your router) pointing the name at the Caddy container. home.arpa is the domain reserved for exactly this kind of home-network use.

Step 5: Validate and reload

Check the file for mistakes, then reload without dropping connections:

caddy validate --config /etc/caddy/Caddyfile
systemctl reload caddy

Follow the log to watch certificates being issued:

journalctl -u caddy -f

For a public hostname you will see Caddy obtain the certificate within a few seconds. Certificates are stored under Caddy’s data directory (/var/lib/caddy/.local/share/caddy for the Debian package), so include the container in your backups.

Step 6: Tell Home Assistant to trust Caddy

Same as the NPM step — add the http: section to configuration.yaml with the Caddy container’s IP in trusted_proxies:

http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 192.168.1.51  # replace with your Caddy container's IP

Restart Home Assistant and test your HTTPS URL.

Step 7: Adding more services

To proxy another service, add a new block to /etc/caddy/Caddyfile, run caddy validate, then systemctl reload caddy. The reload is graceful and Caddy requests the new certificate in the background. One thing to know: a reload closes open WebSocket connections, so Home Assistant dashboards will briefly show a reconnecting message.


Shared next steps for both setups

No port forwarding? Use the DNS challenge

If you cannot or would rather not open ports 80 and 443 — for example behind CGNAT, or for services you only use at home — you can still get real Let’s Encrypt certificates with the DNS-01 challenge. The proxy proves ownership by creating a temporary DNS record through your DNS provider’s API, so nothing needs to be reachable from the internet. In NPM, turn on Use DNS Challenge on the SSL tab, pick your provider and paste its API token into the credentials box. NPM stores that token in plain text, so for Cloudflare create a token limited to Zone → DNS → Edit on just the one domain. Caddy needs a build that includes your provider’s DNS module, available from Caddy’s download page or built with xcaddy. Pair this with a local DNS record pointing the hostname at the proxy and you get trusted HTTPS on the LAN with no open ports.

Nginx Proxy Manager SSL tab with Use DNS Challenge on and Cloudflare selected as the DNS provider, showing the API token credentials template
NPM’s DNS challenge with Cloudflare: replace the example token with your own scoped API token.

Make sure the proxy starts at boot

Your reverse proxy is now the front door for everything, so it needs to come back on its own after a host reboot. Containers you create through the Create CT wizard do not start at boot unless you turn it on. Select the container → Options → double-click Start at boot and tick it. Check this on whichever container you used, whether it came from a script or the wizard.

Proxmox LXC Options panel showing Start at boot set to Yes
Container → Options: Start at boot should read Yes for your proxy.

Keeping the container updated

For either community-script container, run update from the container’s console — the scripts install that command, and it pulls the latest release of NPM or Caddy. For a manually built Caddy container, apt update && apt upgrade handles it. Include the proxy container in your scheduled backups; if you have not set those up yet, the Proxmox Backup Server guide or our guide to backing up to an external USB drive is the natural next step.

Lock down the NPM admin port

Port 81 (the NPM admin UI) should never be reachable from outside your network — make sure it is not in your router’s port-forward list. You can go further by enabling the Proxmox firewall on the container and allowing port 81 only from your trusted LAN or management VLAN. Turning on two-factor authentication for your NPM account (in your user profile) is also worth the minute it takes.

Protect the host with a UPS

With every service behind one container, a power blip on the Proxmox host takes everything offline until it boots again. A UPS gives the host a clean shutdown window and protects your storage from sudden power loss. Our best UPS for your homelab guide walks through what to look for.

Check price: Amazon UK | Amazon US | Amazon IE


Troubleshooting quick-reference

Symptom Most likely cause Fix
Certificate request fails or times out Port 80 not forwarded to the proxy, or the DNS record does not point at your public IP Check the router rules and the container IP; check the A record. Or switch to the DNS challenge.
Home Assistant returns 400: Bad Request Missing or wrong trusted_proxies in configuration.yaml Set use_x_forwarded_for: true and add the proxy container’s exact IP, then restart HA
HA dashboard loads but never updates WebSockets not passed through NPM: turn on Websockets Support for the host. Caddy handles WebSockets automatically — check the backend address and port instead.
Port 81 shows a blank page or will not load after install NPM services still starting, or a service failed Wait a minute and refresh; inside the container run systemctl status npm openresty and journalctl -u npm -e
Login page asks for [email protected], which does not work Following an older guide NPM 2.13+ has no default account — complete the setup wizard instead
Browser warning on a LAN-only Caddy hostname Using tls internal without trusting Caddy’s root CA Install Caddy’s root certificate on your devices, or use the DNS challenge for a publicly trusted certificate

FAQ

Do I need a domain name, or can I use the reverse proxy locally?

You can use it locally. Point hostnames at the proxy with a local DNS resolver such as AdGuard Home or Pi-hole. For HTTPS without browser warnings, the cleanest route is a cheap real domain plus the DNS challenge described above — you get publicly trusted certificates even for names that only resolve on your LAN. Without a domain, Caddy’s tls internal gives you HTTPS from its own CA, but every device then needs to trust that CA’s root certificate.

Can I run NPM and Caddy at the same time on the same Proxmox host?

Yes. Each lives in its own container with its own IP, so both can listen on ports 80 and 443 without conflict. What you cannot do is forward the same external ports from your router to both. Pick one for external traffic; the other can still serve internal hostnames if your local DNS points at it.

What happens to my reverse proxy if Proxmox reboots?

If Start at boot is enabled on the container, it comes back up with the host and NPM or Caddy starts automatically inside it, usually within a minute of the host finishing its boot. If it does not come back, check that setting first — it is off by default for containers created through the wizard. Your backend services do not need to start before the proxy; requests simply fail with a 502 until each backend is up.

Stay Updated

Enjoyed this article?

Get the best stories delivered straight to your inbox. No spam, ever.

No spam. Unsubscribe any time.