InfraRunBook
    Back to articles

    Configuring Split-Horizon DNS with BIND9 Views on Ubuntu – Production Guide

    DNS
    Published: Feb 27, 2026
    Updated: Feb 27, 2026

    Learn how to configure split-horizon DNS using BIND9 named views on Ubuntu, returning different DNS answers for internal and external clients in a production environment.

    Configuring Split-Horizon DNS with BIND9 Views on Ubuntu – Production Guide

    Introduction

    Split-horizon DNS — also known as split-brain DNS or split DNS — is a technique where a DNS server returns different answers depending on the origin of the query. Internal clients querying from within the corporate network receive private IP addresses, while external clients on the internet receive public-facing addresses. This is critical for any organisation running internal infrastructure that is also partially exposed to the internet.

    BIND9 implements split-horizon DNS through a powerful feature called views. A view defines a set of DNS zones and the set of clients (by IP range or ACL) that will be served by that view. This guide walks through a complete, production-ready split-horizon DNS setup using BIND9 on Ubuntu 22.04 LTS for the domain

    solvethenetwork.com
    , hosted on the server ns-infrarunbook-01.solvethenetwork.com.

    Scenario: The web server
    www.solvethenetwork.com
    has an internal IP of
    10.10.1.50
    and a public IP of
    203.0.113.100
    . Internal staff should resolve to
    10.10.1.50
    ; external users should resolve to
    203.0.113.100
    .

    Prerequisites

    • Ubuntu 22.04 LTS server with root or sudo access
    • BIND9 installed (
      apt install bind9 bind9utils bind9-doc
      )
    • Internal subnet:
      10.10.0.0/16
    • External interface with public IP:
      203.0.113.1
      (DNS server public IP)
    • Domain:
      solvethenetwork.com

    Step 1 — Install BIND9

    If BIND9 is not yet installed on ns-infrarunbook-01, install it now:

    apt update
    apt install -y bind9 bind9utils bind9-doc dnsutils
    systemctl enable named
    systemctl start named
    

    Verify BIND9 is running and listening:

    systemctl status named
    ss -tulnp | grep :53
    

    Step 2 — Define ACLs in named.conf.options

    Access Control Lists (ACLs) define which client ranges are considered "internal". Open

    /etc/bind/named.conf.options
    and add your ACL definitions at the very top, before the
    options
    block:

    acl "infrarunbook-internal" {
        10.10.0.0/16;
        127.0.0.1;
        ::1;
    };
    
    options {
        directory "/var/cache/bind";
    
        recursion yes;
        allow-recursion { infrarunbook-internal; };
    
        listen-on { any; };
        listen-on-v6 { any; };
    
        dnssec-validation auto;
    
        auth-nxdomain no;
    
        // Do not allow zone transfers to anyone by default
        allow-transfer { none; };
    
        // Prevent DNS amplification attacks
        allow-query { any; };
    };
    

    Key points:

    • Recursion is restricted to
      infrarunbook-internal
      — external clients cannot use this server as a recursive resolver.
    • Zone transfers are denied globally; they will be enabled per-view if needed for secondaries.
    • allow-query { any; }
      permits all clients to query authoritative zones (both views will handle this selectively).

    Step 3 — Configure Views in named.conf.local

    Views are defined in

    /etc/bind/named.conf.local
    . Each view must list its
    match-clients
    and reference the relevant zone files. The internal view must appear first. BIND evaluates views in order — the first match wins.

    // Internal view — served to corporate clients on 10.10.0.0/16
    view "internal" {
        match-clients { infrarunbook-internal; };
        recursion yes;
        allow-recursion { infrarunbook-internal; };
    
        zone "solvethenetwork.com" {
            type master;
            file "/etc/bind/zones/internal/db.solvethenetwork.com";
            allow-transfer { none; };
        };
    
        zone "10.10.in-addr.arpa" {
            type master;
            file "/etc/bind/zones/internal/db.10.10";
            allow-transfer { none; };
        };
    
        include "/etc/bind/named.conf.default-zones";
    };
    
    // External view — served to all other clients (internet)
    view "external" {
        match-clients { any; };
        recursion no;
    
        zone "solvethenetwork.com" {
            type master;
            file "/etc/bind/zones/external/db.solvethenetwork.com";
            allow-transfer { none; };
        };
    };
    

    Important: When using views, ALL zones — including the default zones (

    localhost
    ,
    .
    hint, etc.) — must be inside a view. The
    include "/etc/bind/named.conf.default-zones";
    line inside the internal view handles this. Do not include it outside any view.


    Step 4 — Create the Zone File Directories

    mkdir -p /etc/bind/zones/internal
    mkdir -p /etc/bind/zones/external
    chown -R root:bind /etc/bind/zones
    chmod -R 775 /etc/bind/zones
    

    Step 5 — Write the Internal Zone File

    Create

    /etc/bind/zones/internal/db.solvethenetwork.com
    . Internal clients will receive private IP addresses:

    ; Internal zone for solvethenetwork.com
    ; Served only to: 10.10.0.0/16
    $TTL 300
    @   IN  SOA  ns-infrarunbook-01.solvethenetwork.com. hostmaster.solvethenetwork.com. (
                2024022701  ; Serial (YYYYMMDDNN)
                3600        ; Refresh
                900         ; Retry
                604800      ; Expire
                300 )       ; Negative cache TTL
    
    ; Name servers
    @   IN  NS   ns-infrarunbook-01.solvethenetwork.com.
    
    ; A records — INTERNAL addresses
    ns-infrarunbook-01   IN  A   10.10.0.10
    www                  IN  A   10.10.1.50
    app                  IN  A   10.10.1.51
    db-primary           IN  A   10.10.2.10
    db-replica           IN  A   10.10.2.11
    smtp                 IN  A   10.10.3.5
    vpn                  IN  A   10.10.0.254
    
    ; CNAME records
    api   IN  CNAME  app.solvethenetwork.com.
    

    Step 6 — Write the Internal Reverse Zone File

    Create

    /etc/bind/zones/internal/db.10.10
    for PTR records of the
    10.10.0.0/16
    subnet:

    ; Reverse zone for 10.10.0.0/16
    $TTL 300
    @   IN  SOA  ns-infrarunbook-01.solvethenetwork.com. hostmaster.solvethenetwork.com. (
                2024022701  ; Serial
                3600        ; Refresh
                900         ; Retry
                604800      ; Expire
                300 )       ; Negative cache TTL
    
    @   IN  NS   ns-infrarunbook-01.solvethenetwork.com.
    
    ; PTR records
    10.0    IN  PTR  ns-infrarunbook-01.solvethenetwork.com.
    50.1    IN  PTR  www.solvethenetwork.com.
    51.1    IN  PTR  app.solvethenetwork.com.
    10.2    IN  PTR  db-primary.solvethenetwork.com.
    11.2    IN  PTR  db-replica.solvethenetwork.com.
    5.3     IN  PTR  smtp.solvethenetwork.com.
    254.0   IN  PTR  vpn.solvethenetwork.com.
    

    Step 7 — Write the External Zone File

    Create

    /etc/bind/zones/external/db.solvethenetwork.com
    . External clients receive only public IPs. Never expose RFC 1918 addresses here:

    ; External zone for solvethenetwork.com
    ; Served to: any (internet clients)
    $TTL 3600
    @   IN  SOA  ns-infrarunbook-01.solvethenetwork.com. hostmaster.solvethenetwork.com. (
                2024022701  ; Serial
                3600        ; Refresh
                900         ; Retry
                604800      ; Expire
                3600 )      ; Negative cache TTL
    
    ; Name servers
    @   IN  NS   ns-infrarunbook-01.solvethenetwork.com.
    
    ; A records — PUBLIC addresses only
    ns-infrarunbook-01   IN  A   203.0.113.1
    www                  IN  A   203.0.113.100
    smtp                 IN  A   203.0.113.105
    vpn                  IN  A   203.0.113.110
    
    ; MX record
    @   IN  MX  10  smtp.solvethenetwork.com.
    
    ; TXT records for email authentication
    @   IN  TXT  "v=spf1 mx a:smtp.solvethenetwork.com ~all"
    

    Note the significantly higher TTL (

    3600
    seconds) in the external zone — external records change less frequently and benefit from longer caching. The internal zone uses
    300
    seconds for faster propagation of internal changes.


    Step 8 — Validate Configuration

    Always validate before reloading BIND9 in production:

    # Check global named configuration
    named-checkconf
    
    # Check the internal forward zone
    named-checkzone internal /etc/bind/zones/internal/db.solvethenetwork.com
    
    # Check the internal reverse zone
    named-checkzone 10.10.in-addr.arpa /etc/bind/zones/internal/db.10.10
    
    # Check the external forward zone
    named-checkzone external /etc/bind/zones/external/db.solvethenetwork.com
    

    All checks should return

    OK
    with no errors. Fix any reported issues before proceeding.


    Step 9 — Reload BIND9

    systemctl reload named
    # OR use rndc for a graceful reload without stopping the daemon:
    rndc reload
    

    Check the systemd journal for errors:

    journalctl -u named --since "1 minute ago"
    

    Step 10 — Test Split-Horizon Resolution

    Test from an internal host (10.10.x.x range):

    # From an internal client (expects 10.10.1.50)
    dig @10.10.0.10 www.solvethenetwork.com A +short
    # Expected: 10.10.1.50
    
    # Reverse lookup
    dig @10.10.0.10 -x 10.10.1.50 +short
    # Expected: www.solvethenetwork.com.
    

    Test from an external host (or simulate with localhost using a different source):

    # From an external client (expects 203.0.113.100)
    dig @203.0.113.1 www.solvethenetwork.com A +short
    # Expected: 203.0.113.100
    
    # Confirm recursion is refused for external clients
    dig @203.0.113.1 google.com A +short
    # Expected: connection refused / REFUSED (recursion not available)
    

    Advanced: Adding a Secondary DNS Server with View-Aware Zone Transfers

    Secondary DNS servers in a split-horizon setup must also use views and must use TSIG keys for secure zone transfers. Here is an example using TSIG to allow a secondary (

    ns-infrarunbook-02
    at
    10.10.0.11
    ) to transfer the internal view zone:

    # Generate TSIG key on ns-infrarunbook-01
    tsig-keygen -a HMAC-SHA256 infrarunbook-xfer > /etc/bind/tsig-xfer.key
    
    # Output looks like:
    # key "infrarunbook-xfer" {
    #     algorithm hmac-sha256;
    #     secret "base64encodedkeyhere==";
    # };
    

    Include the key file in

    /etc/bind/named.conf
    and reference it in the view:

    include "/etc/bind/tsig-xfer.key";
    
    view "internal" {
        match-clients { infrarunbook-internal; key infrarunbook-xfer; };
        ...
        zone "solvethenetwork.com" {
            type master;
            file "/etc/bind/zones/internal/db.solvethenetwork.com";
            allow-transfer { key infrarunbook-xfer; };
            also-notify { 10.10.0.11; };
        };
    };
    

    Troubleshooting Common Issues

    • Error: "not in any view" — You have zones defined outside all views. Move all zones (including defaults) inside views.
    • Error: "view 'internal' not yet defined" — ACL used in a view is defined after the view. Define all ACLs in
      named.conf.options
      before
      named.conf.local
      is included.
    • External clients getting internal IPs — Your view order is wrong, or the
      match-clients
      ACL overlaps. External view must use
      match-clients { any; };
      and appear last.
    • Recursion available to external clients — Ensure
      recursion no;
      is set inside the external view, overriding any global setting.
    • Zone serial number not updated — After editing zone files, always increment the serial number or BIND9 secondary servers will not pull the new zone.

    Frequently Asked Questions

    Q: What is split-horizon DNS and why do I need it?

    A: Split-horizon DNS returns different DNS answers based on the client's source IP. It allows internal users to access services via private IPs (lower latency, no hairpinning through NAT) while external users receive public IPs. Without it, internal clients may fail to reach services if NAT hairpinning is not configured on the firewall.

    Q: Can I use split-horizon DNS with DNSSEC?

    A: Yes, but each view must be signed independently. The internal and external zones are separate zone files and will have separate DNSSEC signatures. Internal zones do not need to be DNSSEC-signed if all internal resolvers trust the authoritative server directly, but external zones should always be signed.

    Q: Do both views need to include the same hostname records?

    A: No. You only need to include records that are relevant to each view's audience. For example, internal database server records (

    db-primary
    ,
    db-replica
    ) should only appear in the internal view. External-facing services like
    www
    and
    smtp
    appear in both views with their respective IPs.

    Q: What happens if a client's IP is not matched by any view?

    A: BIND9 returns REFUSED. Since the external view uses

    match-clients { any; };
    , all clients will always match at least one view. The order matters — internal clients match the internal view first.

    Q: How do I update a zone record in production without downtime?

    A: Edit the zone file, increment the serial number, run

    named-checkzone
    to validate, then run
    rndc reload
    . This reloads zone files without dropping any connections or queries in flight. There is no downtime during a zone reload.

    Q: Can I have more than two views?

    A: Yes. BIND9 supports an unlimited number of views. You could have separate views for

    management-vlan
    (10.10.100.0/24),
    dmz
    (172.16.50.0/24), and
    external
    . Each view is matched in order, so the most specific ACLs should appear first.

    Q: How do I configure secondary (slave) DNS servers in a split-horizon setup?

    A: Secondary servers must also be configured with matching views and must use TSIG keys for zone transfers. The secondary's internal view pulls from the primary's internal view zone, and the external view pulls from the external zone. Without matching views on the secondary, zone transfers will fail.

    Q: What is the recommended TTL for internal vs external zones?

    A: Internal zones benefit from a lower TTL (300 seconds / 5 minutes) because internal IP changes propagate faster and clients flush their cache quickly. External zones can use higher TTLs (3600 seconds / 1 hour) because public IP changes are less frequent and longer caching reduces query load.

    Q: Will split-horizon DNS break DNSSEC validation for internal clients?

    A: Only if the internal zone has different records from the signed external zone and the internal resolver attempts to validate against the signed external zone data. To avoid this, either sign the internal zone separately or set the internal resolver to use the internal view directly (which uses the local authoritative data without needing to validate upstream).

    Q: How do I verify which view a client is hitting?

    A: Use

    rndc querylog on
    to enable query logging, then query from the client. The BIND9 log will show the view name alongside each query. Alternatively, check the answer section — if
    www.solvethenetwork.com
    returns
    10.10.1.50
    , you are in the internal view; if it returns
    203.0.113.100
    , you are in the external view.

    Q: Is split-horizon DNS a substitute for a firewall?

    A: Absolutely not. Split-horizon DNS is a convenience and performance tool, not a security boundary. An attacker who can reach internal services by guessing their IP addresses does not need DNS at all. Always enforce network-level access controls (firewall rules, VPN requirements) for internal services, regardless of DNS configuration.

    Frequently Asked Questions

    What is split-horizon DNS and why do I need it?

    Split-horizon DNS returns different DNS answers based on the client's source IP. It allows internal users to access services via private IPs while external users receive public IPs, preventing NAT hairpinning issues.

    Can I use split-horizon DNS with DNSSEC?

    Yes, but each view must be signed independently. The internal and external zones are separate zone files and will have separate DNSSEC signatures.

    Do both views need to include the same hostname records?

    No. Only include records relevant to each view's audience. Internal-only resources should only appear in the internal view.

    What happens if a client's IP is not matched by any view?

    BIND9 returns REFUSED. Using 'match-clients { any; }' in the external view ensures all clients match at least one view.

    How do I update a zone record in production without downtime?

    Edit the zone file, increment the serial number, validate with named-checkzone, then run 'rndc reload'. This reloads zones without dropping connections.

    Can I have more than two views?

    Yes. BIND9 supports an unlimited number of views. You can have separate views for management VLANs, DMZ networks, and external clients.

    How do I configure secondary DNS servers in a split-horizon setup?

    Secondary servers must also be configured with matching views and use TSIG keys for zone transfers. Each view on the secondary pulls from the corresponding view on the primary.

    What is the recommended TTL for internal vs external zones?

    Internal zones benefit from lower TTLs (300 seconds) for faster propagation. External zones can use higher TTLs (3600 seconds) since public IP changes are less frequent.

    Will split-horizon DNS break DNSSEC validation for internal clients?

    Only if the internal resolver attempts to validate against signed external zone data with different records. Sign internal zones separately or configure resolvers to use internal views directly.

    How do I verify which view a client is hitting?

    Use 'rndc querylog on' to enable query logging. The BIND9 log will show the view name alongside each query. You can also check the IP address returned in the answer section.

    Related Articles