Andrew Mercer
on this page

A LAN caching (recursive-only) resolver — not authoritative for any zone — forwarding to public resolvers, plus the chrooted hardening variant.

Install

sudo yum install -y bind bind-utils

Configuration

/etc/named.conf — starting point is the Red Hat default caching-only config, then the changes made to it:

acl "allowed" {
    192.168.0.0/24;
    localhost;
    localnets;
};

options {
    listen-on port 53 { 127.0.0.1; 192.168.0.254; };
    listen-on-v6 port 53 { ::1; };
    directory       "/var/named";
    dump-file       "/var/named/data/cache_dump.db";
    statistics-file "/var/named/data/named_stats.txt";
    memstatistics-file "/var/named/data/named_mem_stats.txt";
    allow-query     { localhost; allowed; };
    forwarders      { 8.8.8.8; 8.8.4.4; };

    /*
     - If you are building an AUTHORITATIVE DNS server, do NOT enable recursion.
     - If you are building a RECURSIVE (caching) DNS server, you need to enable
       recursion.
     - If your recursive DNS server has a public IP address, you MUST enable access
       control to limit queries to your legitimate users. Failing to do so will
       cause your server to become part of large scale DNS amplification
       attacks. Implementing BCP38 within your network would greatly
       reduce such attack surface.
    */
    recursion yes;

    dnssec-enable yes;
    dnssec-validation yes;

    bindkeys-file "/etc/named.iscdlv.key";
    managed-keys-directory "/var/named/dynamic";

    pid-file "/run/named/named.pid";
    session-keyfile "/run/named/session.key";
};

logging {
    channel default_debug {
        file "data/named.run";
        severity dynamic;
    };
};

zone "." IN {
    type hint;
    file "named.ca";
};

include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";

The only material changes from stock are the acl block and the two lines inside options:

acl "allowed" {
    192.168.0.0/24;
    localhost;
    localnets;
};

options {
    listen-on port 53 { 127.0.0.1; 192.168.0.254; };

    allow-query     { localhost; allowed; };
    forwarders      { 8.8.8.8; 8.8.4.4; };
    ...

What each piece is doing, since the stock file's comments explain the why but not always the specific values here: - listen-on is narrowed to the loopback plus the one LAN-facing address (192.168.0.254) this box actually serves — not any, which would also bind interfaces you may not want offering DNS (e.g., a WAN uplink). - allow-query { localhost; allowed; } is the actual access control the stock config's comment block warns is mandatory: without it, recursion yes plus an address reachable from outside the LAN is a textbook open-resolver amplification vector. - forwarders { 8.8.8.8; 8.8.4.4; } sends anything not already cached upstream to Google's public resolvers rather than having this box walk the full root→TLD→authoritative chain itself — appropriate for a small caching layer in front of a handful of LAN clients, trading a dependency on an external provider for simpler/faster resolution. - dnssec-enable/dnssec-validation yes mean this resolver validates signatures on anything it fetches — worth keeping on even for a small home resolver, since it costs nothing here and protects against a poisoned upstream answer.

One gap in the original config worth calling out: default_debug's channel is defined but no category statement routes anything to it, so in practice this logging block does very little beyond the RHEL package defaults already covered by other implicit categories. If you actually want visibility into what this resolver is doing, add an explicit category, e.g.:

logging {
    channel default_debug {
        file "data/named.run";
        severity dynamic;
    };
    category default { default_debug; };
};

Apply and Restart

sudo named-checkconf /etc/named.conf
sudo systemctl restart named
sudo systemctl enable named

Configure Clients

Point LAN clients at this resolver:

# /etc/resolv.conf
nameserver 192.168.0.254

Verify:

dig google.ca
# Confirm the SERVER line at the bottom of the output shows this resolver answered:
#   ;; SERVER: 192.168.0.254#53(192.168.0.254)

Hardening: Running named in a chroot

This was left as a "todo" in the original notes — filled in here. bind-chroot confines named's effective filesystem view to /var/named/chroot/, so a serious vulnerability that gives an attacker file access is contained to that subtree instead of the whole host. This is a meaningful hardening step for a resolver reachable from more than just localhost — worth doing here given this box already listens on a LAN-facing address.

Install

sudo yum install -y bind bind-utils bind-chroot

The bind-chroot package sets up /var/named/chroot/ and bind-mounts/symlinks the standard paths (/etc, /var/named, /var/log equivalents) into it automatically on most distributions — confirm with rpm -ql bind-chroot what your specific version relocates versus symlinks, since this has varied across RHEL major versions.

Permissions

If setting up or auditing the jail by hand:

chown root:named /var/named/chroot/
chown -R root:named /var/named/chroot/etc
find /var/named/chroot/etc -type f -exec chmod 0640 {} \;
find /var/named/chroot/etc -type d -exec chmod 0750 {} \;

chown root:named /var/named/chroot/var
chown named:named /var/named/chroot/var/log
chown named:named /var/named/chroot/var/tmp
chown root:named /var/named/chroot/var/named
chown -R named:named /var/named/chroot/var/named/data

(The original notes repeated the two find commands a second time after the var block — that was redundant; they only need to run once, against etc, as above.)

The pattern to remember: configuration (etc/) is root-owned, group-readable by named (0640/0750 — read-only for the daemon, since it shouldn't need to modify its own config at runtime), while the specific directories named actively writes to while running (var/log, var/tmp, var/named/data) are owned by named directly.

Enable the chroot-aware service

sudo systemctl enable --now named-chroot.service

Note this is a different systemd unit than plain named.service — enabling the chroot variant without disabling the original can lead to a confusing state where both are configured to start; disable the non-chroot unit if switching an existing install over:

sudo systemctl disable --now named.service

Path translation gotcha

Once running chrooted, every path in /etc/named.conf (directory, zone file paths, key files) is resolved inside the jail. directory "/var/named"; in the config means /var/named/chroot/var/named/ on the real filesystem. This trips people up when troubleshooting:

  • named-checkconf / named-checkzone run against the real (non-chrooted) path will report files missing that are actually present inside the jail — either run these tools from within the jail's root, or double-check you're pointing at /var/named/chroot/... on disk when inspecting files by hand.
  • Logs configured to a path like data/named.run (relative, per the stock config above) land at /var/named/chroot/var/named/data/named.run, not /var/named/data/named.run.

Verify

sudo named-checkconf
dig @192.168.0.254 google.ca
sudo systemctl status named-chroot

Troubleshooting

Same underlying causes as the Debian setup (see the companion homelab notes) apply here too — most commonly a log-path permission mismatch between the file's on-disk owner and the user named runs as (named, not bind, on RHEL-family systems), and rndc connection failures tracing back to named not actually being up. On a chrooted install specifically, also double check paths are being interpreted relative to /var/named/chroot/ before concluding a file is "missing."

References

  • https://www.isc.org/bind
  • https://en.wikipedia.org/wiki/BIND
  • https://bind9.readthedocs.io/en/latest/dnssec-guide.html