How-Tos

How-To: Watch Your DNS Zone for Unexpected Changes With dig and a Timer

Leave a comment

If you run your own authoritative DNS, you want to know the minute a record changes when you didn’t change it. A typo, a bad deploy or someone hijacking a record can all send your visitors somewhere else. Here’s a small, read-only watcher built from dig, a baseline file and a systemd timer. Everything below uses example.com, so swap in your own zone and nameservers.

1. Make a baseline

mkdir -p ~/dnswatch && cd ~/dnswatch
cat > records.txt <<'R'
example.com A
example.com AAAA
example.com MX
example.com NS
example.com TXT
www.example.com A
R
while read -r name type; do
  printf '%s %s ' "$name" "$type"
  dig +short @ns1.example.com "$name" "$type" | sort | tr '\n' ' '; echo
done < records.txt > baseline.txt

Read baseline.txt once by eye and make sure it really is what you expect.

2. The check script

#!/usr/bin/env bash
# ~/dnswatch/check.sh  (read-only: only queries DNS)
cd ~/dnswatch || exit 1
NS=(ns1.example.com ns2.example.com)
rc=0
for ns in "${NS[@]}"; do
  while read -r name type; do
    printf '%s %s ' "$name" "$type"
    dig +short @"$ns" "$name" "$type" | sort | tr '\n' ' '; echo
  done < records.txt > "now-$ns.txt"
  if ! diff -u baseline.txt "now-$ns.txt" > "diff-$ns.txt"; then
    echo "DNS CHANGE on $ns:"; cat "diff-$ns.txt"; rc=1
  fi
done
# Both servers should serve the same SOA serial
s1=$(dig +short @"${NS[0]}" example.com SOA | awk '{print $3}')
s2=$(dig +short @"${NS[1]}" example.com SOA | awk '{print $3}')
[ "$s1" = "$s2" ] || { echo "SOA serial mismatch: $s1 vs $s2"; rc=1; }
exit $rc

Run chmod +x check.sh and try it once by hand.

3. Run it every 15 minutes

# ~/.config/systemd/user/dnswatch.service
[Unit]
Description=DNS zone watcher
[Service]
Type=oneshot
ExecStart=%h/dnswatch/check.sh

# ~/.config/systemd/user/dnswatch.timer
[Unit]
Description=Run DNS watcher every 15 minutes
[Timer]
OnCalendar=*:0/15
Persistent=true
[Install]
WantedBy=timers.target
systemctl --user daemon-reload
systemctl --user enable --now dnswatch.timer
journalctl --user -u dnswatch.service -n 20   # see the results

A failed run shows up in systemctl --user --failed. From there you can hook it to whatever alerting you already use.

4. When you make a real change

Bump the serial, reload the zone, wait for both servers to agree, then rebuild baseline.txt with step 1. A nice side effect: if you forget to bump the serial, the mismatch check catches it.

Why I like this

It’s one job, read-only and boring, and that’s the point. It doesn’t touch the zone, it just tells you when reality stops matching what you expect.

Have a blessed day! And Happy Hacking!

References & Further Reading

Subscribe (RSS) Follow How-Tos via RSS

Leave a note

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.