How to Get HTTPS for Every Homelab Service (NPM + Let's Encrypt)
A repeatable workflow for putting trusted HTTPS in front of every service in your homelab using Nginx Proxy Manager and free Let's Encrypt certificates. No self-signed warnings, no per-service certs.
Once you have three or four services running at home, plain http://192.168.1.10:8096 gets old fast. You remember ports, you retype IP addresses, and every browser nags you that the connection is not secure. The fix is to put trusted HTTPS in front of everything, so jellyfin.yourdomain.com just works with a green padlock, on every device, with no warnings. This guide is the repeatable workflow I use to do exactly that with Nginx Proxy Manager and free Let’s Encrypt certificates.
This is the certificate layer of your network. If you are still assembling the pieces around it, the homelab networking from scratch guide walks the full build order from DNS to reverse proxy to remote access, and the stack page shows how it all fits together in a real setup. Here we are zoomed in on one job: getting valid HTTPS on every service and keeping it that way.
Disclosure: some links below are affiliate or referral links. They cost you nothing extra and I only point at tools I actually run.
Why bother with HTTPS on your own network
It is a fair question. The traffic never leaves your house, so why encrypt it? A few reasons that matter in practice:
- App compatibility. More and more self-hosted apps assume HTTPS. Service workers, camera and microphone access, push notifications, and some login flows silently break over plain HTTP. Immich and Vaultwarden are two you will hit early.
- No more warnings. Self-signed certificates encrypt the connection but nothing trusts them, so every browser throws a scary warning you have to click through. That gets old, and it trains you to ignore real warnings.
- Remote access. The moment you reach a service through Tailscale or a Cloudflare Tunnel, you want the same trusted certificate working from your phone on cellular as from your laptop at home.
- It is free. Let’s Encrypt certificates cost nothing, and NPM automates the whole lifecycle. There is no downside once it is set up.
The only thing you actually need to spend money on is a domain. A cheap one from Cloudflare Registrar runs a few dollars a year and gives you the DNS control this whole approach depends on.
The two paths to a trusted certificate
There are two honest ways to get certificates the browser trusts across your homelab.
| Approach | What it is | Best for |
|---|---|---|
| Let’s Encrypt via NPM | Free public certificates issued by a trusted authority, renewed automatically | Almost everyone; you own a domain |
| Internal CA (mkcert / step-ca) | Your own certificate authority that you trust on each device | Fully offline labs with no domain |
This guide uses the first path, because it is less work and it scales. You set up the domain and API token once, and every future service is a two-minute job. An internal CA means installing and trusting a root certificate on every laptop, phone, and tablet you own, and re-trusting it when you add a device. That is real friction, and it is only worth it if you refuse to register a domain. For the price of a domain, Let’s Encrypt is the cheaper choice in time.
HTTP-01 vs DNS-01: pick the right challenge
Before Let’s Encrypt hands you a certificate, it makes you prove you control the domain. There are two ways to prove it, and choosing the right one is the single most important decision in this whole process.
HTTP-01 challenge. Let’s Encrypt sends a request to yourdomain.com/.well-known/acme-challenge/ and checks for a file NPM places there. This requires port 80 to be open and forwarded to your server from the public internet. It works, but it only proves control of a publicly reachable host, and it cannot issue wildcard certificates.
DNS-01 challenge. Let’s Encrypt asks you to create a specific DNS TXT record. NPM creates it automatically through the Cloudflare API, Let’s Encrypt reads it, and the certificate issues. Nothing ever connects to your server.
For a homelab, use DNS-01. It is the better choice for three reasons:
- No open ports. You do not have to forward port 80, which means one less thing exposed to the internet.
- Internal services work. A service that is only reachable on your LAN or over Tailscale has no public route, so HTTP-01 cannot reach it. DNS-01 does not care.
- Wildcards. Only DNS-01 can issue a
*.yourdomain.comcertificate, which is the shortcut that makes every future service trivial.
The one-time cost is a Cloudflare API token scoped to edit DNS on your zone. The NPM setup guide covers creating that token step by step, so I will not repeat the clicks here.
The workflow: HTTPS on every service
This is the part you will repeat every time you add a service. Once the domain and token are in place, it takes about two minutes per service.
1. Point a subdomain at your reverse proxy
Create a DNS record for the service so its hostname resolves to the machine running NPM. In Cloudflare:
- Type: A (or CNAME to an existing record)
- Name: the service, e.g.
jellyfin - Value: the internal IP of your NPM host, e.g.
192.168.1.100 - Proxy status: DNS only (the gray cloud)
Keep it in DNS-only mode. You do not want internal traffic bouncing out to Cloudflare’s edge and back. For internal hostnames you would rather not put in public DNS at all, run Technitium DNS and create the record there instead.
2. Create the proxy host
In NPM, go to Hosts, Proxy Hosts, Add Proxy Host:
- Domain Names:
jellyfin.yourdomain.com - Scheme:
http(NPM talks to the service over HTTP internally; it adds the HTTPS layer on the outside) - Forward Hostname / IP: the service’s IP or container name
- Forward Port: the service port, e.g.
8096 - Block Common Exploits: on
- Websockets Support: on
Save it. At this point the service loads over HTTP through the proxy, but not yet HTTPS.
3. Attach the certificate
Edit the proxy host and open the SSL tab. You have two options:
- First time, no wildcard yet: choose Request a new SSL Certificate, enable Use a DNS Challenge, select Cloudflare, and paste your API token.
- You already have a wildcard cert: just select it from the SSL Certificate dropdown. Nothing else to request. This is why the wildcard is worth setting up early.
4. Force SSL and enable HTTP/2
Still on the SSL tab, turn on:
- Force SSL: redirects any
http://request tohttps://automatically - HTTP/2 Support: enables the faster modern protocol
Save. Load https://jellyfin.yourdomain.com and you should get a clean, trusted padlock with no warning.
5. Lock it in with HSTS
Force SSL redirects HTTP to HTTPS, but a browser still tries HTTP first. HSTS (HTTP Strict Transport Security) tells the browser to never even attempt plain HTTP for this hostname again. On the SSL tab, enable HSTS Enabled (and optionally HSTS Subdomains if you want it to apply site-wide).
If you prefer to set it by hand, add this to the Advanced tab of the proxy host:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Do not enable HSTS until HTTPS is confirmed working. Once a browser sees the header, it will refuse to load the site over HTTP for the duration of max-age, so a broken certificate becomes a locked door.
The one wildcard cert shortcut
Requesting a fresh certificate per service works, but it is unnecessary friction and it burns against Let’s Encrypt rate limits if you spin services up and down a lot. Instead, request a single wildcard certificate for *.yourdomain.com once, then assign it to every proxy host from the dropdown in step 3.
Ten services, one certificate, one renewal cycle. When you add service number eleven, you create the proxy host, pick the wildcard from the list, and you are done. There is no new certificate request at all.
I cover the exact wildcard request flow, including the dns_cloudflare_api_token credential format, in the wildcard SSL guide. If you are setting up HTTPS for the first time, do the wildcard first and every service after it is a dropdown selection.
Fixing the HTTPS problems you will actually hit
“Not secure” warning that will not go away. Almost always a browser cache issue after switching a service from HTTP to HTTPS. Hard refresh, or open the site in a private window. If it persists, confirm Force SSL is on and the certificate is actually assigned to that proxy host.
502 Bad Gateway over HTTPS. The certificate is fine; NPM just cannot reach the backend. Check the forward IP and port, and confirm the container is running with docker ps. HTTPS problems and connectivity problems look similar but are unrelated.
Mixed content warnings. The page loads over HTTPS but pulls some assets (images, scripts) over HTTP, so the browser flags it. This is usually an app setting. Look for a “base URL” or “external URL” field in the service’s own config and set it to the full https:// hostname.
Certificate request times out. With DNS-01 this is nearly always DNS propagation. Wait two minutes and retry. You can confirm the challenge record is live:
dig TXT _acme-challenge.yourdomain.com @1.1.1.1
If the record is missing, the Cloudflare API token does not have write access to that zone. Recreate it with the Edit zone DNS template scoped to the right domain.
Renewal: set it and forget it
Let’s Encrypt certificates expire every 90 days, and NPM renews them automatically well before that using the stored Cloudflare token. There is no cron job to write and nothing to remember.
The one failure mode worth knowing: if the Cloudflare API token is deleted or expires, renewal fails silently and then every service throws a certificate warning at once, because they all share the wildcard. If that ever happens, check the token first:
docker logs nginx-proxy-manager 2>&1 | grep -iE "cert|ssl|error" | tail -20
Cloudflare API tokens do not expire unless you configure them to, so set yours to never expire and this stays a non-issue.
Where this fits in the stack
HTTPS is the outermost polish on a good homelab: it makes every service feel like a real, hosted product instead of an IP and a port you have to remember. Once it is in place, the natural next steps are locking down who can reach those services and reaching them from outside the house.
For access control on top of HTTPS, Authelia adds SSO and 2FA in front of any proxy host. For remote access without opening ports, Tailscale pairs perfectly with DNS-01 certificates. And if you are still deciding whether NPM is even the right reverse proxy for you, Nginx Proxy Manager vs Caddy vs Traefik lays out the trade-offs. Short version: for most homelabs, NPM plus a wildcard Let’s Encrypt certificate is the fastest path to HTTPS everywhere, and it costs nothing but the price of a domain.
For the wider view of what to expose and what to keep internal, see the homelab security basics article.