Skip to content

You May Want Local DNS Broken Like This

A friend called me with what seemed, at first, like a routine DNS issue. He runs a small home lab with a local DNS server and a reverse proxy, and his internal services all use his registered domain, something like service.example.com. On the LAN, that name resolves to a private address. Outside the network, it resolves to a public endpoint.

That arrangement was deliberate. It keeps internal traffic on-net, avoids hairpinning through the gateway, and means the same FQDN works everywhere. If he wants to expose something externally, he can do it at the proxy without introducing a second naming scheme or switching between different addresses depending on where he is working.

The odd part was that the problem only appeared on one Linux host. Internal lookups were returning SERVFAIL.

dig service.example.com
status: SERVFAIL
SERVER: 127.0.0.53#53

On its own, that is not especially revealing. The query is going through the local stub resolver, so the next step is to bypass it and query the upstream DNS server directly.

dig @192.168.1.2 service.example.com
status: NOERROR
service.example.com → 192.168.1.100

That narrows it down quickly. The DNS server has the correct answer; the client is declining to use it.

resolvectl query service.example.com
resolve call failed: DNSSEC validation failed: no-signature

At that point it becomes clear that the resolver is receiving a valid answer and rejecting it during validation, so the issue sits with client-side resolver behaviour.


Two Models That Don’t Agree

The domain is publicly registered and DNSSEC-signed, so the client resolver validates responses against the public trust chain. At the same time, the local DNS server is deliberately overriding part of that same zone with private records for internal use. Inside the LAN, service.example.com points to a private address, while externally the same name resolves to a signed public endpoint.

From the point of view of the local network, the override is correct and expected. From the point of view of a validating resolver, it is an answer that cannot be verified against the public record. The resolver therefore rejects it rather than accepting something it cannot confirm.

This is where the problem tends to be misread as a fault in DNS or in the client. In practice, it is the result of two valid approaches being applied at the same time.

One option is to keep DNSSEC and move internal services onto a separate namespace, such as service.home.arpa. Namespaces under .arpa are reserved for this purpose, so there is no overlap with public DNS and validation works cleanly. From a design perspective, that is consistent and predictable. In practice, it introduces a second naming scheme, and that change carries through certificates, proxy configuration, and anything that already depends on the existing names.

The other option is to keep the existing namespace and adjust the client resolver to align with it. Disabling DNSSEC allows the local override to be accepted, so service.example.com resolves to the private address on the LAN while continuing to resolve publicly outside it.


Choosing Simplicity Over Purity

In this case, the system was already built around a single namespace tied to the registered domain. That naming had been carried into scripts, bookmarks, and proxy rules over time, and it defined how services were accessed both internally and externally. Introducing a second namespace would have required revisiting that structure and updating anything that depended on it.

Disabling DNSSEC was a contained change on the affected clients. It aligned the resolver with the way the environment was already designed to operate. In a controlled setup where the DNS path, gateway, and LAN are trusted, the associated risk is low, and TLS continues to provide end-to-end protection for the services themselves.

After that change, resolution behaved as expected. One FQDN per service, internal traffic stayed on-net, and the existing operational model remained intact.

Conclusion

While both approaches are valid, they optimise for different intentions.

Using a separate internal namespace preserves DNSSEC and maintains a clear separation between internal and external views of a domain. It aligns with best practice, but introduces a second naming model that must be carried through certificates, proxies, and client configurations.

Keeping a single namespace with split-horizon DNS is a common and practical pattern. It keeps naming consistent, avoids hairpinning, and simplifies exposure of services. Disabling DNSSEC on selected client systems is a small concession in a controlled environment where the DNS path is already trusted, and it does not materially increase the attack surface.

In this case, the simpler model remained the better fit. The naming scheme stayed consistent, the operational overhead stayed low, and the trade-off was understood and accepted.