How to Configure Split-horizon DNS with Bind9 Views on Ubuntu Server

Learning how to configure split-horizon DNS with Bind9 Views on Ubuntu Server is one of the most practical skills you can add to your server administration toolkit. Split-horizon DNS lets you serve different DNS responses depending on where a query originates. Internal users get private IP addresses. External users get public-facing ones. This is especially useful for companies running internal services behind a firewall. It keeps your network clean, secure, and easy to manage. In this tutorial, you’ll set up two Bind9 views , one for internal clients and one for external clients , on Ubuntu Server. By the end, you’ll have a fully working split-horizon DNS configuration you can adapt for your own environment.

Prerequisites for Configuring Split-Horizon DNS with Bind9 Views

Before you begin, make sure you have the following in place:

Required software and access:

  • Ubuntu Server 20.04 or 22.04 (fresh install recommended)
  • Root or sudo access to the server
  • Bind9 installed (covered in Step 1)
  • A registered domain name (e.g., example.com)
  • Basic familiarity with DNS concepts like zones, records, and resolvers

Assumed knowledge: You should be comfortable working in the Linux terminal. You don’t need to be a DNS expert, but knowing what an A record and a zone file are will help.

Estimated time: 30–45 minutes depending on your familiarity with Bind9.

You’ll also want two IP ranges defined before you start. For this tutorial, the internal network is 192.168.1.0/24 and the external network represents everything else. Adjust these to match your actual setup.

For deeper background on how Bind9 works, check the official Bind9 documentation.

Step-by-Step Guide to Split-Horizon DNS with Bind9 Views on Ubuntu

See also: How to Create Custom Wordpress Gutenberg Blocks with the Block Editor Api

Step 1: Install Bind9 on Ubuntu Server

First, update your package list and install Bind9.

sudo apt update
sudo apt install bind9 bind9utils bind9-doc -y

Once installed, verify the service is running:

sudo systemctl status bind9

You should see active (running) in the output. If not, start it with sudo systemctl start bind9.

Step 2: Back Up the Default Configuration

Always back up your config files before editing them. This saves you if something goes wrong.

sudo cp /etc/bind/named.conf.local /etc/bind/named.conf.local.bak
sudo cp /etc/bind/named.conf.options /etc/bind/named.conf.options.bak

Step 3: Define ACLs in named.conf.options

Open the options file:

sudo nano /etc/bind/named.conf.options

Add ACL blocks at the very top, before the options block:

acl "internal-clients" {
    192.168.1.0/24;
    localhost;
    localnets;
};

acl "external-clients" {
    any;
};

Inside the options block, add these lines to allow recursion for internal clients only:

options {
    directory "/var/cache/bind";

    recursion yes;
    allow-recursion { internal-clients; };

    forwarders {
        8.8.8.8;
        8.8.4.4;
    };

    dnssec-validation auto;
    listen-on { any; };
};

Save and close the file with Ctrl+X, then Y, then Enter.

Step 4: Configure Views in named.conf.local

This is the core step. Open the local config file:

sudo nano /etc/bind/named.conf.local

Add the following two views. The internal view serves private IPs. The external view serves public IPs.

view "internal" {
    match-clients { internal-clients; };
    recursion yes;

    zone "example.com" {
        type master;
        file "/etc/bind/zones/db.example.com.internal";
    };
};

view "external" {
    match-clients { external-clients; };
    recursion no;

    zone "example.com" {
        type master;
        file "/etc/bind/zones/db.example.com.external";
    };
};

Save and close the file.

Step 5: Create the Zone Directory

sudo mkdir -p /etc/bind/zones

Step 6: Create the Internal Zone File

sudo nano /etc/bind/zones/db.example.com.internal

Add the following content. Replace 192.168.1.10 with your internal server IP.

$TTL 604800
@   IN  SOA ns1.example.com. admin.example.com. (
            2024010101 ; Serial
            3600       ; Refresh
            1800       ; Retry
            604800     ; Expire
            86400 )    ; Negative Cache TTL

@       IN  NS  ns1.example.com.
ns1     IN  A   192.168.1.10
@       IN  A   192.168.1.10
www     IN  A   192.168.1.10
mail    IN  A   192.168.1.20

Save and close.

Step 7: Create the External Zone File

sudo nano /etc/bind/zones/db.example.com.external

Add the following. Replace 203.0.113.10 with your real public IP.

$TTL 604800
@   IN  SOA ns1.example.com. admin.example.com. (
            2024010101 ; Serial
            3600       ; Refresh
            1800       ; Retry
            604800     ; Expire
            86400 )    ; Negative Cache TTL

@       IN  NS  ns1.example.com.
ns1     IN  A   203.0.113.10
@       IN  A   203.0.113.10
www     IN  A   203.0.113.10
mail    IN  A   203.0.113.20

Save and close.

Step 8: Validate and Restart Bind9

Check your configuration for syntax errors:

sudo named-checkconf

Check each zone file:

sudo named-checkzone example.com /etc/bind/zones/db.example.com.internal
sudo named-checkzone example.com /etc/bind/zones/db.example.com.external

If no errors appear, restart Bind9:

sudo systemctl restart bind9

Test from an internal client:

dig @192.168.1.10 www.example.com

You should get 192.168.1.10 back. Test from an external source and you should get 203.0.113.10.

Troubleshooting Common Bind9 View Configuration Issues

Error: “no matching view” in logs

Check /var/log/syslog for Bind9 errors. This usually means your ACLs don’t cover all possible clients. Add any; as a catch-all in your last view.

Both clients get the same response

Double-check your ACL definitions. Make sure the internal ACL lists your actual subnet. Run sudo named-checkconf again to catch any typos.

Bind9 won’t start after changes

Run this command to see detailed errors:

sudo journalctl -xe | grep named

Zone serial numbers must be unique. If you copy a zone file, update the serial number. The format YYYYMMDDNN works well.

Recursion refused for internal clients

Make sure your allow-recursion directive references the correct ACL name. ACL names are case-sensitive in Bind9.

For more detail on DNS record types and zone file syntax, the Ubuntu Server DNS documentation is a great reference.

Conclusion

You now know how to configure split-horizon DNS with Bind9 Views on Ubuntu Server. You’ve set up ACLs, created two separate views, written internal and external zone files, and tested the whole setup. This configuration gives you precise control over what DNS data different clients receive. Internal users stay on your private network. External users see only your public-facing addresses. From here, you can add more zones, set up reverse DNS, or add DNSSEC signing to your zones for extra security. Split-horizon DNS is a foundational technique. Getting it right makes your entire infrastructure easier to manage and more secure.

Similar Posts