Running a local caching DNS for your app

By Jake Morrison in DevOps on Fri 04 January 2019

When your app is acting as a proxy to back end servers, it it may need to do a DNS lookup to convert the host name of the server into an IP hundreds of times a second. DNS can become the bottleneck for requests. It also puts heavy load on hosting provider DNS servers, which may not be able to take it, or they may get unhappy with you. Hard coding IPs of back end servers is dangerous, as they may change over time.

Running a local caching DNS server on the app server machine caches results of DNS lookups to make them fast and reduce load on external DNS servers.

The local DNS server forwards requests to the upstream DNS servers and caches the results.

Here is an example /etc/named.conf:

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

    filter-aaaa-on-v4 yes;

    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; };
    allow-recursion { 127.0.0.1; };
    allow-transfer { none; };
    notify no;

    minimal-responses yes;

    dnssec-enable yes;
    dnssec-validation yes;
    dnssec-lookaside auto;

    /* Path to ISC DLV key */
    bindkeys-file "/etc/named.iscdlv.key";

    managed-keys-directory "/var/named/dynamic";

    forwarders {
        8.8.8.8;
        8.8.4.4;
    };
    forward only;
};

include "/etc/named/rndc.key";

// controls {};
controls {
    inet 127.0.0.1 allow { localhost; } keys { "rndc-key"; };
};

statistics-channels {
    inet 127.0.0.1 port {{ bind_cache_statistics_port }} allow { 127.0.0.1; };
};

logging {
    // http://www.zytrax.com/books/dns/ch7/logging.html

    channel debug_log {
        file "/var/log/named/debug.log" versions 10 size 10m;
        severity dynamic;
        // debug and above. Assume the global debug level defined by either the command line parameter -d or by running rndc trace
        print-time yes;
        print-severity yes;
        print-category yes;
    };

    channel simple_log {
        file "/var/log/named/bind.log" versions 10 size 1m;
        //severity warning;
        severity notice;
        print-time yes;
        print-severity yes;
        print-category yes;
    };

    channel query_log {
        file "/var/log/named/query.log" versions 10 size 1m;
        severity dynamic;
        //severity warning;
        print-time yes;
        print-severity yes;
        print-category yes;
    };

    category default {
        simple_log;
        // Enable this to get debug logging
        //debug_log;
    };

    category queries {
        // Enable this to get query logging, it's not on for category default
        // query_log;
    };
};

view "localhost" {
    match-destinations { 127.0.0.1; };

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

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

You should also configure /etc/resolv.conf to specify the caching DNS first, followed by your upstream DNS servers:

nameserver 127.0.0.1
nameserver 8.8.8.8
nameserver 8.8.4.4

Here is an Ansible role that sets up the caching DNS server. It's also in Ansible Galaxy.