Rebuilding my reverse proxy: Nginx Proxy Manager, a wildcard cert and single sign-on with Authelia

Rebuilding my reverse proxy: Nginx Proxy Manager, a wildcard cert and single sign-on with Authelia

For a long time my homelab “dashboard” was a browser folder full of bookmarks like http://10.0.0.15:8080. Every app had its own port, half of them threw certificate warnings, and every one had its own login page. It worked, but it was messy, and messy is how mistakes happen.

So I tore the old proxy down and rebuilt it from scratch with three goals:

  1. Every app gets a proper name, like nas.home.example.com.
  2. Everything is HTTPS with a real, trusted certificate, with no browser warnings.
  3. One login in front of everything, so I’m not managing ten separate passwords.

VM or LXC?

I’m running Proxmox, so the first decision was whether the proxy lives in a container (LXC) or a full VM. I went with a small Ubuntu VM running Docker. An LXC is lighter, but Docker inside LXC needs extra fiddling (nesting, privileged containers, AppArmor). A VM keeps things simple and isolated, and it gives me a general-purpose Docker host I can put other things on later. (More on that in a future post.)

The stack

Nginx Proxy Manager (NPM) gives you a friendly web UI on top of Nginx. Authelia handles logins. They live in the same Docker Compose file so they share a network and can talk to each other by name:

services:
  npm:
    image: jc21/nginx-proxy-manager:latest
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "81:81"        # admin UI – keep this LAN-only
    volumes:
      - ./npm/data:/data
      - ./npm/letsencrypt:/etc/letsencrypt

  authelia:
    image: authelia/authelia:latest
    restart: unless-stopped
    volumes:
      - ./authelia/config:/config
    environment:
      TZ: Europe/London

One certificate to rule them all

Rather than requesting a certificate per app, I requested a single wildcard certificate (*.home.example.com) from Let’s Encrypt using the DNS challenge.

The DNS challenge is the important bit. Let’s Encrypt proves you own the domain by checking a DNS record rather than connecting to your server. That means none of these apps need to be reachable from the internet to get a valid certificate.

My domain’s DNS is on Cloudflare, so in NPM it’s: SSL Certificates → Add → Let’s Encrypt → Use a DNS Challenge → Cloudflare, then paste an API token.

Tip: Create a Cloudflare API token scoped to Zone → DNS → Edit for that one zone only. Don’t use your global API key. If the token ever leaks, the damage is limited to DNS on one domain.

Making the names work at home

The certificate is public, but the names don’t need to be. I don’t create public DNS records for my internal apps at all. Instead, my local DNS server (Pi-hole) has local records pointing each name at the proxy:

nas.home.example.com    →  10.0.0.20   (the proxy)
media.home.example.com  →  10.0.0.20

From inside the house, every name resolves to the proxy and NPM forwards it to the right app. From outside, those names don’t exist. That’s split-horizon DNS in its simplest form.

Adding single sign-on with Authelia

Authelia sits in front of your apps. When you visit a protected app, Nginx first asks Authelia “is this person logged in?” If not, you’re bounced to one login page (with optional two-factor), and once you’re in, you’re in for every protected app.

The setup is in two parts:

  1. Authelia’s config (configuration.yml): define your domain, a users file, session settings and access rules. Start with a simple rule like “everything under *.home.example.com requires one-factor”, then tighten it.
  2. Each proxy host in NPM: paste Authelia’s forward-auth snippet into the Advanced tab. Authelia’s documentation has an Nginx Proxy Manager integration page. Follow it exactly for your version, because the endpoint paths changed in recent releases.

The first time you hit an app and get redirected to a clean login page instead of the app’s own one, it feels like a proper bit of infrastructure.

Lesson learned: not everything likes being proxied

Most apps were perfectly happy behind the proxy. My media server was not. It worked fine in a browser, but the TV app had constant connection problems through the proxied address. The fix was simply to point the TV back at the server directly on the local network.

The takeaway: the proxy is a convenience, not a religion. If an app or a client misbehaves behind it, it’s fine to leave it direct.

Things I’d tell myself before starting

  • Keep the NPM admin port (81) off the internet. Ideally, put the admin UI itself behind the proxy and Authelia.
  • Back up the data and letsencrypt folders. Rebuilding proxy hosts by hand is tedious.
  • Change the default NPM login immediately.
  • Don’t protect apps that have their own API clients (mobile apps, TV apps) with Authelia, or they’ll break. Use Authelia’s access rules to bypass specific paths, or leave those apps out.

What’s next

With a solid proxy and a spare Docker host, the next job was moving apps off my NAS and onto it. That’s the next post.

 

Leave a Comment

Your email address will not be published. Required fields are marked *