1. What Split-Horizon DNS Is¶
Split-horizon (also called split-view, split-brain, or internal/external DNS) is the practice of giving different answers for the same name depending on who's asking. The canonical case: app.example.com should resolve to a private, RFC 1918 address (10.0.5.20) for clients on the corporate LAN or VPN, but to a public address (203.0.113.10, likely a load balancer or CDN edge) for everyone else on the internet.
This is distinct from — and often confused with — zone delegation (handing off a subdomain to different authority) and from ordinary per-record routing like geolocation-based answers (giving different public clients different public answers based on geography, covered in the geolocation-routing guide). Split-horizon specifically means: the same query, from different vantage points, is a genuinely different question — an internal client asking about app.example.com isn't asking "which nearby server should I use," it's asking "how do I reach this on my private network," and the answer space (private vs. public addressing) is categorically different, not just a performance optimization.
2. Why It's Needed¶
- Private addressing that's meaningless externally. Internal infrastructure often uses RFC 1918 addresses that simply aren't routable from the internet — publishing them externally wouldn't just be a privacy issue, it would be actively useless to an external client.
- Bypassing the public path for internal traffic. Even when a service is also reachable publicly, internal clients often benefit from resolving directly to an internal load balancer or the origin server, skipping a public CDN/WAF layer that adds latency for traffic that's already inside the trusted network.
- Security posture / information hiding. Internal-only services (admin panels, internal APIs, database endpoints) may have DNS names that shouldn't be resolvable — or even minimally enumerable — from outside the organization at all.
- VPN/remote-access transparency. Users on a VPN expect internal names to "just work" identically to being physically in the office, without needing separate internal-vs-external hostnames memorized.
3. Implementation Approaches¶
A. Separate authoritative servers entirely¶
The simplest conceptual model: two completely independent sets of authoritative servers for the same zone name, one reachable only internally, one public-facing — each with its own separate zone file, never synchronized automatically (or synchronized by a separate management process, e.g. octoDNS pushing shared records to both while excluding internal-only ones from the external copy).
Internal DNS servers (LAN-only, e.g. Active Directory-integrated)
→ zone "example.com" → app.example.com A 10.0.5.20
External DNS servers (internet-facing, e.g. cloud-managed DNS)
→ zone "example.com" → app.example.com A 203.0.113.10
Clients are steered to the correct server set purely by network configuration — internal DHCP hands out the internal resolvers' addresses, external clients (by definition, not on the internal network) can only reach the external ones. No special DNS server feature is required; the split is entirely a matter of which server a client can reach at all.
B. BIND view statements¶
A single named process serving different zone data to different clients based on their source address, as covered in the BIND guide (§3.5):
view "internal" {
match-clients { 10.0.0.0/8; 172.16.0.0/12; };
zone "example.com" {
type master;
file "/etc/bind/internal/db.example.com";
};
};
view "external" {
match-clients { any; };
zone "example.com" {
type master;
file "/etc/bind/external/db.example.com";
};
};
Views are evaluated top-to-bottom, first match wins — internal must come before the catch-all external view, or every client would match external first and internal clients would never see internal-view data. Once views are used at all, every zone in the config must be inside some view; mixing top-level zones and viewed zones isn't supported.
C. Cloud/managed DNS split policies¶
Managed providers (Route 53 Private Hosted Zones, Azure Private DNS Zones, Google Cloud DNS private zones) implement the same idea natively: a "private" zone is only resolvable from within specifically associated VPCs/VNets, while a separate "public" zone of the same name answers everyone else — the cloud provider's network fabric enforces the visibility boundary instead of BIND's match-clients doing it.
4. Keeping Internal and External Views Consistent¶
The operational risk unique to split-horizon setups: the two zones/views can silently drift, since nothing forces them to stay in sync automatically. Common patterns to manage this:
- Shared record set, internal-only overlay. Maintain one canonical record set (e.g., in octoDNS YAML, per the octoDNS guide) for everything that should be identical in both views, then a small internal-only overlay file for the records that genuinely differ (private addresses, internal-only names) — applied as two targets (internal view, external view) from mostly-shared source data, rather than two entirely hand-maintained zone files.
- Automated diffing/alerting. Periodically compare internal and external answers for names that are supposed to match, flagging unexpected divergence.
- Clear naming conventions for what's expected to differ, so an on-call engineer debugging a resolution issue doesn't have to guess whether a given name is supposed to answer differently internally vs. externally, or whether that's the bug.
5. Troubleshooting Split-Horizon Issues¶
Split-horizon setups produce a distinctive failure signature: "it works for me but not for them" (or vice versa) even though nothing about the record itself has changed — because the actual cause is which view/server the client's query landed on, not the record data itself.
# Query explicitly against the internal-facing server
dig @<internal-dns-ip> app.example.com
# Query explicitly against the external-facing server
dig @<external-dns-ip> app.example.com
# Compare — if they differ as designed, the split is working;
# if a client reports an unexpected result, the real question is
# which server *they* are actually querying, not whether the zone is wrong
The most common root cause of unexpected split-horizon behavior isn't a DNS misconfiguration at all — it's a client on the wrong network path getting routed to the wrong view: a VPN client whose split-tunnel config doesn't route DNS traffic through the tunnel, a device using a public resolver (1.1.1.1, 8.8.8.8) instead of the internal one due to a DHCP or manual override, or a caching layer between the client and the correct server having stale data from before the client's network context changed (e.g., a laptop that cached an external answer before connecting to VPN, per the general DNS guide's caching section).
6. Split-Horizon vs. Related Concepts¶
| Split-horizon | Zone delegation | Geolocation routing | |
|---|---|---|---|
| What varies | The answer, based on who's asking (internal vs. external) | Who's authoritative for a subtree of names | The answer, based on where an external client is located |
| Same zone, different data? | Yes — same name, different view | No — different zone, different owner | Yes — same name, different pool by geography |
| Typical driver | Private vs. public addressing, security | Delegated administrative control | Latency/compliance/load distribution |
7. Reference Links¶
- BIND ARM,
viewstatement documentation: https://bind9.readthedocs.io/ - AWS Route 53, Private Hosted Zones: https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-private.html