Peter Wiggers
Bedrock: Security

Internal apps deserve real certificates too

Stop installing private root CAs on every device. With Let's Encrypt DNS validation, internal apps get publicly trusted certificates without ever being reachable from the internet.

3 min read

Every company has them: internal dashboards, admin panels and staging environments on names like grafana.internal.example.com. And almost every company handles their certificates the same painful way.

Either they use self-signed certificates and everyone learns to click through browser warnings. Or they run a private certificate authority and spend the rest of their lives installing its root certificate everywhere: laptops, phones, CI runners, Docker images, Java truststores, Python’s certifi, Node’s NODE_EXTRA_CA_CERTS. Each one is a small chore. Together they’re a constant source of “works on my machine”.

There’s a simpler option, and it’s free.

Internal apps can have publicly trusted certificates. They just don’t need to be public.

DNS validation instead of HTTP validation

Let’s Encrypt has to verify that you control a domain before it issues a certificate. The well-known method is HTTP validation: Let’s Encrypt connects to your server over the internet. For internal apps that’s a non-starter.

The other method is DNS validation (the dns-01 challenge). You prove control by creating a TXT record in the domain’s public DNS. Let’s Encrypt only looks at DNS. It never connects to your server.

So the setup is:

  • internal.example.com is a subdomain of a domain you own, with DNS at a provider that has an API.
  • The app’s actual address only resolves on your internal network, through private DNS, split-horizon DNS or your VPN.
  • Your certificate tool creates the TXT record through the DNS provider’s API, gets the certificate and cleans up.

The result is a certificate that every browser, phone and HTTP client already trusts. Nothing to install anywhere.

In Kubernetes: cert-manager

With cert-manager it’s one ClusterIssuer. Here with Cloudflare, but Route 53, Google Cloud DNS, Azure DNS and many others work the same way:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-account
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-api-token
              key: api-token

Point your Ingress or Certificate at that issuer and you’re done. Renewal happens automatically.

Outside Kubernetes, Caddy and Traefik support DNS validation out of the box, and lego or acme.sh cover almost everything else. If you use Tailscale, its built-in HTTPS certificates are this exact pattern, done for you.

The caveats

Certificate Transparency makes hostnames public. Every publicly trusted certificate ends up in public CT logs. If you issue payroll-admin.internal.example.com, anyone can find that name. The fix is a wildcard certificate for *.internal.example.com, which reveals the zone but not what’s in it. Wildcards require DNS validation anyway.

Your DNS API token is now a powerful secret. Whoever has it can change your DNS. Scope it to the one zone it needs. Even better, delegate only the _acme-challenge records to a separate zone with a CNAME, so the token can’t touch anything else.

Automation is not optional. Let’s Encrypt certificates are short-lived, and lifetimes across the industry are getting shorter. If renewal isn’t automated and monitored, you’ve just moved the outage to a later date.

Why it’s worth it

The hidden cost of a private CA isn’t the CA. It’s the friction: every new laptop, every new container image, every new language runtime that needs to be told what to trust. People work around friction, and the workaround is usually verify=False. Once that’s in your codebase, TLS is protecting nothing.

Publicly trusted certificates remove the reason for that workaround. Your internal apps get the same TLS as your public ones, and nobody needs to install anything. That’s the kind of security improvement people don’t resist, because it makes their life easier too.