Split-Horizon DNS
Split-horizon (split-brain) DNS with BIND views. Different answers for internal vs external clients.
What Split-Horizon DNS Does
Split-horizon (split-brain) DNS returns different answers for the same query depending on who’s asking. Internal clients get private IPs; external clients get public IPs. This lets you use the same domain name everywhere without exposing internal infrastructure.
BIND Views — The Implementation
acl "internal" { 10.50.0.0/16; 172.16.0.0/12; 127.0.0.0/8; };
view "internal" {
match-clients { internal; };
recursion yes;
allow-query { internal; };
zone "inside.domusdigitalis.dev" IN {
type master;
file "inside.domusdigitalis.dev.internal.zone";
};
zone "1.50.10.in-addr.arpa" IN {
type master;
file "10.50.1.internal.rev";
};
};
view "external" {
match-clients { any; };
recursion no;
allow-query { any; };
zone "inside.domusdigitalis.dev" IN {
type master;
file "inside.domusdigitalis.dev.external.zone";
};
};
Views are evaluated in order — first match wins. internal is listed first so trusted clients match before any. External view disables recursion (authoritative-only for the internet).
Zone Files for Each View
; inside.domusdigitalis.dev.internal.zone
$TTL 3600
@ IN SOA ns1.inside.domusdigitalis.dev. admin.domusdigitalis.dev. (
2026041001 3600 900 604800 86400
)
@ IN NS ns1.inside.domusdigitalis.dev.
@ IN NS ns2.inside.domusdigitalis.dev.
ns1 IN A 10.50.1.2
ns2 IN A 10.50.1.3
ise-01 IN A 10.50.1.20
dc01 IN A 10.50.1.50
vault IN A 10.50.1.60
nas IN A 10.50.1.70
Internal zone has all hosts, all records, full AD service discovery SRV records.
; example.com.external.zone
$TTL 3600
@ IN SOA ns1.example.com. admin.example.com. (
2026041001 3600 900 604800 86400
)
@ IN NS ns1.example.com.
@ IN NS ns2.example.com.
@ IN A 198.51.100.10
www IN A 198.51.100.10
@ IN MX 10 mail.example.com.
mail IN A 198.51.100.11
External zone exposes only what must be publicly reachable. No internal hostnames, no SRV records, no reverse zones.
View Ordering Rules
# CORRECT — specific before general
view "internal" { match-clients { internal; }; ... };
view "external" { match-clients { any; }; ... };
# WRONG — any matches everything, internal view is never reached
view "external" { match-clients { any; }; ... };
view "internal" { match-clients { internal; }; ... };
# If a zone exists in one view, it must exist in all views
# or clients in the other view get REFUSED for that zone
BIND requires consistency across views. A zone in the internal view but missing from the external view means external queries for that zone return REFUSED rather than NXDOMAIN.
Testing Split-Horizon
dig @10.50.1.90 inside.domusdigitalis.dev A +short
# From an external machine or using a public DNS
dig @public-ns.example.com example.com A +short
dig @8.8.8.8 example.com A +short
If your domain is publicly delegated, querying public DNS shows the external view’s answer.
sudo rndc status
BIND’s query log (when debug is enabled) shows which view served each query. Enable temporarily with rndc trace 1.
VyOS Dual-BIND and Split-Horizon
# Both vyos-01 and vyos-02 need identical view definitions
# Internal view: same zone data (master/slave replication)
# External view: only if these servers face the internet
# On vyos-01 (master)
view "internal" {
match-clients { internal; };
zone "inside.domusdigitalis.dev" { type master; file "inside.domusdigitalis.dev.internal.zone";
allow-transfer { 10.50.1.3; }; };
};
# On vyos-02 (slave)
view "internal" {
match-clients { internal; };
zone "inside.domusdigitalis.dev" { type slave; masters { 10.50.1.2; };
file "slaves/inside.domusdigitalis.dev.internal.zone"; };
};
When using views with master/slave replication, the zone transfer happens within the context of the view. Both servers must have matching view definitions.
Common Pitfalls
-
Forgot to put all zones in all views: clients in the missing view get REFUSED, not NXDOMAIN
-
ACL ordering:
anybefore a specific ACL means the specific ACL is never reached -
Zone transfers across views: slave must define the zone in the same view as the master
-
Recursion in external view: never enable — you become an open resolver for the internet
-
Different serials: each view has its own zone file with its own serial — increment independently
See Also
-
BIND — named.conf view configuration
-
Zones — zone file management per view
-
Authoritative — master/slave with views