Back to blog
Go DNS Monitoring

Building a DNS Monitoring Tool in Go After Missing a DNS Misconfiguration

From a missed DNS change to a CLI tool and GitHub Action

8 min read

The Problem

I once missed a DNS misconfiguration on one of my domains. The records had changed, but I didn't notice until it caused real issues. It wasn't a dramatic outage, but the kind of quiet failure that could have been caught early with a simple check.

After that experience, I wanted a way to periodically verify that my DNS records matched what I expected. Not a full-blown monitoring service, just something lightweight I could run on my own.

Why Not Use an Existing Service?

There are monitoring tools like UptimeRobot that do a great job for HTTP uptime checks. But most of them focus on HTTP-level monitoring, not DNS record verification. I needed something that could check specific record types (A, MX, TXT, NS, CNAME) and alert when actual values didn't match expected ones.

For a personal domain, signing up for an external SaaS felt like overkill. The dependency itself was unnecessary. I just needed a small tool that I could run periodically, ideally as a GitHub Actions cron job.

Why Go?

My initial thought was to build this in TypeScript. But as I considered the distribution story, Go became the obvious choice:

  • 1. Single binary - No runtime, no node_modules, no installation steps. Just download and run.
  • 2. Cross-platform - Go cross-compiles to Linux, macOS, and Windows with ease. GoReleaser handles multi-platform releases automatically.
  • 3. Zero dependencies - The only external dependency is gopkg.in/yaml.v3 for config parsing. Everything else uses Go's standard library.

For a monitoring tool that needs to be reliable and easy to deploy, these properties matter more than developer familiarity. TypeScript would have worked fine functionally, but Go's distribution model is a better fit.

Design Decisions

DNS-over-HTTPS

Instead of using the OS resolver, dns-watchdog queries Google Public DNS via HTTPS (dns.google). This has a few advantages:

  • Works behind firewalls that block port 53
  • No dependency on OS DNS configuration
  • Consistent results regardless of where it runs (local machine, CI, Docker)

Two Match Modes

DNS records aren't all equal. An A record has a predictable format, but TXT records can contain varying content (SPF records, domain verification tokens, etc.). To handle this, dns-watchdog supports two matching modes:

expected (exact match)

The actual DNS response must exactly match the listed values. Good for A, MX, NS, CNAME records.

contains (substring match)

Any returned record containing the substring is a match. Useful for TXT records like SPF.

Slack Notifications

When a check fails, dns-watchdog sends a notification via Slack incoming webhook. The webhook URL is passed through an environment variable, keeping secrets out of config files. One webhook, one message, no complex integrations.

Running with GitHub Actions

The simplest way to run dns-watchdog on a schedule is as a GitHub Action. Add a workflow file with a cron trigger:

name: DNS Watchdog
on:
  schedule:
    - cron: "0 */6 * * *"   # every 6 hours
  workflow_dispatch:

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: daigotanaka0714/dns-watchdog@v1
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
          DNS_WATCHDOG_CONFIG: ./config.yml

The config file lives in your repository, and the Slack webhook URL is stored as a GitHub secret. Every 6 hours, the action checks your DNS records and sends a Slack notification if anything is off.

Publishing it as a GitHub Action also makes it easy for others to use. Just reference daigotanaka0714/dns-watchdog@v1 and provide a config file.

Configuration

The config file is YAML and straightforward:

domain: example.com
checks:
  - type: A
    name: "@"
    expected:
      - "93.184.216.34"
  - type: MX
    name: "@"
    expected:
      - "10 mail.example.com."
  - type: TXT
    name: "@"
    contains: "v=spf1"
  - type: NS
    name: "@"
    expected:
      - "ns1.example.com."
      - "ns2.example.com."
  - type: CNAME
    name: "www"
    expected:
      - "example.com."
notify:
  slack_webhook_env: "SLACK_WEBHOOK_URL"
  template: "default"

Each check specifies a record type, the name to query, and either expected (exact values) or contains (substring to search for). The tool exits with code 0 if all checks pass, or 1 if any fail.

Wrapping Up

dns-watchdog is a small tool born from a real need. It won't replace a full monitoring stack, but for individuals who manage their own domains and want peace of mind, it's a practical solution. Define your expected records in YAML, run it on a cron, and get notified when something changes.