Secure Internal Services with TLS Certificates Using Split-Horizon DNS and Let's Encrypt

TLS certificates for internal services done right

Secure Internal Services with TLS Certificates Using Split-Horizon DNS and Let's Encrypt

I explain why using self-signed certificates for internal services is a hassle and propose a better approach. By combining split-horizon DNS with NetBird and Let's Encrypt, you can use trusted public certificates even for private apps. Adding an nginx WAF ensures only VPN traffic reaches your internal tools, giving you seamless security without managing custom trust stores on every machine.

Security is about layers (just like onions and ogres). Our first layer is split-horizon DNS but if for whatever reason it fails or is cleverly bypassed we have a 2nd layer - WAF - that should hold the line.
  1. EvanAnderson

    The relative proximity of the words "done right" and "split-horizon DNS" makes my insides hurt a little bit.

    Use DNS validation to allow these internal services to pull ACME certs. There's so much less headache, long-term.

    Split-horizon DNS (and the tedious make-work it can create when you start needing to mirror public-accessibly records in the private DNS) has always been something to aspire to move away from in my experience.

  2. raffraffraff

    Hmm. I don't really care enough about leaking home network host names because they are all super generic names like 'router', 'laptop', 'tv', 'nas'. So I use my public zone on cloudflare. I just use internal ip addresses (eg: nas.example.com = 10.1.2.3) on the public zone and DNS01 challenge for let's encrypt. Anyone can resolve the ip for any of my hosts, but obviously you'd need to be on the wireguard vpn to hit them.

    This means that I can always use public DNS servers like 1.1.1.1, 8.8.8.8, nextDNS etc

    This is not "done right" by any stretch but it's extremely low effort to set up and has never once failed me, unlike countless complex meshy things.

  3. boscillator

    The real answer here is that configuring HTTPS clients to trust a self-signed cert (or signed by an internal CA) shouldn't be as difficult as it is. I find it extremely annoying that every programming language has it's own idea of where certificates should live instead of just checking the os trust store.

  4. patrakov

    My preferred procedure is to use DNS-01 validation and have no publicly accessible "A" or "AAAA" record for internal services.

    Or even a more extreme example: https://crt.sh/?id=27555237869 (sorry for any possible crt.sh downtime) - the domain name in question never existed in public or private DNS by itself. It is used only for a WPA3-Enterprise network, as the CN that WiFi clients expect to be present in the RADIUS server certificate, but never resolve. In the public DNS, only the "_acme-challenge" TXT record exists.

  5. wrxd

    I use the acme dns-1 challenge on my public domain. That gives you certificates you can use as you see fit, without needing to expose anything else to the public internet.

    I also use Tailscale so I configure my DNS to use my Tailscale IP addresses. If you don’t want to expose them on a public DNS server you can add them only to an internal DNS server.

  6. samgranieri

    Split horizon DNS is not something I'm willing to do.

    I'll just rock out with .internal or .home.arpa, have step-ca and bind communicate to each other, either in step-issuer in kube or maybe even rfc2136 if i feel like a bit of the dns-01 strategy is in order. I slap the internal ca root certs everywhere, and keep my home infra out of the crt.sh logs.

    I get it, I could just do *.mydomain.com and slap that wildcard cert everywhere, but it's still in the public logs..

  7. xurukefi

    Or...

    - Don't use split DNS. Don't use any special internal or dev domain. Leave it to your infrastructure to route/NAT those public IPs to your internal network.

    - Don't use the HTTP-01 challenge. Use DNS-01.

    - Don't run your own internal CA. Use Let's Encrypt. If you care about name leakage (CT Logs), use wildcard certs. Use a central reverse proxy/load balancer for termination.

  8. Hamuko

    I use a registered domain with DNS validation and then CNAMEs that I resolve locally. Basically:

    1. Register a domain ("server.com") and put it on some public DNS that can do DNS validation with acme.sh.

    2. Use DNS validation to get a certificate on your domain from Let's Encrypt. You can just grab a wildcard one ("*.server.com").

    3. CNAME all of your services on a public DNS to an internal address ("email.server.com" → "server.internal", "plex.server.com" → "server.internal").

    4. Resolve your internal address on a local DNS server with an A record ("server.internal" → 192.168.0.123). This can often just be done on your router.

    Since you use DNS validation, you just API keys for your public DNS service that acme.sh can use. No need to have any VPN network interfaces for getting your certificate. Your wildcard certificate also doesn't leak any details about your services.

More from this day

2026-07-09