The core problem this solves is that your DNS zone data is often sensitive or optimized differently for internal vs. external consumption, and you need a way to present distinct views of the same zone to different types of clients.

Let’s see it in action with BIND 9, a common DNS server.

Imagine you have a zone file, internal.example.com.zone:

$TTL 86400
@       IN      SOA     ns1.internal.example.com. admin.internal.example.com. (
                        2023102701 ; serial
                        3600       ; refresh
                        1800       ; retry
                        604800     ; expire
                        86400      ; minimum TTL
                        )
        IN      NS      ns1.internal.example.com.
ns1     IN      A       192.168.1.10
server1 IN      A       192.168.1.20
db-server IN    A       192.168.1.30

And you want an external view, external.example.com.zone, which might hide internal IP addresses and perhaps only expose a public-facing web server:

$TTL 86400
@       IN      SOA     ns1.external.example.com. admin.external.example.com. (
                        2023102701 ; serial
                        3600       ; refresh
                        1800       ; retry
                        604800     ; expire
                        86400      ; minimum TTL
                        )
        IN      NS      ns1.external.example.com.
ns1     IN      A       203.0.113.10
www     IN      A       203.0.113.50

Here’s how you configure BIND 9 to serve these using view clauses in named.conf:

acl "internal_clients" { 192.168.1.0/24; 10.0.0.0/8; localhost; localnets; };

options {
    directory "/var/cache/bind";
    recursion yes;
    allow-query { any; };
};

view "internal" {
    match-clients { internal_clients; };
    zone "internal.example.com" {
        type master;
        file "/etc/bind/zones/internal.example.com.zone";
    };
    // You might also want to serve other internal zones here.
};

view "external" {
    match-clients { any; }; // Catch all other clients
    zone "external.example.com" {
        type master;
        file "/etc/bind/zones/external.example.com.zone";
    };
    // You might also want to serve other external zones here.
};

In this setup:

  • acl "internal_clients" defines the network ranges that are considered "internal."
  • The options block contains general server settings.
  • The first view "internal" block specifies that any client matching internal_clients will see the internal.example.com zone loaded from its corresponding file.
  • The second view "external" block acts as a fallback. Any client not matching internal_clients will fall into this view and see the external.example.com zone.

When a query for server1.internal.example.com arrives from 192.168.1.50:

  1. BIND checks the view clauses.
  2. match-clients { internal_clients; } for the "internal" view matches 192.168.1.50.
  3. The "internal" view is selected.
  4. BIND looks for the internal.example.com zone within this view and serves the IP 192.168.1.20.

When a query for server1.internal.example.com arrives from 203.0.113.100 (an external IP):

  1. BIND checks the view clauses.
  2. match-clients { internal_clients; } for the "internal" view does not match 203.0.113.100.
  3. BIND proceeds to the next view.
  4. match-clients { any; } for the "external" view matches 203.0.113.100.
  5. The "external" view is selected.
  6. BIND looks for the internal.example.com zone within this view. Since it’s not defined here, the query fails for that zone. If the query was for www.external.example.com, BIND would find it and serve 203.0.113.50.

The order of view blocks matters. BIND processes them sequentially and uses the first one that matches the client’s IP address. This allows for complex layering, where you might have a "super-internal" view, then a general "internal" view, and finally a default "external" view.

A common, but often overlooked, detail is how allow-query interacts with views. If allow-query is set in the global options block to a restrictive list, it applies before views are even considered. If you want views to completely control access, ensure your global allow-query is broad enough (e.g., { any; }) and then use match-clients within each view to enforce your desired access policies.

The next step is often managing different DNS record types or even entire subdomains differently for internal and external clients, which can lead you to explore response policy zones (RPZ) for more granular control.

Want structured learning?

Take the full Bind course →