Certificates for Internal-Only Self-Hosted Services
Give internal self-hosted apps real HTTPS with mkcert or a local ACME CA so your private dashboards stop showing scary certificate warnings.
Netbay Infrastructure Team
Netbay Engineering
On this page
Not every self-hosted service faces the public internet. A Home Assistant dashboard, a local Git server, or a monitoring console on your internal network all deserve TLS too, but they cannot always get a public Let's Encrypt certificate — there is no public domain, or the service is not reachable from outside. That leaves two honest options: issue your own certificates with mkcert for local development trust, or run a small internal ACME server so any private service can rotate certificates automatically. Both remove the browser's angry "not secure" warning.
mkcert for Local Sizes
mkcert is a small helper that creates a local certificate authority and signs certificates for hostnames or IPs you name. You install the CA into your machine's trust store once, then generate certs on demand. It is the fastest way to go from "invalid cert" to "padlock" for a small lab.
mkcert -install
mkcert dashboard.local 192.168.1.20 localhostThe first command installs the root CA, and the second produces a certificate valid for the listed names — which you then point your proxy, Cockpit-style admin panel, or app at. The real caveat is that mkcert's trust is per-device: every laptop, phone, and browser needs that root CA trusted before the green padlock appears, which does not scale to a fleet.
A Local ACME Server for Scale
For more than a handful of devices the manual trust becomes a bottleneck, and you want automated renewal like the public internet gets. That is what a private ACME server gives you: your own certificate authority speaking the standard protocol, so any ACME client — including Caddy, which talks to ACME natively — can obtain and renew internal certs automatically.
services:
step-ca:
image: smallstep/step-ca:latest
restart: unless-stopped
environment:
- DOCKER_STEPCA_INIT_NAME=Homelab CA
- DOCKER_STEPCA_INIT_DNS_NAMES=ca.local,127.0.0.1
ports:
- "9000:9000"
volumes:
- ./certs:/home/stepSmallstep's step-ca is the standard tool here. Once initialized it exposes an ACME endpoint, and Caddy can use it as its CA for internal-only hostnames.
Point Caddy at the Internal CA
Caddy normally fetches from Let's Encrypt, but you can override the ACME endpoint and require TLS when roots are involved. For internal uses the endpoint becomes your step-ca address:
{
acme_ca https://ca.local/acme/acme/directory
}
dashboard.local {
reverse_proxy 127.0.0.1:8080
}The global acme_ca option tells Caddy to obtain its certificate from your internal CA instead of the public one, and because the domain resolves only inside your network, no public validation is needed. Renewal happens the same way it does for public certs — silently, before expiry.
Distribution of the Root
Both approaches hinge on one discipline: the private root CA must be distributed only to devices you control and kept off the public internet. Treat your root key like a password — anyone holding it can mint credentials your whole network will trust. Back it up securely and never ship it in an image.
Watch Expiry Even When Renewal Is Automatic
Automatic renewal removes most of the work but not all of the risk. A host that is off overnight, a misconfigured timer, or a changed internal CA can let a certificate quietly lapse — and the resulting browser distrust is worse than an expired public cert because your own root is involved. Get into the habit of listing the validity of every internal service's certificate in one command and inspection, and surface any near-expiry in your monitoring.
# check the notAfter date of a service's served certificate
echo | openssl s_client -connect dashboard.local:443 2>/dev/null |
openssl x509 -noout -datesA short loop over all internal hostnames shows, at a glance, which certificates are healthy and which are approaching their end. Certificate hygiene is one of those quiet duties that never makes the news until a whole internal fleet shows padlocks with an exclamation point on the same morning.
Mixed Public and Private on One Host
It is common to run public services with Let's Encrypt and internal ones with your own CA on the same proxy. There is no conflict: the proxy picks the right ACME authority per domain. Keep the two realms clearly separated in your config comments and your Caddyfile, so a later editor does not accidentally route a public domain to the internal CA or vice versa. The distinction lives in the acme_ca selection, not in the network.
Takeaway
Public TLS is the default, but internal services deserve the same green padlock. mkcert gets a small lab trusted in minutes; step-ca plus Caddy automates renewal at scale. Either way, secure internal HTTPS and keep your root key safe. You can stand up such an internal profile on a Netbay Lucknow instance in under an hour — start at netbayhosts.in.
Keep reading
Follow along on a real VPS
Deploy Linux in under 60 seconds
These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.
Deploy an instance