Real HTTPS for a homelab means every internal service loads with a green padlock and no browser warning — and the honest answer is you get there with a real domain name, an ACME client, and either a public certificate authority or your own internal CA. There is no fourth option that scales past three services. I run about forty self-hosted endpoints behind OPNsense and a Proxmox host, and the certificate layer is the one thing I stopped improvising years ago.
Here is the stake: the “click through the warning” habit is not harmless. Every time you train yourself and your family to ignore a browser certificate error, you have quietly disabled the one signal that would tell you a device on your IoT VLAN is impersonating your NAS. The whole point of TLS is that the warning means something. Once you have clean certificates everywhere, a warning becomes a real alarm again — and that is worth more than the padlock aesthetics.
This is the map for the whole cluster. I will walk the decisions in the order I actually made them, link out to the deep dives on each piece, and give you the comparison table I wish someone had handed me before I wasted a weekend on a self-signed mess.

Why Do Homelab Services Throw HTTPS Warnings in the First Place?
Browsers throw warnings because your self-hosted service is presenting either no certificate, a self-signed one, or a certificate for the wrong name — and none of those chain up to a certificate authority the browser already trusts. Trust is the whole game. A certificate is just a signed claim; the signature only matters if the signer is in your device’s trust store.
When you first spin up Proxmox, Home Assistant, or a NAS web UI, each one generates its own self-signed certificate on install. That certificate is cryptographically fine — it will encrypt the connection perfectly well — but nothing vouches for it. Your browser has never heard of “Proxmox Virtual Environment” as a certificate authority, so it does the responsible thing and screams. The encryption is real; the identity is unverified. Those are two different properties, and the warning is specifically about identity.
You have exactly three ways to make the warning go away honestly. One: get a certificate from a public CA that browsers already trust, using a domain you actually own. Two: run your own internal CA and teach your devices to trust it. Three: some hybrid, where a reverse proxy fronts everything and handles the certificates for you. Every legitimate homelab TLS setup is a variation on those three, and this cluster covers all of them.
Should You Use a Public Domain or .local for Internal HTTPS?
Use a real, registered public domain — a subdomain like home.example.com — not .local and not a made-up TLD. This single decision unlocks free public certificates and avoids a category of DNS bugs that will haunt you for months. It is the first choice you should make, and most people get it wrong because .local sounds right.
The problem is that .local is not yours to use. It is reserved by RFC 6762 for multicast DNS — the same mDNS that Apple Bonjour, Chromecast, and printer discovery ride on. Point a normal DNS zone at .local and you are fighting the operating system’s mDNS resolver for the same namespace. On my network I watched a macOS laptop take eight seconds to resolve a .local host because it tried multicast first and only fell back to unicast DNS after a timeout. Real domain, problem gone.
The bigger reason is certificates. Public CAs are contractually forbidden from issuing certificates for internal-only names — the CA/Browser Forum required members to stop issuing certificates for internal server names and reserved TLDs, with issuance for those names ending on November 1, 2015. So homeassistant.local can never get a Let’s Encrypt certificate, full stop. But ha.home.example.com can, even though it only ever resolves inside your LAN, because example.com is a real domain you can prove you control. I unpack the whole trade-off, including split-horizon DNS, in why a public domain beats .local for homelab HTTPS.
How Do You Get Free Trusted Certificates for Internal Services?
You use Let’s Encrypt with the DNS-01 challenge, which proves you control the domain by writing a TXT record instead of exposing anything to the public internet. This is the trick that makes free, browser-trusted certificates work for services that are only reachable inside your LAN. No port 80 open to the world, no service exposed — just a DNS record.

Standard Let’s Encrypt validation (the HTTP-01 challenge) needs your server reachable on port 80 from the public internet so their servers can fetch a token. That is a non-starter for an internal NAS. The DNS-01 challenge sidesteps it entirely: the ACME client asks Let’s Encrypt for a token, writes it as a TXT record at _acme-challenge.ha.home.example.com through your DNS provider’s API, and Let’s Encrypt reads the record to confirm control. Nothing inbound. The service can live on a VLAN with no internet ingress at all.
DNS-01 is also the only challenge type that can issue a wildcard certificate. One *.home.example.com certificate covers every current and future service under that subdomain, so you provision once and stop thinking about it. Remember Let’s Encrypt certificates are valid for 90 days, which sounds short until you automate renewal — then it is a feature, because a compromised key has a short blast radius. The mechanics, provider API tokens, and the rate limits that will bite you are in Let’s Encrypt wildcard certs with DNS-01 for internal services.
When Should You Run Your Own Certificate Authority Instead?
Run your own internal CA when you want certificates that never touch a third party, need very short lifetimes, or want to issue for names and IPs a public CA will not sign. For most people the public-domain-plus-Let’s-Encrypt route is simpler, but a private CA is the right tool for a genuinely air-gapped lab or a zero-trust setup where you control every second of a certificate’s life.
The modern way to do this is step-ca from Smallstep, which is an ACME server you run yourself. That is the elegant part: your internal services use the exact same ACME client and workflow they would use for Let’s Encrypt, but they point at your CA instead. step-ca can issue certificates with lifetimes measured in hours — its automated-provisioner defaults land around 24 hours — so a leaked key is worthless by tomorrow. On my host the CA runs in its own LXC container on the management VLAN, deliberately isolated. The full build, provisioner setup, and the ACME endpoint config live in running your own internal certificate authority with step-ca.
The catch — and it is the whole catch — is that nothing trusts your CA until you tell it to. A Let’s Encrypt certificate works on a brand-new phone out of the box. A certificate from your step-ca does not, until you install your root CA into that phone’s trust store. That is the tax you pay for total control, and it is the reason the next section exists.
How Do You Make Devices Trust Your Own Certificates?
You install your CA’s root certificate into each device’s trust store — and the process is genuinely different on every platform, which is where people give up. Get this right once and your own certificates are indistinguishable from a public CA’s; get it wrong and you are back to clicking through warnings, which defeats the entire exercise.
The traps are platform-specific and unintuitive. Android splits its trust store into “system” and “user” tiers, and since Android 7 (Nougat, 2016) apps ignore user-installed CAs by default — so your browser trusts the cert but your self-hosted app’s mobile client throws a cryptic error. iOS makes you do two separate steps: install the profile, then also flip a toggle under Certificate Trust Settings that is buried three menus deep. Firefox ignores the operating system entirely and keeps its own trust store. I have watched all four of these trip people up, and I document the exact taps and the security.enterprise_roots.enabled Firefox flag in trusting your own CA on phones, laptops, and browsers.
This is also the honest tie-breaker between the two approaches. If your household has a rotating cast of guest devices, or you just do not want to touch every phone, the public-domain-plus-Let’s-Encrypt path wins precisely because it skips this entire section. If your lab is yours alone and you value control, the one-time trust install is a fair price.
What Actually Terminates HTTPS — The Reverse Proxy Question
A reverse proxy sits in front of your services, holds the certificates, terminates HTTPS once, and forwards plain HTTP to each backend on your trusted LAN — so you provision certificates in exactly one place instead of on twenty separate apps. This is the architecture I run and the one I recommend for anyone past a handful of services. Certificates on the proxy, not scattered across every container.

The three tools everyone actually chooses between are Caddy, Traefik, and Nginx Proxy Manager. Caddy does automatic HTTPS by default — point it at a hostname and it fetches and renews the certificate with zero configuration, and it will even spin up its own internal CA for local names. Traefik shines when your services are Docker containers, because it reads labels and reconfigures itself live as containers come and go. Nginx Proxy Manager wraps nginx in a friendly web UI for people who want to click, not edit config files. I compare all three head-to-head, including the DNS-01 wildcard config for each, in Caddy vs Traefik vs Nginx Proxy Manager for automatic HTTPS.
Whichever you pick, the pattern is the same: the proxy owns the wildcard certificate, every service gets a clean service.home.example.com name, and the messy per-app certificate handling disappears. The Caddy automatic HTTPS documentation is worth reading even if you end up on a different proxy, because it explains the on-demand and internal-CA modes clearly.
Can You Get a Real Certificate for the Firewall Itself?
Yes — OPNsense has an ACME Client plugin that will fetch and auto-renew a Let’s Encrypt certificate for the firewall’s own web UI, using the same DNS-01 challenge, and reassign it automatically on renewal. The firewall admin page is the one interface where I most want a real certificate, because it is the keys to the kingdom and it is the login I type most carelessly.
The plugin (os-acme-client) supports dozens of DNS providers for the challenge, ties the certificate to the GUI under System settings, and restarts the web service cleanly when the certificate rotates. The one gotcha I hit early: if you bind the certificate to the GUI but the automation fails silently on renewal, you can lock yourself into a warning-again state right when you are not watching. I cover the DNS-provider config, the auto-renewal cron behavior, and how to verify a renewal actually swapped the live certificate in ACME on OPNsense: free certs for the firewall web UI. The same pattern works on pfSense with its ACME package, if that is your camp.
What About Locking Services Down With Client Certificates?
Mutual TLS (mTLS) flips the model so the client must also present a certificate the server trusts — turning a certificate into a login. This is the strongest access control you can put in front of a self-hosted service, and it is the natural endgame once you already run an internal CA, because you already have the machinery to issue client certificates.

In a normal HTTPS connection only the server proves who it is. With mTLS the reverse proxy is configured to demand a client certificate too, verify it against your CA, and refuse the connection outright if the device does not hold one. No password prompt, no login page to brute-force — a device without the certificate never gets to the application at all. I use it in front of the handful of services I would never want exposed even by accident. The client certificate lives in the browser or phone as a PKCS#12 bundle. The proxy config, the client-cert install, and where mTLS genuinely beats a password (and where it is overkill) are in mTLS for self-hosted services: client certificates explained.
Which Certificate Approach Fits Your Homelab?
Here is the decision the whole cluster comes down to, in one table. Match the row to how many services you run, whether you want to touch every device, and how much control you need.
| Approach | Trusted by default? | Wildcard? | Renewal | Best for | Main drawback |
|---|---|---|---|---|---|
| Let’s Encrypt + DNS-01 (public domain) | Yes, everywhere | Yes | Automated, every 60–90 days | Most homelabs; zero device setup | Needs a domain and a DNS provider API |
| Internal CA with step-ca | No — install root once | Yes | Automated, hours to days | Air-gapped labs, zero-trust, short lifetimes | Must install root CA on every device |
| mkcert (dev machines) | Local machine only | Yes | Manual | A single workstation, quick testing | Does not scale to phones or many devices |
| Per-service self-signed | No | No | Manual, painful | One throwaway service | A warning per service; unmanageable past 3 |
| Reverse proxy (Caddy/Traefik) + Let’s Encrypt | Yes, everywhere | Yes | Fully automatic | Anyone past a handful of services | One more component to run and monitor |
My own network is the bottom row feeding off the top row: a reverse proxy holding a single Let’s Encrypt wildcard, fetched over DNS-01, for everything that does not need client certificates — and step-ca plus mTLS for the few services that do. It took an afternoon to set up and it has needed roughly zero attention since.
The Mistake I Made First (So You Skip It)
My first attempt was a self-signed certificate per service, and I genuinely thought I was being disciplined about it. I generated a proper 2048-bit key for the NAS, imported it, admired the padlock. Then I did the same for Proxmox. Then Home Assistant. By the fourth service I was managing four unrelated self-signed certificates, four expiry dates I would never remember, and four separate “add exception” clicks on every new device — and my kids had learned to bulldoze through certificate warnings on the family tablet like they were cookie banners.
The smell of that mistake is specific: it is the low-grade dread of opening a service six months later to a fresh red warning page and not remembering which self-signed cert expired or how you made it. I tore the whole thing out over a Saturday, stood up step-ca and later a wildcard from Let’s Encrypt, and the difference was night and day. The lesson: certificates are an infrastructure problem, not a per-service chore. Solve it once at the layer where it belongs — the domain, the CA, and the proxy — and every future service inherits the fix for free. That is the same instinct that makes me put each hobby on its own VLAN instead of trusting the flat network: solve it at the architecture layer, once.
How Do You Keep Certificate Trust From Crossing VLANs?
Keep the certificate authority and the DNS that issues names on your management segment, and hand out only leaf certificates — never the CA key — to services on other VLANs. A certificate authority is a trust anchor; treat its private key like the master key to the building, because functionally it is. On my network the step-ca container and the DNS resolver that answers home.example.com both live on MANAGEMENT, default-deny to everything else.
This is where the polymath-on-one-router problem shows up in the certificate layer. The hydro lab, the workshop sensors, the kids’ consoles, and the lab subnet are four different trust zones, and I do not want a compromised IoT gadget to be able to reach the CA’s issuance API. So the rule I run on OPNsense is simple: only the reverse proxy and the specific hosts that need to enrol may talk to step-ca’s ACME port, and the IoT VLAN cannot reach it at all. A device on the no-egress IoT segment can still present a certificate my proxy trusts, but it can never request a new one. Issuance and use are separated by firewall rule, not by hope.
The same instinct that makes me trunk each hobby to its own tagged VLAN makes me scope certificate trust deliberately: the guest network gets nothing, the IoT segment gets a leaf certificate with a name and nothing else, and the management plane holds the keys. If you already segment your network — and if you are reading a homelab site you probably do — the certificate topology should mirror the network topology. A flat trust model on a segmented network is a contradiction I see constantly, and it quietly undoes the segmentation you worked for.
What Do the Common Certificate Errors Actually Mean?
The three errors you will actually hit are name mismatch, untrusted issuer, and expired — and each points at a different layer, so reading them precisely saves hours. A browser lumps them under one scary red page, but the specific error string tells you exactly which part of the chain failed. Learn to read it and you stop guessing.
NET::ERR_CERT_COMMON_NAME_INVALID means the certificate is valid and trusted but issued for a different name than the one in your address bar — you hit https://192.168.10.20 but the certificate is for nas.home.example.com. The fix is to always browse by the DNS name, never the raw IP, because certificates authenticate names, not addresses. This is the single most common self-inflicted error I see, and it is why local DNS matters as much as the certificate itself.
NET::ERR_CERT_AUTHORITY_INVALID means the certificate itself is fine but its issuer is not in this device’s trust store — classic symptom of an internal CA whose root you have not installed on that particular phone yet. And the plain “expired” error means automated renewal silently died; because a Let’s Encrypt certificate only lives 90 days, a renewal cron that has been broken for two months surfaces exactly when the clock runs out, never before. That last one bit me once: my renewal was failing quietly for weeks, and the only warning I got was the padlock turning red on a Sunday morning. The lesson — monitor renewals, do not just trust them — is why I now watch certificate expiry on the same Grafana dashboard as everything else.
Frequently Asked Questions
Do I need to expose my services to the internet to get a Let’s Encrypt certificate?
No. The DNS-01 challenge proves domain control by writing a TXT record through your DNS provider’s API, so nothing needs to be reachable from the public internet. Your services can sit on a VLAN with no inbound access at all and still get browser-trusted certificates.
Why can’t I just use a .local domain for my homelab?
The .local suffix is reserved for multicast DNS by RFC 6762, so a normal DNS zone using it fights the operating system’s mDNS resolver. More importantly, public certificate authorities stopped issuing certificates for internal names and reserved TLDs in 2015, so a .local name can never get a Let’s Encrypt certificate. Use a subdomain of a real domain you own instead.
What is the difference between a public CA and running my own internal CA?
A public CA like Let’s Encrypt is already trusted by every browser and phone, so its certificates work with zero device setup, but it will only sign names on domains you can prove you control. Your own internal CA, such as step-ca, can sign any name or IP and issue very short-lived certificates, but nothing trusts it until you install its root certificate on each device.
How long are homelab TLS certificates valid, and do I have to renew them by hand?
Let’s Encrypt certificates are valid for 90 days and are designed to be renewed automatically by an ACME client, typically at the 60-day mark. An internal CA like step-ca can issue certificates that live only hours. In a well-set-up homelab you never renew a certificate by hand; the ACME client or reverse proxy does it for you.
Do I still need a reverse proxy if I use Let’s Encrypt?
You do not strictly need one, but it is the cleaner architecture past a handful of services. A reverse proxy holds one wildcard certificate and terminates HTTPS in a single place, so you configure certificates once instead of on every individual application. Caddy, Traefik, and Nginx Proxy Manager all handle the certificate fetching and renewal for you.
What I’d Do Starting Today
If you are staring at a homelab full of certificate warnings right now, here is the order I would follow. Register or reuse a real domain and carve out a home.example.com subdomain — that is the decision in public domain versus .local, and it unblocks everything else. Stand up a reverse proxy and point it at a single Let’s Encrypt wildcard over DNS-01. Give the firewall its own ACME certificate while you are in the mindset. That covers ninety percent of homelabs with zero device setup.
Only if you have a real reason — an air-gapped segment, a zero-trust goal, a taste for short-lived certificates — add an internal CA with step-ca, budget the afternoon to install its root on your devices, and reserve mTLS client certificates for the two or three services that genuinely warrant a certificate-as-a-login. Do it in that order and you will never click through a certificate warning in your own house again.
Keep Building
- Why a Public Domain Beats .local for Homelab HTTPS
- Let’s Encrypt Wildcard Certs With DNS-01 for Internal Services
- Caddy vs Traefik vs Nginx Proxy Manager for Automatic HTTPS
- Running Your Own Internal Certificate Authority With step-ca
- Trusting Your Own CA on Phones, Laptops, and Browsers
- ACME on OPNsense: Free Certs for the Firewall Web UI
- mTLS for Self-Hosted Services: Client Certificates Explained