Andrew Mercer
on this page

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).

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
  • BIND ARM, view statement documentation: https://bind9.readthedocs.io/
  • AWS Route 53, Private Hosted Zones: https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-private.html