Split-horizon DNS — also known as split-brain DNS or split-view DNS — is a configuration where a DNS server returns different answers depending on who is asking. Internal clients on your local network get private IP addresses (like 192.168.1.50), while external queries from the internet receive public IP addresses (like 203.0.113.10). This is one of the most powerful techniques for self-hosters who want to use the same domain names both inside and outside their network.
In this guide, we compare four popular open-source tools for implementing split-horizon DNS, with Docker Compose configurations, step-by-step setup instructions, and real-world deployment patterns.
Why Self-Hosted Split-Horizon DNS Matters
When you run self-hosted services — a Nextcloud instance, a Gitea forge, a Jellyfin media server — you typically want to access them using the same domain name whether you’re on your home network or browsing from a coffee shop. Without split-horizon DNS, you face a choice: use the public IP everywhere (wasting bandwidth by routing local traffic through the internet) or use the private IP locally and the public IP remotely (managing two separate hostnames for the same service).
Split-horizon DNS eliminates this problem. It’s the foundation of a proper homelab network, enabling:
- Consistent domain names —
nextcloud.example.comresolves correctly from everywhere - Reduced latency — local traffic stays local, no hairpin NAT required
- Improved security — internal infrastructure details stay hidden from external queries
- Service availability — services remain accessible locally even when the public internet connection is down
For related reading on DNS fundamentals, check out our self-hosted DNS resolver guide and DNS firewall and RPZ configuration tutorial.
Comparison: Split-Horizon DNS Solutions
| Feature | Pi-hole | dnsmasq | CoreDNS | BIND 9 |
|---|---|---|---|---|
| Split-horizon method | Custom DNS records + DHCP | Conditional forwarding per interface | Plugin chain with rewrite | Views (ACL-based zones) |
| Complexity | Low (web UI) | Low (config file) | Medium (YAML plugins) | High (named.conf) |
| Web UI | Yes (built-in) | No | Yes (optional dashboard) | No |
| Ad blocking | Yes | No | Via plugins | No |
| Docker support | Official image | Community images | Official image | Official image |
| Kubernetes ready | No | No | Yes (native) | No |
| Query logging | Yes (web dashboard) | Syslog only | Yes (log plugin) | Yes (named logs) |
| IPv6 support | Yes | Yes | Yes | Yes |
| DNSSEC validation | Via forwarder | Yes | Yes | Yes |
| Performance | Good (lightweight) | Excellent (minimal) | Good (Go-based) | Good (C-based) |
| Learning curve | Beginner | Beginner | Intermediate | Advanced |
| Best for | Home users, homelabs | Embedded, routers | Kubernetes, cloud | Enterprise DNS |
Pi-hole currently has 57,679 stars on GitHub and was last updated on 2026-04-22. It’s the most popular choice for home users because the web interface makes configuration straightforward, and the split-horizon setup leverages its built-in “Local DNS” records feature combined with DHCP-provided DNS.
Configuring Pi-hole for Split-Horizon DNS
Pi-hole handles split-horizon DNS through a combination of local DNS records and conditional forwarding. When internal clients query a domain that exists in your Local DNS configuration, Pi-hole returns the private IP. For everything else, queries are forwarded upstream.
Docker Compose Setup
| |
Split-Horizon Configuration via dnsmasq.d
Pi-hole uses dnsmasq under the hood. Add custom split-horizon rules by creating files in the mounted /etc/dnsmasq.d volume:
| |
With this setup, any internal client using Pi-hole as its DNS server will resolve nextcloud.example.com to 192.168.1.50. External clients querying your public DNS (not Pi-hole) will resolve it to your public IP via your authoritative DNS server.
Configuring dnsmasq for Split-Horizon DNS
dnsmasq is the lightweight DNS and DHCP server that powers Pi-hole and many router firmware projects. Running it standalone gives you a minimal, highly efficient split-horizon setup without the web UI overhead.
Docker Compose Setup
| |
Configuration File
| |
The dnsmasq approach is ideal when you want a simple, single-config-file solution. The addn-hosts directive lets you maintain a standard /etc/hosts file alongside dnsmasq rules, which is useful for managing dozens of internal services.
Configuring CoreDNS for Split-Horizon DNS
CoreDNS takes a fundamentally different approach: it’s a plugin-based DNS server written in Go. Each feature (forwarding, caching, rewriting, logging) is a separate plugin chained together in a Corefile. This makes it extremely flexible for complex split-horizon scenarios, especially in Kubernetes environments.
Docker Compose Setup
| |
Corefile Configuration
| |
For a more advanced split-horizon setup using the acl plugin to differentiate by client IP:
| |
CoreDNS shines when you need programmatic DNS resolution. The file plugin can read zone files, the hosts plugin handles static mappings, and the rewrite plugin transforms queries on the fly — all without restarting the server.
Configuring BIND 9 for Split-Horizon DNS
BIND 9 is the industry-standard authoritative and recursive DNS server. Its “views” feature is the most powerful split-horizon mechanism available, allowing completely different zone configurations based on the client’s source IP address, network interface, or other ACL criteria.
Docker Compose Setup
| |
named.conf with Views
| |
Internal Zone File
| |
External Zone File
| |
BIND 9 views are the most powerful split-horizon mechanism because they allow completely independent zone files for internal and external clients. This means you can have entirely different DNS records, different SOA serials, and different NS delegations for each view.
Deployment Patterns
Pattern 1: Pi-hole as Primary DNS with DHCP Integration
The simplest setup for a homelab. Configure your router’s DHCP server to hand out Pi-hole’s IP as the DNS server. Add all internal service IPs via Pi-hole’s Local DNS Records (or the dnsmasq.d method shown above). External clients continue to use your domain registrar’s DNS or your authoritative nameserver.
| |
Pattern 2: CoreDNS as Kubernetes DNS Gateway
In Kubernetes deployments, CoreDNS runs as the cluster DNS by default. Configure the rewrite and hosts plugins to handle split-horizon resolution for services that need to be accessible from both inside and outside the cluster.
| |
Pattern 3: BIND 9 Views for Enterprise DNS
For organizations with multiple office locations, BIND 9 views can be extended to provide geographic-aware DNS. Each office network gets its own view with optimized server selections.
| |
Troubleshooting Common Issues
DNS caching problems: When you change a split-horizon record, clients may still see the old IP due to OS-level DNS caching. On Linux, clear with sudo systemd-resolve --flush-caches. On macOS, use sudo dscacheutil -flushcache. Windows uses ipconfig /flushdns.
VPN split-DNS: When connected to a WireGuard or Tailscale VPN, your device’s DNS resolver may prioritize the VPN’s DNS server over local resolution. Configure your VPN to push the split-horizon DNS server as the primary DNS, or use domain-based routing (e.g., Tailscale’s MagicDNS). For more on secure remote access, see our WireGuard VPN deployment guide.
NAT loopback / hairpinning: If your router doesn’t support NAT loopback, external IP resolution from inside the network will fail. Split-horizon DNS is the correct solution — always resolve to the internal IP for local clients.
DNSSEC validation failures: When using split views, ensure DNSSEC signatures are consistent across both views. BIND 9 handles this with inline signing; for Pi-hole, ensure your upstream forwarder validates DNSSEC properly.
FAQ
What is the difference between split-horizon DNS and DNS views?
Split-horizon DNS is the general concept of returning different DNS answers based on the client’s location or network. DNS views are BIND 9’s specific implementation of this concept — an ACL-based mechanism that selects which zone file to serve based on the client’s source IP. Other tools like dnsmasq achieve the same result using address= directives, while CoreDNS uses the rewrite and hosts plugins.
Can Pi-hole truly do split-horizon DNS?
Pi-hole achieves split-horizon behavior through its Local DNS Records feature combined with dnsmasq configuration files in /etc/dnsmasq.d. It doesn’t have true “views” like BIND 9 — every client querying Pi-hole gets the same response. However, since Pi-hole is typically only queried by internal clients (configured as the DHCP DNS server), the effect is equivalent: internal clients get private IPs while external clients query your public authoritative DNS.
Do I need split-horizon DNS if I use a VPN?
If your VPN routes all DNS queries through the tunnel, you still need split-horizon DNS to ensure internal service names resolve to private IPs. Without it, nextcloud.example.com might resolve to the public IP, causing traffic to leave the VPN tunnel, traverse the internet, and re-enter through your firewall — a pattern called “trombone routing” that adds latency and defeats the purpose of the VPN.
Which tool should I choose for my homelab?
For most homelab users, Pi-hole is the best starting point — it provides a web interface, ad blocking, and straightforward split-horizon configuration through Local DNS records. If you need more advanced conditional forwarding or DHCP integration, dnsmasq running standalone is lighter and more configurable. For Kubernetes-based homelabs, CoreDNS integrates natively. Reserve BIND 9 for enterprise environments requiring completely independent zone files for internal and external views.
How do I test if split-horizon DNS is working correctly?
Use dig or nslookup from both an internal and external network to compare responses:
| |
If both return the same IP, your split-horizon configuration needs adjustment. Also verify that dig shows the correct answer source with +comments flag enabled.
Can I use split-horizon DNS with Docker containers?
Yes. When running DNS servers in Docker, use macvlan or host networking to ensure the DNS server is reachable on your local network IP (port 53). Standard Docker bridge networking creates a separate subnet that your LAN clients cannot reach directly. The Docker Compose examples in this guide use macvlan for Pi-hole and host networking for dnsmasq to solve this issue.