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.
Thank you, I was unaware of that. It looks like it's already support in the acme.sh client, but there is a Let's Encrypt discussion saying it's still pending at LE:
Another big problem with split-horizon is you can get clients that cache the dns result before connecting to the vpn, then after connecting, can't actually use the service, because it is using the public ip instead of the private one.
CAUTION, though, the last time I downloaded a binary release, ClamAV triggered on it, so I kept my old version which worked. I was using the 1.0 series (without any problems!), and now it seems the project has picked up development again with a 2.0 series.
I get antsy about a private key being in lots of places. You've also got to worry about renewal so you might as well just have "consumer" of the wildcard just provision its own non-wildcard certificate.
- 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.
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.
I do the same thing. I'm not worried about them seeing my FQDNs.
I use the form of hostname.int.example.com for everything inside my home network. None of which is accessible to the outside world. I use LetsEncrypt with DNS validation to get the certificates.
Fair, but what about names that are specific enough to give an attacker a clue to a potential attack surface, like "authelia.example.com" - now they know you've likely got an Authelia setup, and can start digging for exploitable CVEs etc. I'm in the process of removing all my individual certs and replacing with a wildcard cert served by Traefik. Is that a bad idea?
Can they dig for exploitable CVEs if they're not on the Wireguard network? It is a clue to your infrastructure, but I personally think the simplicity is worth it.
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.
Possibly a separate concern, but I have some degree of confidence that the requirements and oversight of the CA/B forum (or whomever else determines which root certs go into bundles) are sufficiently strict, and issuers kept under sufficient scrutiny, that I tend to trust those more than I trust myself to secure my own root CA keys adequately. The ideal would be for people setting up their own PKI to ensure their root uses the Name Constraints extension, but the default “can sign anything for any host” I fear makes it easy for people to install their own self-pwn device, and probably left the private key lying around on a box exposed to the Internet.
* with some notable root certs that I have… questionable… trust and confidence are not simply controlled by certain state actors.
Even if all applications look at the OS trust store, in my experience there's always a gap distributing the CA to every consumer, leading to time spent on debugging from time to time... Maybe that's not the case in perfectly homogeneous or sufficiently small environments where every team uses the same infra / stack.
It's hard to get right when OSs, programming languages, browsers and sometimes other applications have their own opinions about trust stores. I understand why our IT department want corporate devices to use internal CA certs on paper but it just breaks stuff in the real world.
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.
I do have a DNS server in my LAN, with some records served to internal clients only. But the _acme-challenge record needs to be public for the DNS-01 validation to succeed.
The point was that you can obtain a certificate for a domain name without creating any records other than the _acme-challenge TXT record. I.e., that the domain might be completely empty all the time except for this record.
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.
So if "example.com" is control by Corporate IT, and they don't want 'random' folks fiddling with it, then you can create a "dnsauth.example.com" and point the dns-1 challenge record from "…foo.example.com" to "foo.dnsauth.example.com" (or a completely different domain, like "…example.net").
There are DNS servers written strictly focused on this use case:
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..
Personally, I hate split horizon DNS. I prefer the "BeyondCorp" model. I MUCH prefer putting an mTLS cert in my trusted devices over relying on VPNs in same devices. I've yet to see a "clever" DNS setup not cause annoyances.
Specifically grafana is nice to be able to see on the phone, and split horizon DNS and corp VPN is a hassle, to say the least, on phones.
This is crazy. If you have a home network with a few internal services, or some sort of network where you don't control the endpoints, just use DNS validation. That's why it exists.
But on hosts you control, you should absolutely provision them with an identity and join the local CA. You're going to need it for a multitude of other reasons.
There's certainly something to be said for ease of use and not having to ensure you push trusted certs to every device that touches your internal network.
I was under impression that I understand networking and DNS resolution. It was really hard to follow, the OP did worked hard, just not sure what exact problem was being solved with the proposed solution that isnt already been solved.
Arguably it's 1/2. You can put public certs on proxy then give proxy private CA to backend services. Then you don't need public certs for all the private stuff nor need to trust the private CA on all your devices.
I don’t know much in this space, but I find myself wishing there was a dead simple self hosted CA solution and also that trust on first use (à la ssh) was A Thing for self-managed root certs in client implementations. TOFU is such an elegant, good-enough solution for these use cases. Fixed deployment is always still an option, but in this day and age it feels so much like we are unnecessarily still dealing with solved problems
I guess I mean treat it as a clear first class feature. Right now most browsers treat it as an arcane error. I’m thinking more “This is the first time you’re connecting to this site. Do you trust it?”
And later if something changes, then they can do the whole DOING SOMETHING NASTY! thing, which is effectively the experience today
When using letsencrypt for internal services it becomes very hard to distinguish badplayer.com LE certificate from the good players. You only ensure encryption. Not identity. Do not use letsencrypt internally for things that matter.
A Github Action running acme.sh that pushes certs to S3 solves the split dns issue for hosts, which can cause all sorts of weirdness after a while. You can then grab a cert on a schedule and even make them wildcard if you want. Then you will get NXDOMAIN if you are not on the VPN so ideally no public traffic.
Why not just map the domain to an internal IP and call it a day? Then the only way it can be accessed is through a VPN. Then use a wildcard so none of leaks into cert transparency logs
> Then use a wildcard so none of leaks into cert transparency logs
You now also have to build infrastructure to distribute the wildcard from (presumably) central place where you generate it to all the different places where it is desired.
And hope the wildcard's private key does not leak from one of myriad of places it now lives.
I've documented how to securely set up TLS certificates for internal services without creating TLS issues for http clients downstream. All thanks to split-horizon DNS, WAF and ACME protocol. All for free!
"Right" without use case can be wrong. And by use case I include scale. For a small team, few machines, some in-place infrastructure may worth it. Smaller than that may be overkill, bigger than that may not be enough, or end being cumbersome, insecure or not work for everyone.
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.
There's really two ways, the other is to manage your own CA. But it seems like every browser/piece of software/etc out there is hell bent on making that as difficult as possible. It'd also be nice if it was easier to scope a certificate authority to a specific domain, but support for that is pretty patchy which is functionally the same as no support at all. And that's not to mention software that ignores the system certificate store. Or how tedious and nonstandardized it can be to get a trusted certificate store in a Docker container in cases where you have services that need to trust each other. Or how annoying it is to install your own trusted CA on devices (though, step-cli does help a lot at least on normal computers... phones however...). On and on and on, the barriers to what should be the obvious solution are extremely high.
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.
[1]: https://letsencrypt.org/2026/02/18/dns-persist-01
Once it's supported I think my next iteration will be DNS persist + internal ip addresses on the public zone.
Thank you all for comments and feedback! It's cool to see real interest in this blog post
https://community.letsencrypt.org/t/dns-persist-01-deploymen...
I wonder if the interim version has been rolled out to some CAs.
You need a DNS provider which supports API calls (I use DNSimple) but the core is all very straightforward.
To prevent having to include DNSimple authentication on the client's internal server I have a small API server on the web which does the Acme work.
https://github.com/acme-dns/acme-dns
CAUTION, though, the last time I downloaded a binary release, ClamAV triggered on it, so I kept my old version which worked. I was using the 1.0 series (without any problems!), and now it seems the project has picked up development again with a 2.0 series.
Services themselves are constrained to the server, bound to listen on 127.0.0.1 only.
Key is only available to the reverse proxy.
- 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.
If you do that anyways, you could also use something like oidc authN/authZ on the reverse proxy level and just expose it to the internet.
You dont even need to self host the oidc idp, you can use Google/Github or even something like ATProto
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.
I use the form of hostname.int.example.com for everything inside my home network. None of which is accessible to the outside world. I use LetsEncrypt with DNS validation to get the certificates.
Removing attack surface is better than trying to hide it.
I'd prefer this over split DNS, any day.
* with some notable root certs that I have… questionable… trust and confidence are not simply controlled by certain state actors.
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.
The point was that you can obtain a certificate for a domain name without creating any records other than the _acme-challenge TXT record. I.e., that the domain might be completely empty all the time except for this record.
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.
See also perhaps DNS aliasing in case you are not able to dynamically update your 'primary' domain, but can update a secondary or sub-domain:
* https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mo...
So if "example.com" is control by Corporate IT, and they don't want 'random' folks fiddling with it, then you can create a "dnsauth.example.com" and point the dns-1 challenge record from "…foo.example.com" to "foo.dnsauth.example.com" (or a completely different domain, like "…example.net").
There are DNS servers written strictly focused on this use case:
* https://github.com/acme-dns/acme-dns
Also code that handles a bunch of DNS provider APIs so you don't have to roll your own for ACME client hooks:
* https://github.com/dns-lexicon/dns-lexicon
https://www.eff.org/deeplinks/2018/02/technical-deep-dive-se...
I get it, I could just do *.mydomain.com and slap that wildcard cert everywhere, but it's still in the public logs..
Specifically grafana is nice to be able to see on the phone, and split horizon DNS and corp VPN is a hassle, to say the least, on phones.
I bet you can do it with HA-Proxy, but I use https://github.com/ThomasHabets/sni-router
But on hosts you control, you should absolutely provision them with an identity and join the local CA. You're going to need it for a multitude of other reasons.
In that case there's no need to validate anything as names, dns records, certificates and anything else should already be in place.
Unless you enjoy that sort of thing.
Most browsers support trust on first use for leaf certs
And later if something changes, then they can do the whole DOING SOMETHING NASTY! thing, which is effectively the experience today
Using a browser in an air gapped environment is so much more pain than it should be.
* "internal services" = on a single server that is publicly routable
You now also have to build infrastructure to distribute the wildcard from (presumably) central place where you generate it to all the different places where it is desired.
And hope the wildcard's private key does not leak from one of myriad of places it now lives.
Leaking is an issue but we're talking about internal services too.
https://www.certkit.io/tools/ct-logs/?query=tuxnet.dev