Why DNS Drift Detection Matters

DNS is the backbone of every networked service. When DNS records change unexpectedly — whether through manual console edits, rogue API calls, or compromised credentials — the impact can range from minor service disruptions to full-scale outages. A single unauthorized MX record change can redirect your organization’s email to a malicious server. An A record modification can send user traffic to an attacker-controlled IP.

DNS drift occurs when the actual state of your DNS records diverges from the desired, documented, or infrastructure-as-code (IaC) definition. Without automated detection, these changes can go unnoticed for days or weeks, leaving security teams blind to potential compromises or configuration errors.

For related reading, see our PowerDNS vs BIND9 vs Knot authoritative DNS comparison and DNS-as-code guide with DNSControl, OctoDNS, and Lexicon.

What Is DNS Drift?

DNS drift happens in several common scenarios:

  • Manual console changes: An administrator makes a direct edit in the cloud provider’s DNS console, bypassing the IaC pipeline
  • Shadow IT: Another team deploys a service and adds DNS records without coordination
  • Provider-side issues: DNS providers occasionally experience bugs that corrupt or duplicate records
  • Credential compromise: An attacker with DNS API access adds or modifies records
  • Stale IaC state: Infrastructure code is updated but not applied, creating a gap between code and reality

The solution is continuous DNS drift detection — automated tools that compare your desired DNS state against the actual live records and alert you when they diverge.

Tool Comparison at a Glance

FeatureDNSControlOctoDNSTerraform DNS
LanguageGoPythonGo (HCL)
GitHub Stars3,8163,687138
Last UpdatedApril 2026April 2026April 2026
Drift Detectiondnscontrol preview--dry-runterraform plan
Multi-ProviderYes (40+ providers)Yes (30+ providers)Yes (via providers)
CI/CD IntegrationExcellentExcellentExcellent
Docker SupportOfficial imageOfficial imageDocker-compatible
DSL / ConfigJavaScript DSLYAML configHCL (HashiCorp)
Best ForTeams wanting JS-based configPython shops, multi-provider syncTerraform-centric environments

DNSControl: Drift Detection with JavaScript DSL

DNSControl is developed by Stack Exchange and uses a JavaScript-based domain-specific language (DSL) to define DNS records. Its preview command is the primary drift detection mechanism — it compares your declared DNS configuration against the live state of your DNS provider and shows exactly what would change.

How DNSControl Detects Drift

DNSControl’s drift detection works by:

  1. Reading your dnsconfig.js file (desired state)
  2. Querying your DNS provider’s API for current records (actual state)
  3. Computing the diff between the two
  4. Outputting a report of additions, deletions, and modifications
1
dnscontrol preview --provider cloudflare

Sample output:

1
2
3
4
5
6
******************** Domain: example.com
----- Getting nameservers from: cloudflare
----- DNS Provider: cloudflare...
+ CREATE A api.example.com 192.168.1.100 ttl=300
~ MODIFY TXT example.com: (v=spf1 include:_spf.google.com ~all) -> (v=spf1 include:_spf.google.com include:mail.example.com ~all)
- DELETE CNAME old.example.com

DNSControl Docker Setup

Run DNSControl drift detection in a container:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
version: "3.8"
services:
  dnscontrol:
    image: stackexchange/dnscontrol:latest
    volumes:
      - ./dnsconfig.js:/dns/dnsconfig.js:ro
      - ./creds.json:/dns/creds.json:ro
    command: ["preview", "--provider", "cloudflare"]
    environment:
      - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}

The creds.json file contains provider credentials:

1
2
3
4
5
6
{
  "cloudflare": {
    "TYPE": "CLOUDFLAREAPI",
    "apitoken": "$CLOUDFLARE_API_TOKEN"
  }
}

DNSControl Configuration Example

A typical dnsconfig.js file defines your desired DNS state:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var REG_NONE = NewRegistrar("none");
var DSP_CLOUDFLARE = NewDnsProvider("cloudflare");

D("example.com", REG_NONE, DnsProvider(DSP_CLOUDFLARE),
  A("@", "192.168.1.1"),
  A("www", "192.168.1.1"),
  A("api", "192.168.1.100"),
  MX("@", MX("mail.example.com.", 10)),
  TXT("@", "v=spf1 include:_spf.google.com ~all"),
  CNAME("blog", "example.github.io."),
);

OctoDNS: Multi-Provider DNS Sync with Drift Detection

OctoDNS by GitHub focuses on managing DNS across multiple providers simultaneously. Its --dry-run mode performs drift detection by comparing the desired YAML configuration against the live DNS state across all configured providers.

How OctoDNS Detects Drift

OctoDNS uses a two-phase approach:

  1. Sync planning: Reads your YAML configuration and queries all providers
  2. Dry-run execution: Computes the differences and outputs a detailed change plan without applying anything
1
octodns-sync --config-file=config.yaml --doit=false

Sample output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
********************************************************************************
* example.com.
********************************************************************************
* cloudflare
*     Update
*         <ARecord A 300, api.example.com., 10.0.0.1> =>
*         <ARecord A 300, api.example.com., 192.168.1.100>
*     Create
*         <TxtRecord TXT 300, _dmarc.example.com., "v=DMARC1; p=reject">
********************************************************************************

OctoDNS Docker Setup

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
version: "3.8"
services:
  octodns:
    image: octodns/octodns:latest
    volumes:
      - ./config:/config:ro
    command: ["--config-file=/config/config.yaml", "--doit=false"]
    environment:
      - OCTODNS_CLOUDFLARE_TOKEN=${CLOUDFLARE_API_TOKEN}
      - OCTODNS_ROUTE53_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
      - OCTODNS_ROUTE53_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}

OctoDNS Configuration Example

OctoDNS uses YAML for configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
providers:
  cloudflare:
    class: octodns_cloudflare.CloudflareProvider
    token: env/CLOUDFLARE_API_TOKEN
  route53:
    class: octodns_route53.Route53Provider
    access_key_id: env/AWS_ACCESS_KEY_ID
    secret_access_key: env/AWS_SECRET_ACCESS_KEY

zones:
  example.com.:
    sources:
      - config
    targets:
      - cloudflare
      - route53

sources:
  config:
    class: octodns_source.yaml.YamlSource
    directory: ./zones/

Zone file (zones/example.com.yaml):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
---
'':
  - type: A
    value: 192.168.1.1
www:
  - type: A
    value: 192.168.1.1
api:
  - type: A
    value: 192.168.1.100
'@':
  - type: MX
    values:
      - exchange: mail.example.com.
        preference: 10
  - type: TXT
    values:
      - v=spf1 include:_spf.google.com ~all

Terraform DNS Provider: IaC-Based Drift Detection

The Terraform DNS Provider by HashiCorp supports RFC 2136 dynamic DNS updates and integrates with Terraform’s built-in drift detection via terraform plan. This approach works best for organizations already using Terraform for infrastructure management.

How Terraform Detects DNS Drift

Terraform’s drift detection is built into its workflow:

  1. terraform plan reads your .tf files (desired state)
  2. It queries the DNS provider for current records (actual state)
  3. It outputs a plan showing what would change
  4. If the plan is empty, your DNS is in sync
1
terraform plan -target=dns_a_record_set.api

Sample output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Terraform will perform the following actions:

  # dns_a_record_set.api will be updated in-place
  ~ resource "dns_a_record_set" "api" {
      ~ addrs     = [
          - "10.0.0.1",
          + "192.168.1.100",
        ]
        id        = "api.example.com"
        # (5 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Terraform DNS Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
provider "dns" {
  update {
    server        = "ns1.example.com"
    key_name      = "terraform-key"
    key_algorithm = "hmac-sha256"
    key_secret    = var.dns_key_secret
  }
}

resource "dns_a_record_set" "api" {
  zone = "example.com."
  name = "api"
  addrs = ["192.168.1.100"]
  ttl   = 300
}

resource "dns_txt_record_set" "spf" {
  zone = "example.com."
  name = "@"
  txt = ["v=spf1 include:_spf.google.com ~all"]
  ttl = 3600
}

Docker Setup for Terraform DNS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
version: "3.8"
services:
  terraform:
    image: hashicorp/terraform:latest
    volumes:
      - ./terraform:/workspace
    working_dir: /workspace
    command: ["plan"]
    environment:
      - TF_VAR_dns_key_secret=${DNS_KEY_SECRET}

Initialize and plan:

1
2
docker compose run --rm terraform init
docker compose run --rm terraform plan

Setting Up Automated DNS Drift Monitoring

The real value of DNS drift detection comes from running it continuously. Here’s how to set up automated monitoring with any of the three tools.

Option 1: Cron-Based Drift Checking

Create a cron job that runs every 15 minutes and alerts on any detected changes:

1
2
# /etc/cron.d/dns-drift-check
*/15 * * * * root /usr/local/bin/check-dns-drift.sh

The check script (/usr/local/bin/check-dns-drift.sh):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash
set -euo pipefail

LOG_FILE="/var/log/dns-drift-check.log"
WEBHOOK_URL="https://hooks.example.com/dns-drift-alert"

echo "[$(date -u)] Running DNS drift check..." >> "$LOG_FILE"

# Run drift detection
DRIFT_OUTPUT=$(cd /opt/dns-monitoring && docker compose run --rm dnscontrol preview --provider cloudflare 2>&1)

# Check if there are any changes (lines starting with +, -, or ~)
if echo "$DRIFT_OUTPUT" | grep -qE "^[+\-~] "; then
  echo "[$(date -u)] DRIFT DETECTED!" >> "$LOG_FILE"
  echo "$DRIFT_OUTPUT" >> "$LOG_FILE"
  
  # Send alert to webhook (Slack, Discord, etc.)
  curl -s -X POST "$WEBHOOK_URL" \
    -H "Content-Type: application/json" \
    -d "{\"text\": \"DNS Drift Detected:\\n\\n\`\`\`\\n${DRIFT_OUTPUT}\\n\`\`\`\"}"
else
  echo "[$(date -u)] No drift detected" >> "$LOG_FILE"
fi

Option 2: CI/CD Pipeline Integration

Add DNS drift detection to your CI/CD pipeline for automated pre-deployment checks:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# GitHub Actions workflow
name: DNS Drift Detection
on:
  schedule:
    - cron: "0 */4 * * *"  # Every 4 hours
  workflow_dispatch:

jobs:
  check-drift:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: DNSControl Drift Check
        run: |
          docker run --rm \
            -v $(pwd)/dnsconfig.js:/dns/dnsconfig.js:ro \
            -v $(pwd)/creds.json:/dns/creds.json:ro \
            stackexchange/dnscontrol:latest preview --provider cloudflare
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

Option 3: Prometheus Metrics Export

For organizations using Prometheus, export DNS drift status as metrics:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3
"""DNS drift status exporter for Prometheus."""
import subprocess
import time
from http.server import HTTPServer, BaseHTTPRequestHandler

class DriftExporter(BaseHTTPRequestHandler):
    def do_GET(self):
        result = subprocess.run(
            ["dnscontrol", "preview", "--provider", "cloudflare"],
            capture_output=True, text=True, cwd="/opt/dns"
        )
        has_drift = 1 if "+" in result.stdout or "-" in result.stdout else 0
        
        self.send_response(200)
        self.send_header("Content-Type", "text/plain")
        self.end_headers()
        self.wfile.write(f"dns_drift_detected {has_drift}\n".encode())

if __name__ == "__main__":
    HTTPServer(("0.0.0.0", 9199), DriftExporter).serve_forever()

Choosing the Right Tool

CriteriaDNSControlOctoDNSTerraform DNS
Already using TerraformNoNoYes
Prefer JavaScript configYesNoNo
Prefer YAML configNoYesNo
40+ DNS providers neededYesNo (30+)Depends
RFC 2136 supportNoNoYes
Dry-run / preview modeYesYesYes
Team familiarityJS developersPython/DevOpsTerraform users

DNSControl is the best choice for teams comfortable with JavaScript who want the broadest provider support and a mature, actively maintained tool from Stack Exchange.

OctoDNS excels for organizations managing DNS across multiple providers (e.g., Cloudflare + Route 53) and preferring declarative YAML configuration.

Terraform DNS Provider is ideal when DNS management is part of a broader Terraform-managed infrastructure, enabling unified drift detection across all resources with terraform plan.

Best Practices for DNS Drift Detection

  1. Run checks frequently: Every 15-30 minutes for critical domains, hourly for others
  2. Alert on any change: Even small TXT record modifications can indicate compromise
  3. Maintain a baseline: Keep your DNS-as-code configuration in version control
  4. Test in staging first: Point preview/--dry-run at a staging domain before production
  5. Log all drift events: Build a history of changes for forensic analysis
  6. Integrate with incident response: Treat unexpected DNS changes as security events
  7. Use read-only API tokens: The drift detection tool only needs read access to your DNS provider

For additional DNS hardening techniques, check our DNS cache hardening guide and DNS health validation tools comparison.

FAQ

What is DNS drift and why should I care?

DNS drift is the divergence between your documented or infrastructure-as-code DNS configuration and the actual live DNS records. It matters because unauthorized DNS changes can redirect user traffic, intercept email, or enable phishing attacks. Without automated drift detection, these changes can go unnoticed indefinitely.

How often should I check for DNS drift?

For production domains, check every 15-30 minutes. For less critical domains, hourly checks are sufficient. The goal is to detect changes before they cause extended outages or security incidents.

Can DNSControl detect changes made directly in the provider console?

Yes. DNSControl’s preview command queries your DNS provider’s API for the current live state and compares it against your dnsconfig.js file. Any record added, modified, or deleted directly in the console will appear as a drift in the preview output.

Does OctoDNS support DNSSEC record drift detection?

OctoDNS supports DNSSEC-related record types (DS, DNSKEY) in its configuration files. When running --dry-run, it will detect drift in these records just like any other record type, provided your DNS provider supports DNSSEC through the OctoDNS plugin.

Can I use these tools without changing my existing DNS setup?

Absolutely. All three tools run in read-only “preview” or “dry-run” mode without modifying any records. You can start monitoring for drift immediately without changing how your DNS is currently managed.

What happens if the drift detection tool itself gets compromised?

Use read-only API tokens for drift detection. This limits the blast radius — even if the monitoring tool’s credentials are compromised, the attacker can only read your DNS records, not modify them. Separate your monitoring credentials from your deployment credentials.

How do I handle false positives in DNS drift alerts?

Some providers automatically add or modify certain records (e.g., verification TXT records). Configure your drift detection tool to ignore specific record types or names. Both DNSControl and OctoDNS support exclusion patterns in their configuration files.

DNS Drift Detection Comparison Summary

Implementing automated DNS drift detection is one of the highest-ROI security investments for any organization with an online presence. Whether you choose DNSControl’s JavaScript DSL, OctoDNS’s YAML-based multi-provider sync, or Terraform’s unified IaC approach, the key is running drift checks continuously and alerting on any unexpected changes.