
Automate Daily DNS Checks for Your Mail Server Records
Before you run this
This guide builds a small bash script that queries your live DNS once a day and compares the MX, A, and AAAA records it gets back against the values you declare in a config file. If they don't match, it emails you. That's all it does.
It is read-only. It never touches DNS, your zone, your mail config, or anything else — it only runs dig and compares strings. There is nothing destructive here and nothing to roll back in the data sense.
- Privileges: running the check itself needs no root —
digworks fine as an unprivileged user. You only needsudoto install the script into/usr/local/bin, drop the config into/etc, and add the cron entry. Run it as a normal user first. - Test it first: read the script before you install it, and run it by hand once so you can see the output format and confirm your expected values are correct. The one real pitfall is a wrong value in your config file — that produces a false alarm, not an outage, but it trains you to ignore the alert, so get it right before you schedule it.
- What "correct" means here: the config file is your source of truth. The script tells you when DNS disagrees with it. It does not tell you which side is wrong — that's your judgement.
Assumed environment: Debian 12 or Ubuntu 22.04, bash, and cron. The dig binary comes from bind9-dnsutils on current Debian/Ubuntu (the old dnsutils package is now a transitional alias). On RHEL/Alma the package is bind-utils and you'd use a systemd timer or cron the same way.
Install dig
sudo apt-get update
sudo apt-get install -y bind9-dnsutils
Confirm it's there:
dig -v
Declare what you expect
Create a config file that states, in plain terms, the records your zone should be serving. This is the file you keep in sync with your real intent.
sudo mkdir -p /etc/dns-check
sudo tee /etc/dns-check/expected.conf >/dev/null <<'EOF'
# Replace every value below with YOUR real domain, host, and addresses.
DOMAIN="example.com"
MAIL_HOST="mail.example.com"
# MX: priority and target exactly as dig prints it, WITH the trailing dot.
# For multiple MX records, put each on its own line inside the quotes.
EXPECTED_MX="10 mail.example.com."
# Forward records for the mail host.
EXPECTED_A="203.0.113.10"
EXPECTED_AAAA="2001:db8::10"
EOF
Two things to get right:
dig +short MXprints the target with a trailing dot (10 mail.example.com.). Match that exactly.- If you run IPv4 only, set
EXPECTED_AAAA="". The script then expects an empty answer and will alert if an AAAA record suddenly appears.
For multiple MX records the value is just multi-line:
EXPECTED_MX="10 mail1.example.com.
20 mail2.example.com."
The script
#!/usr/bin/env bash
# Daily read-only check that live DNS matches /etc/dns-check/expected.conf
set -euo pipefail
CONFIG="${1:-/etc/dns-check/expected.conf}"
# Optional: query a specific resolver (e.g. an authoritative server or 1.1.1.1)
# instead of the system default. Leave empty to use /etc/resolv.conf.
RESOLVER=""
source "$CONFIG"
fail=0
check() {
local type="$1" name="$2" expected="$3" actual
# || true so an empty/NXDOMAIN answer doesn't trip 'set -e'
if [[ -n "$RESOLVER" ]]; then
actual=$(dig +short "@$RESOLVER" "$type" "$name" | sort || true)
else
actual=$(dig +short "$type" "$name" | sort || true)
fi
expected=$(printf '%s\n' "$expected" | grep -v '^$' | sort || true)
if [[ "$actual" == "$expected" ]]; then
logger -t dns-check "OK $type $name"
else
# Printed to stdout so cron emails it; also logged to syslog.
echo "FAIL $type $name"
echo " expected: $(echo "$expected" | tr '\n' ' ')"
echo " actual: $(echo "$actual" | tr '\n' ' ')"
logger -t dns-check "FAIL $type $name expected=[$expected] actual=[$actual]"
fail=1
fi
}
check MX "$DOMAIN" "$EXPECTED_MX"
check A "$MAIL_HOST" "$EXPECTED_A"
check AAAA "$MAIL_HOST" "$EXPECTED_AAAA"
exit "$fail"
Install and make it executable:
sudo tee /usr/local/bin/dns-check.sh >/dev/null < dns-check.sh # paste the script above
sudo chmod 0755 /usr/local/bin/dns-check.sh
The design choice worth noting: on success the script is silent on stdout (it only writes an OK line to syslog via logger), and on failure it prints the mismatch to stdout. That matters for the cron step below — cron emails you only when a job produces output, so a silent run means "all good" and an email means "something drifted".
On which resolver to query
By default the script uses whatever /etc/resolv.conf points at, which tells you what the world mostly sees (subject to caching and TTLs). If you'd rather compare against a specific view — your authoritative server, or a public resolver like 1.1.1.1 to bypass a local caching layer — set RESOLVER="1.1.1.1" at the top. Pick one and stick with it so the results are consistent day to day.
Run it by hand first
/usr/local/bin/dns-check.sh
echo "exit: $?"
Exit 0 and no output means everything matched. To see what a failure looks like, temporarily change one value in expected.conf (say, bump the MX priority to 99) and run again — you'll get the FAIL block and exit 1. Put the correct value back when you're done.
Schedule it daily with cron
Add a system cron entry. The MAILTO line sends the job's output to you; because the script is silent on success, you'll only hear from it on a mismatch or a script error.
sudo tee /etc/cron.d/dns-check >/dev/null <<'EOF'
MAILTO="admin@example.com"
# Replace admin@example.com above with where alerts should go.
# Run every day at 06:15, as an unprivileged user.
15 6 * * * nobody /usr/local/bin/dns-check.sh
EOF
Two notes on that entry: files in /etc/cron.d take a username field (nobody here) that per-user crontabs don't, and for MAILTO to actually send mail the host needs a working local MTA or sendmail-compatible binary — which, on a mail server, you have. A systemd timer with OnCalendar= plus an OnFailure= unit is the common alternative if you prefer that over cron; the logic is identical.
Verify it's wired up
Confirm cron parsed the file and will run it:
# Debian/Ubuntu logs cron activity to the system journal
journalctl -u cron --since today | grep dns-check
Check the script's own log lines after a run:
journalctl -t dns-check --since today
You should see OK MX, OK A, and OK AAAA lines once the scheduled run fires. To prove the alert path end to end, break one expected value, wait for (or manually trigger) a run, confirm the email arrives, then fix the value.
Undo
There's nothing to reverse in terms of data — the script never changed anything. To remove the automation entirely:
sudo rm /etc/cron.d/dns-check
sudo rm /usr/local/bin/dns-check.sh
sudo rm -r /etc/dns-check
That takes you back to where you started. For the exact meaning of dig output fields and the flags used here, see the dig manual page (man dig) and the ISC BIND documentation; for the cron.d file format, see man 5 crontab on your system.
Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.
More about the author →Was this article helpful?
Tap a star — no sign-in needed.
Be the first to rate this article.
Related guides
Automate DNS Record Checks and Alert on Drift with dig
This is a read-only monitor . The script queries DNS with dig , compares each answer against a baseline file you control, and prints (and by cron, emails) a line for every record that no longer matches. It changes no DNS records, no zone files, and no configuration — the worst it can do is send you an email or write to a file you point it at. Because of that there is nothing to roll back on the DNS side.
Monitor Dovecot Process Memory and Alert on Runaway Processes
This guide sets up a small bash script, run on a schedule, that reads the resident memory (RSS) of every process inside the dovecot.service cgroup and warns you when one crosses a threshold you set. It only reads and reports — it writes a line to syslog and optionally sends an email. It does not kill processes, change Dovecot config, or touch any mailbox.
Alert on a Growing Postfix Mail Queue with a Cron Script
A mail queue that quietly grows is one of those failures you notice late — usually when a user asks why their mail from three hours ago hasn't arrived. A backlog can mean a dead relay host, a DNS problem, a downstream server rejecting everything, or an outbound spam run from a compromised account. This guide sets up a small cron job that counts the queue and emails you when it crosses a threshold, so you hear about it early.
Automate a Scheduled Reachability and Speed Test to Key Hosts
This guide builds a small bash script that pings a fixed list of hosts on a schedule, records latency and packet loss to a CSV file, and optionally measures throughput to a host you control. It is read-only from a system-configuration standpoint : it changes no firewall rule, route, or setting. It only sends test traffic and appends lines to a log file.




