Small, tested Python and Node.js scripts that detect and repair real problems across DNS and domains. Missing or wrong records, broken email authentication (SPF, DKIM, DMARC), nameserver and delegation mismatches, TTL and propagation surprises, DNSSEC gaps, certificates and domains about to expire, CAA blocking issuance, and dangling records that invite subdomain takeover.
Every fix is safe by default. The scripts start in a dry run mode that reports what they would do, so you can read the plan before anything writes.
By Allan Niñal — AI Solutions Engineer. I build AI powered tools, data products, and AWS automation. Full write ups with diagrams for each fix live at allanninal.dev/dns.
These scripts check DNS and domain health with standard tools, not a store API. Python uses dnspython for lookups, requests for RDAP/WHOIS and the provider API, and the ssl module for certificates; Node uses the built-in dns and tls modules and fetch. Repairs go through a DNS provider API (Cloudflare, AWS Route 53). The decision logic in every fix is a pure function with no network, so it is unit tested.
Set the environment variables a fix needs. You only need provider credentials for the repair fixes; the checks need nothing but a domain name.
export DNS_DOMAIN="yourdomain.com"
export CLOUDFLARE_API_TOKEN="your token" # only for repair fixes
export DRY_RUN="true" # start safePython needs pip install requests pytest. Node needs Node 18 or newer (the scripts use the built-in fetch, no packages).
| Fix | What it does | Type | Guide |
|---|---|---|---|
| registrar-zone-nameserver-mismatch | Registrar NS glue differs from the authoritative zone NS set. Script compares WHOIS/RDAP NS against live NS query. | Diagnostic | Read |
| missing-subdomain-delegation-glue | Parent zone lacks NS records delegating a subdomain. Script checks parent zone delegation against child zone SOA/NS. | Diagnostic | Read |
| partial-nameserver-cutover-split-answers | Old and new nameservers both listed at registrar during migration. Script queries each NS directly and diffs responses. | Reconciler | Read |
| hosted-zone-recreated-ns-mismatch | Deleting and recreating a zone assigns new NS that no longer match registrar delegation. Script cross-checks zone NS against registrar NS. | Reconciler | Read |
| missing-address-record-nxdomain | Hosted zone lacks an address record for the queried name. Script resolves the name and reports the missing record. | Diagnostic | Read |
| cname-at-zone-apex | A literal CNAME at the root breaks required apex records. Script detects apex CNAME and confirms flattening resolved correctly. | Repair | Read |
| cname-coexistence-violation | A CNAME shares a hostname with MX, TXT, or A records, which is invalid. Script scans the zone for coexistence violations. | Diagnostic | Read |
| duplicate-conflicting-records | Two A records or a CNAME plus another type exist at one name causing undefined resolution. Script fetches the record set and flags duplicates. | Diagnostic | Read |
| cname-to-a-type-change-conflict | Converting a CNAME to an A record fails unless the old record is deleted first. Script orders deletes before creates when reconciling. | Repair | Read |
| www-apex-mismatch | Only www or only apex is set up, so the other variant fails to resolve or serves different content. Script resolves both and compares. | Reconciler | Read |
| wildcard-catch-all-exposure | A broad wildcard resolves every undefined subdomain, masking typos or takeovers. Script enumerates wildcard scope and flags apex-level wildcards. | Diagnostic | Read |
| custom-domain-dns-verification-failure | Apex A records or CNAME target do not match the host's required values (e.g. GitHub Pages). Script checks records against the provider's published values. | Reconciler | Read |
| dangling-cname-subdomain-takeover | CNAME target on a cloud provider is deprovisioned or unclaimed. Script resolves the CNAME chain and flags claimable targets. | Diagnostic | Read |
| dangling-cname-chain-intermediate-hop | A multi-level CNAME chain has a claimable target partway through, not just at the end. Script follows the full chain, not only the first hop. | Diagnostic | Read |
| orphaned-records-after-teardown | A or CNAME records remain pointing at decommissioned infrastructure with no owner. Script diffs zone records against a live infrastructure inventory. | Reconciler | Read |
| wildcard-pointing-at-deprovisioned-service | A wildcard CNAME targets a third-party service that is gone, exposing every unclaimed subdomain to takeover. Script flags wildcards pointing at takeover-prone services. | Diagnostic | Read |
| rdap-hijack-signal-monitoring | Registrant, nameserver, or status fields change unexpectedly ahead of an unauthorized transfer. Script polls RDAP and diffs fields over time. | Diagnostic | Read |
| caa-blocks-intended-ca | CAA excludes the CA attempting issuance, causing an authorization error. Script queries CAA and cross-checks against the CA in use. | Diagnostic | Read |
| caa-blocks-wildcard-issuance | CAA fails wildcard cert issuance even though the base domain issues fine. Script evaluates CAA at both apex and wildcard label. | Diagnostic | Read |
| caa-lookup-servfail-dnssec | A CA's CAA check hits SERVFAIL from a broken DNSSEC chain, blocking renewal. Script validates DS, DNSKEY, and RRSIG before issuance. | Diagnostic | Read |
| tls-san-hostname-mismatch | Served certificate's SAN list does not cover the requested hostname, causing handshake failures. Script opens a TLS connection and checks SAN against hostname. | Diagnostic | Read |
| tls-certificate-expiring | Renewal automation fails silently and the certificate lapses. Script opens a TLS connection, reads notAfter, and alerts before expiry. | Diagnostic | Read |
| ds-record-mismatch-ksk-rollover | Registrar's published DS no longer matches the zone's current KSK, breaking validation. Script compares DS at parent against DNSKEY at the zone. | Diagnostic | Read |
| stale-ds-records-orphaned | Old DS records were never removed, so validators cannot build a trust chain. Script lists all DS records and flags ones with no matching key. | Diagnostic | Read |
| dnssec-pending-transfer-in | The DS record never gets added automatically after a transfer, leaving DNSSEC perpetually pending. Script polls DS presence against expected state. | Diagnostic | Read |
| expired-rrsig-signatures | A signing job stalls and RRSIG validity windows lapse, causing bogus or SERVFAIL answers. Script checks RRSIG expiration timestamps against current time. | Diagnostic | Read |
| spf-exceeds-lookup-limit | Nested include mechanisms push SPF evaluation past 10 lookups, causing PermError. Script recursively counts lookups and flags overage. | Diagnostic | Read |
| duplicate-spf-records | Two separate v=spf1 TXT records exist, which must be treated as PermError. Script queries TXT records and counts SPF-prefixed entries. | Repair | Read |
| spf-duplicate-all-mechanism | Two all directives in one SPF record break evaluation order. Script parses the SPF record and flags duplicate or misordered mechanisms. | Diagnostic | Read |
| dkim-selector-missing | The selector._domainkey record is absent or stale, so signatures cannot verify. Script queries expected selector names and flags absence. | Diagnostic | Read |
| dkim-cname-selector-not-published | Some providers expect CNAME records for selectors and rotation stalls if missing. Script checks that both selectors resolve as CNAMEs to the provider's target. | Reconciler | Read |
| dkim-txt-record-malformed | A copy-pasted or unsplit long key invalidates the public key value. Script fetches the TXT record and validates the key parses correctly. | Diagnostic | Read |
| dkim-key-stale-after-rotation | DNS still publishes the old public key after a private key rotation, so signatures fail. Script diffs the deployed key against the published TXT. | Reconciler | Read |
| dmarc-record-missing-or-malformed | The _dmarc TXT record is absent or missing required tags. Script fetches the record and validates syntax against DMARC tag grammar. | Diagnostic | Read |
| dmarc-stuck-at-p-none | A domain never progresses past monitor-only policy, leaving it unprotected from spoofing. Script parses the DMARC record and flags p=none. | Diagnostic | Read |
| dmarc-rua-reports-not-arriving | The rua destination lacks the required third-party authorization record on a different domain. Script checks both the DMARC record and the external auth TXT record. | Diagnostic | Read |
| leftover-mx-records-after-migration | Old provider's MX entries remain after migration, splitting or losing mail. Script diffs the live MX set against the intended provider's documented hosts. | Reconciler | Read |
| mx-points-to-dead-host | An MX target no longer accepts SMTP, silently dropping mail. Script resolves each MX host and probes port 25 connectivity. | Repair | Read |
| mx-target-missing-address-record | The MX target hostname has no address record, so mail bounces. Script resolves each MX host and flags dangling targets. | Diagnostic | Read |
| mx-record-malformed-target | An MX target includes a stray @ or is a bare IP instead of a hostname, violating RFC. Script validates MX target syntax and resolvability. | Repair | Read |
| ttl-too-high | A long TTL keeps stale answers cached well after a record change. Script reads live TTL values and flags ones above a safe threshold before cutovers. | Diagnostic | Read |
| ttl-too-low | A very short TTL multiplies query load on authoritative servers. Script estimates query volume from TTL and traffic to flag risky low values. | Diagnostic | Read |
| proxied-record-forces-ttl | Enabling proxy mode forces TTL to automatic regardless of the value sent via API. Script detects TTL and proxy-status mismatches against intended config. | Reconciler | Read |
| resolver-answer-inconsistency | A record's value differs between resolvers from stale caches or partial rollout. Script queries multiple public resolvers in parallel and diffs answers. | Diagnostic | Read |
| stale-negative-cache-nxdomain | Resolvers cache a prior NXDOMAIN per the SOA negative TTL even after the record is added. Script queries multiple resolvers to detect stale negative caching. | Diagnostic | Read |
| domain-expiration-approaching | RDAP or WHOIS expiration date falls inside a warning threshold with no renewal triggered. Script checks expiry on a schedule and alerts. | Diagnostic | Read |
| auto-renewal-payment-failure | Auto-renew is on but billing failed silently, leaving the domain stuck in a grace period. Script polls RDAP status and flags domains stuck past renewal. | Diagnostic | Read |
| transfer-lock-blocking-renewal | RDAP shows an approaching expiry plus a clientTransferProhibited lock blocking renewal or transfer. Script parses RDAP status codes and expiration together. | Diagnostic | Read |
| unowned-record-overwritten | An automated DNS tool overwrites or silently reassigns a record it does not own, causing unpredictable drift. Script diffs intended versus live records before writing and checks ownership markers. | Reconciler | Read |
| duplicate-record-write-conflict | Creating a record where one already exists at that name and type errors out. Script lists existing records before writing and upserts instead of blindly creating. | Reconciler | Read |
| stale-acme-challenge-record | A leftover or already-deleted _acme-challenge TXT record leaves certificate renewal stuck in a retry loop. Script detects and clears stale challenge records past a timeout. | Reconciler | Read |
| api-token-missing-scope | An API token lacks zone-read or zone-list permission, breaking automated record writes for certificate validation. Script pre-flight-checks token scopes before attempting writes. | Diagnostic | Read |
| zone-lookup-failure-for-subdomain | Automation cannot auto-detect which hosted zone owns a subdomain, aborting a DNS-01 challenge. Script walks up the label tree to find the correct zone before writing. | Diagnostic | Read |
| false-positive-duplicate-detection | Dedup logic misflags legitimate multivalue or weighted record sets as duplicates, blocking valid changes. Script replicates correct dedup logic using name, type, and set identifier. | Reconciler | Read |
More fixes land as the guides are published. Watch or star the repo to follow along.
The decision logic in every fix is a pure function with no network calls, so the tests run anywhere.
# Python
pip install pytest
pytest
# Node
node --testThese scripts can change live DNS records through the Cloudflare API. Always run with DRY_RUN=true first, read the output, and confirm it is correct before you let a script write. Test against a non-production zone when you can.
Fighting a DNS, email deliverability, or domain problem you would rather hand off? That is what I do.
- GitHub: github.com/allanninal
- LinkedIn: in/allanninal
- Support the work: ko-fi.com/allanninal
MIT. Use it, change it, ship it.