Every time you type a website address into your browser, your computer has to figure out the actual numeric IP address behind that name. This translation process is called DNS resolution, and it happens dozens of times a day without you noticing. But that resolution process isn’t free — it takes time, and it puts load on the DNS servers that answer these queries.
A caching name server solves this problem. Instead of asking a remote DNS server for the same answer over and over again, a caching name server remembers (“caches”) the answer the first time it gets it, and serves that same answer instantly the next time it’s needed — until the answer expires.
In this article, we’ll build a caching-only DNS server from scratch on Linux, understand exactly what it does, and learn how to test, tune, and troubleshoot it. This guide is written so that both complete beginners and experienced network engineers can get value from it.
What Is a Caching Name Server?
A caching name server (sometimes called a “caching-only” DNS server) is a DNS server that does not hold authoritative records for any domain. Its only job is to:
- Receive DNS queries from clients (like your laptop, a printer, or a server).
- Forward those queries to real authoritative DNS servers if it doesn’t already know the answer.
- Store (“cache”) the answer for a period of time defined by the record’s TTL (Time To Live).
- Answer future queries for the same name directly from its cache, without going back out to the internet.
Think of it like a librarian who doesn’t own any books but knows exactly which library has what you need. The first time you ask for a book, the librarian goes and fetches it for you. But the librarian keeps a photocopy on their desk — so if you (or your coworker) ask for the same book again soon after, they hand you the copy immediately instead of making another trip.
Why Use a Caching Name Server?
| Benefit | Explanation |
|---|---|
| Faster lookups | Cached answers are returned instantly from local memory instead of round-tripping across the internet. |
| Reduced bandwidth | Fewer external DNS queries means less network traffic leaving your site. |
| Reduced load on upstream DNS | Public/root DNS servers see fewer repeated requests. |
| Improved reliability | If the upstream DNS is briefly unavailable, cached answers can still be served. |
| Better user experience | Web pages, apps, and services load names faster because resolution is quicker. |
How Caching DNS Fits Into the Bigger DNS Picture
Understanding where a caching server sits in the DNS hierarchy makes everything else easier to understand.
flowchart LR
A[Client Computer] --> B[Caching Name Server]
B -->|Cache Hit| A
B -->|Cache Miss| C[Root DNS Server]
C --> D[TLD DNS Server e.g. .com]
D --> E[Authoritative DNS Server]
E --> B
B --> AWhen a client asks the caching server for www.example.com:
- If the answer is already cached and not expired, the caching server responds immediately (cache hit).
- If not, the caching server performs a recursive lookup: it queries a root server, then a TLD server, then the authoritative server for
example.com, gets the final answer, stores it, and returns it to the client (cache miss).
Prerequisites
Before we configure anything, make sure you have:
- A Linux server (examples here use a Debian/Ubuntu or RHEL/CentOS-based system).
- Root or
sudoaccess. - Basic familiarity with the command line.
- A static IP address configured on the server (recommended, since clients will point to this fixed address).
Step 1: Install BIND (the DNS Server Software)
The most common software used to build a caching name server on Linux is BIND (Berkeley Internet Name Domain).
On Debian/Ubuntu:
sudo apt update
sudo apt install bind9 bind9utils bind9-doc -yOn RHEL/CentOS/Rocky Linux:
sudo dnf install bind bind-utils -yOnce installed, check the service status:
sudo systemctl status named # RHEL/CentOS
sudo systemctl status bind9 # Debian/UbuntuStep 2: Understand the Configuration Files
BIND’s main configuration file locations differ slightly by distribution:
| Distribution | Main Config File | Options File |
|---|---|---|
| Debian/Ubuntu | /etc/bind/named.conf | /etc/bind/named.conf.options |
| RHEL/CentOS | /etc/named.conf | Same file (all-in-one) |
On Debian-based systems, named.conf simply includes three other files:
include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
include "/etc/bind/named.conf.default-zones";For a pure caching server, we mostly care about named.conf.options.
Step 3: Configure the Caching Options
Open the options file:
sudo nano /etc/bind/named.conf.optionsA minimal but production-ready caching configuration looks like this:
options {
directory "/var/cache/bind";
// Only allow queries from our internal network
allow-query { 192.168.1.0/24; localhost; };
// Forward unresolved queries to upstream public resolvers
forwarders {
1.1.1.1;
8.8.8.8;
};
forward only;
// Enable caching-related tuning
max-cache-size 256m;
max-cache-ttl 86400;
max-ncache-ttl 3600;
recursion yes;
dnssec-validation auto;
listen-on { any; };
listen-on-v6 { any; };
};
Explanation of Key Directives
| Directive | Purpose |
|---|---|
directory | Where BIND stores its working files and cache. |
allow-query | Restricts which clients/networks may query this server — critical for security. |
forwarders | Upstream DNS servers to ask when the answer isn’t cached locally. |
forward only | Tells BIND to only use forwarders instead of doing its own recursive root lookups. Remove this line if you want full recursive resolution instead of forwarding. |
max-cache-size | Caps how much memory the cache can use. |
max-cache-ttl | Maximum time (seconds) any record stays cached, regardless of the TTL sent by the authoritative server. |
recursion yes | Allows the server to perform recursive lookups on behalf of clients. |
dnssec-validation auto | Validates DNSSEC signatures automatically for security. |
Step 4: Validate and Restart
Always check your configuration syntax before restarting the service — a typo can take down DNS for your whole network.
sudo named-checkconf /etc/bind/named.confIf there’s no output, the syntax is valid. Now restart BIND:
sudo systemctl restart bind9 # Debian/Ubuntu
sudo systemctl restart named # RHEL/CentOSEnable it to start on boot:
sudo systemctl enable bind9Step 5: Test the Caching Name Server
Use the dig utility to query your new caching server directly:
dig @127.0.0.1 www.example.comLook at the Query time field in the output. Run the same command a second time:
dig @127.0.0.1 www.example.comYou should see the Query time drop dramatically (often from 30–100ms down to 0–1ms) — that’s proof the answer came from cache instead of a fresh internet lookup.
Point a Client at the Caching Server
On a client machine (Linux example), edit /etc/resolv.conf or your network manager settings:
nameserver 192.168.1.10Where 192.168.1.10 is the IP of your caching DNS server.
Real-World Example: A Small Office Network
Imagine a small office with 40 employees, all making DNS queries throughout the day — visiting the same handful of company SaaS tools (email, CRM, chat, calendar) repeatedly.
Without a caching server, every single query for mail.google.com, slack.com, or office.com goes out to the internet. With a caching server sitting on the office router or a small internal Linux box:
- The first employee who visits
slack.comin the morning triggers a real lookup. - Every other employee who visits
slack.comafterward (for the TTL duration, often 300 seconds or more) gets an instant cached answer.
This can cut outbound DNS query volume by well over 90% in a busy office.
Cisco Example: Caching DNS on IOS Devices
Cisco routers can also act as simple caching (and forwarding) DNS servers using the ip dns server feature:
ip domain lookup
ip name-server 8.8.8.8 1.1.1.1
ip dns serverThis turns the router itself into a lightweight caching resolver for clients on the LAN — useful in branch offices where deploying a full Linux BIND server isn’t practical.
Python Example: Querying and Timing DNS Cache Behavior
You can use Python with the dnspython library to test cache performance programmatically:
import dns.resolver
import time
resolver = dns.resolver.Resolver()
resolver.nameservers = ['192.168.1.10'] # your caching server
domain = 'www.example.com'
start = time.time()
resolver.resolve(domain)
print(f"First query: {time.time() - start:.4f} seconds")
start = time.time()
resolver.resolve(domain)
print(f"Second (cached) query: {time.time() - start:.4f} seconds")Running this script twice in a row will clearly show the second query completing much faster, confirming the cache is working.
Monitoring the Cache
BIND includes a statistics channel you can enable to monitor cache hits, misses, and memory usage.
statistics-channels {
inet 127.0.0.1 port 8053 allow { 127.0.0.1; };
};After restarting BIND, view stats with:
curl http://127.0.0.1:8053You can also dump the current cache contents to a file for inspection:
sudo rndc dumpdb -cache
cat /var/cache/bind/named_dump.dbBest Practices
- Restrict
allow-queryto your internal networks only — never leave a caching resolver open to the public internet (this prevents it from being abused in DNS amplification attacks). - Set a sensible
max-cache-ttl— too high, and clients get stale data after a real DNS change; too low, and you lose caching benefits. - Use multiple forwarders for redundancy (e.g., both
1.1.1.1and8.8.8.8). - Enable DNSSEC validation to protect against cache poisoning attacks.
- Monitor cache size so it doesn’t exceed available system memory.
- Log queries during testing, then disable verbose logging in production to save disk I/O.
- Keep BIND updated — DNS software is a common attack target, and patches matter.
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
dig returns SERVFAIL | Forwarders unreachable or misconfigured | Check forwarders list and network connectivity: dig @8.8.8.8 example.com |
dig returns REFUSED | Client IP not covered by allow-query | Update the allow-query ACL to include the client’s subnet |
| Service won’t start | Syntax error in config | Run named-checkconf and fix reported line numbers |
| No caching benefit observed | max-cache-ttl set too low, or upstream TTLs are very short | Check TTL values with dig output, adjust config |
| Clients still using old IP after DNS change | Long TTL still cached | Flush cache with sudo rndc flush |
| Port 53 already in use | systemd-resolved or another DNS service conflicting | Disable conflicting service: sudo systemctl disable systemd-resolved |
Useful Diagnostic Commands
# Check BIND is listening on port 53
sudo ss -tulnp | grep :53
# Flush the entire cache
sudo rndc flush
# Flush a specific domain from cache
sudo rndc flushname example.com
# View real-time query logs
sudo tail -f /var/log/syslog | grep namedSecurity Considerations
A misconfigured caching name server is a serious security risk. The two biggest dangers are:
- Open resolvers — if your caching server answers queries from any IP on the internet, attackers can use it in DNS amplification DDoS attacks against third parties. Always restrict
allow-queryandallow-recursion. - Cache poisoning — attackers try to inject fake DNS answers into your cache. DNSSEC validation and keeping BIND updated are your main defenses.
Understanding Negative Caching
Most people think of caching purely in terms of successful answers, but DNS also caches failures — this is called negative caching. If a client queries for typo.example.com and gets an NXDOMAIN response (domain doesn’t exist), the caching server remembers that failure too, for a period controlled by the minimum field of the zone’s SOA record (or max-ncache-ttl locally).
This matters because without negative caching, a misbehaving application that repeatedly queries a non-existent hostname could generate a flood of pointless upstream queries. With negative caching enabled, the second and subsequent queries for that same bad name are answered instantly from the local “no such name” cache instead of hitting the network again.
# Observe negative caching behavior
dig nonexistent-subdomain-xyz.example.com
dig nonexistent-subdomain-xyz.example.com # should be faster the second timeSizing Your Caching Server for Real Traffic
A question that comes up quickly once a caching server is deployed: how much memory and CPU does it actually need? The answer depends heavily on query volume and the diversity of names being queried.
| Environment | Approx. Query Volume | Suggested max-cache-size | Notes |
|---|---|---|---|
| Home network (5–10 devices) | A few hundred queries/hour | 32–64 MB | Default settings are usually fine |
| Small office (20–50 users) | A few thousand queries/hour | 128–256 MB | Monitor memory growth over a week |
| Mid-size company (200+ users) | Tens of thousands of queries/hour | 512 MB–1 GB | Consider dedicated hardware/VM |
| ISP-scale resolver | Millions of queries/hour | Multiple GB, often clustered | Requires dedicated engineering and redundancy |
A good rule of thumb: start with a conservative cache size, monitor actual memory usage with rndc stats or the statistics channel over a week of real traffic, and adjust upward only if you see the cache is being evicted (old entries removed to make room for new ones) more often than expected.
Building Redundancy: Running Two Caching Servers
A single caching name server is a single point of failure. If it goes down, every client pointed at it loses DNS resolution entirely — which, in practice, means the network “feels broken” even though every other service might be running fine.
The standard solution is to run two independent caching servers and configure every client with both:
nameserver 192.168.1.10
nameserver 192.168.1.11Most resolver implementations will automatically fail over to the second server if the first doesn’t respond within its timeout window. For even higher availability, some organizations place a virtual IP (VIP) in front of a pair of caching servers using keepalived or a load balancer, so clients only ever need to know one address, and failover happens transparently at the network layer.
flowchart TD
Client --> VIP[Virtual IP 192.168.1.9]
VIP --> DNS1[Caching Server 1]
VIP --> DNS2[Caching Server 2]Frequently Asked Questions
Does a caching server need to be publicly reachable on the internet? No — and it generally shouldn’t be. A caching/recursive resolver should only answer queries from clients you trust (your own network). Exposing it to the whole internet turns it into an “open resolver,” which attackers can abuse for DNS amplification DDoS attacks against third parties.
How is a caching server different from just using a public DNS service like 8.8.8.8 directly on every client? Technically you could point every client directly at a public resolver, and many home networks do. But running your own local caching server means the first client in your office to look up a name pays the full internet round-trip, and every other client afterward gets an instant local answer — reducing overall latency and outbound bandwidth use across your whole network, not just for one device.
Can a caching server also serve my own domain’s records? Yes, technically the same BIND instance can be both a caching resolver for outbound queries and an authoritative server for zones you own — but many administrators prefer to keep these roles on separate servers (or at least separate BIND views) for clarity, security, and easier troubleshooting.
What happens if my caching server’s cache becomes corrupted or has stale, wrong data? Simply flush it: sudo rndc flush clears everything, and sudo rndc flushname <domain> clears a single problematic entry without disrupting the rest of the cache.
Summary
A caching name server is one of the simplest, highest-impact pieces of infrastructure you can deploy on a network. It speeds up name resolution, reduces external bandwidth usage, and adds a layer of resilience if upstream DNS has temporary issues. Using BIND on Linux, you can have a secure, functioning caching resolver running in under fifteen minutes — and with the tuning and monitoring techniques covered here, you can operate it confidently in a production environment.
