XiaTools

How to Write a Python Script to Audit SOA Expiry Timers at Scale

Updated 11 Oct 2026

Checking Start of Authority (SOA) expiry timers across hundreds of domains manually is inefficient and prone to human error. A robust python soa audit script automates this process, querying authoritative nameservers directly to validate refresh, retry, expire, and minimum TTL values. Whether you manage a corporate portfolio or handle enterprise client migrations, programmatic DNS validation ensures your secondary zones never silently fail.

To quickly check an individual domain before writing code, you can use the interactive SOA Lookup tool to inspect raw record fields and verify baseline parameters instantly.

Understanding the SOA Record Structure

The Start of Authority record dictates how a zone is propagated and maintained across primary and secondary nameservers. Before writing your python soa audit script, you must understand the individual timers contained within the RDATA portion of the record.

Key Fields in an SOA Record

  • MNAME: The primary master nameserver for this zone.
  • RNAME: The email address of the administrator responsible for the zone, with the @ replaced by a dot.
  • SERIAL: The version number of the zone file, incremented whenever changes are made.
  • REFRESH: The time interval before a secondary nameserver should query the primary to check for zone updates.
  • RETRY: The time interval that a secondary nameserver should wait before retrying a failed refresh attempt.
  • EXPIRE: The upper limit time interval that can elapse before a secondary nameserver stops treating its zone data as authoritative if it cannot reach the primary.
  • MINIMUM: The default Time to Live (TTL) for resource records in the zone that do not specify their own TTL.

Setting Up Your Python Environment

To query DNS records programmatically in Python, you need a reliable library. While Python's built-in socket library can handle basic forward lookups, it cannot parse complex DNS resource records like SOA timers. Instead, you should use dnspython, the industry standard library for DNS operations.

Installing Dependencies

Open your terminal and install the required library using pip:

pip install dnspython

Verify your installation by running a quick interactive Python check or by executing a one-liner command in your shell:

python3 -c "import dns.resolver; print(dns.resolver.__version__)"

Writing the Python SOA Audit Script

Now, let's write a complete, production-ready script. This script reads a list of domains from a text file, queries the authoritative nameservers, extracts the SOA timers, and flags any domains with risky or non-standard expiry settings.

Complete Python Script Example

Create a file named audit_soa.py and add the following code:

import sys
import dns.resolver
import dns.exception

# Configuration thresholds (in seconds)
MIN_EXPIRE_SECONDS = 604800  # 7 days
MAX_EXPIRE_SECONDS = 2419200 # 28 days

def audit_domain(domain):
    resolver = dns.resolver.Resolver()
    resolver.timeout = 5
    resolver.lifetime = 5

    try:
        # Query the SOA record for the domain
        answers = resolver.resolve(domain, 'SOA')
        for rdata in answers:
            print(f"[+] Domain: {domain}")
            print(f"    Primary NS : {rdata.mname}")
            print(f"    Admin Email: {rdata.rname}")
            print(f"    Serial     : {rdata.serial}")
            print(f"    Refresh    : {rdata.refresh}s")
            print(f"    Retry      : {rdata.retry}s")
            print(f"    Expire     : {rdata.expire}s")
            print(f"    Minimum TTL: {rdata.minimum}s")

            # Validate expiry timer thresholds
            if rdata.expire < MIN_EXPIRE_SECONDS:
                print(f"    [WARNING] Expire timer is too low ({rdata.expire}s). Secondary zones may drop quickly during outages.")
            elif rdata.expire > MAX_EXPIRE_SECONDS:
                print(f"    [NOTICE] Expire timer is quite high ({rdata.expire}s).")
            print("-" * 50)
            
    except dns.resolver.NoAnswer:
        print(f"[-] Error: No SOA record found for {domain}")
    except dns.resolver.NXDOMAIN:
        print(f"[-] Error: Domain {domain} does not exist")
    except dns.exception.Timeout:
        print(f"[-] Error: Query timed out for {domain}")
    except Exception as e:
        print(f"[-] Unexpected error for {domain}: {str(e)}")

def main():
    # Example domain list input
    domains = ["example.com", "192.0.2.1.example.com"]
    
    print(f"Starting SOA audit for {len(domains)} domains...")
    print("=" * 50)
    
    for domain in domains:
        audit_domain(domain)

if __name__ == "__main__":
    main()

Running the Script

Execute the script from your terminal:

python3 audit_soa.py

Sample output:

Starting SOA audit for 1 domains...
==================================================
[+] Domain: example.com
    Primary NS : a.iana-servers.net.
    Admin Email: noc.dns.icann.org.
    Serial     : 2023101501
    Refresh    : 7200s
    Retry      : 600s
    Expire     : 1209600s
    Minimum TTL: 3600s
------------------------------------------------==

Advanced Scaling: Asynchronous Queries

If you need to audit thousands of domains, sequential execution will take too long. You can scale your python soa audit script by using Python's asyncio library along with dns.asyncresolver.

import asyncio
import dns.asyncresolver

async def audit_async(domain):
    resolver = dns.asyncresolver.Resolver()
    try:
        answers = await resolver.resolve(domain, 'SOA')
        for rdata in answers:
            print(f"{domain} -> Expire: {rdata.expire}s")
    except Exception:
        print(f"{domain} -> Query Failed")

async def main():
    domains = ["example.com", "test.example.com"]
    await asyncio.gather(*(audit_async(d) for d in domains))

if __name__ == '__main__':
    asyncio.run(main())

Comparison of DNS Query Approaches

Approach Speed Complexity Best Use Case
Standard socket Slow High (Manual Parsing) Minimalist scripts without dependencies
dnspython (Sync) Moderate Low Small to medium domain lists (< 500)
dnspython (Async) Fast Medium Large enterprise portfolios (> 500)

Common Mistakes and How to Fix Them

When writing automated DNS auditing tools, network engineers frequently encounter a few specific pitfalls.

  • Ignoring DNS Timeouts: Network congestion or blocked UDP ports can hang your script indefinitely. Always set explicit timeout and lifetime parameters on your resolver instance.
  • Forgetting the Trailing Dot: When comparing primary nameserver strings returned by the SOA record against your inventory, remember that dnspython returns fully qualified domain names ending with a dot (e.g., ns1.example.com.). Normalize strings before making comparisons.
  • Hardcoding Public Resolvers: While querying 8.8.8.8 works for standard lookups, SOA records retrieved from public forwarders might return cached data with incorrect timers. For true audits, query the authoritative nameservers directly.

Pre-Flight Checklist for Your Audit Script

Before deploying your script to a production automation pipeline, verify the following items:

  • Installed the correct version of dnspython in your virtual environment.
  • Implemented robust exception handling for NXDOMAIN, NoAnswer, and timeouts.
  • Configured logging to output results to a CSV or JSON file for downstream analysis.
  • Defined clear business thresholds for minimum and maximum expire timers.

Frequently asked questions

Why is the SOA expire timer critical for secondary nameservers?

The expire timer determines how long a secondary nameserver will continue to serve cached zone data if it loses connectivity with the primary master. If this timer is set too low, a brief network outage can cause secondary servers to drop the zone entirely, resulting in complete service downtime.

Can I use standard library modules instead of dnspython?

Python's built-in socket library cannot natively query or parse DNS resource record structures like SOA timers. While you can invoke command-line tools like `dig` using the `subprocess` module, using `dnspython` is far cleaner, faster, and provides structured objects.

What is the recommended value for an SOA expire timer?

Industry standards generally recommend setting the expire timer between 1 to 4 weeks (604800 to 2419200 seconds). This provides enough buffer to survive extended primary nameserver outages without leaving stale records active indefinitely.

How do I handle domains that use different primary nameservers?

The `dnspython` library automatically queries the authoritative nameservers designated for the target domain by following delegation paths. Your script does not need to manually discover nameservers unless you want to cross-reference results between multiple providers.

How can I export my script results to a CSV file?

You can import Python's built-in `csv` module, open a file using `open('output.csv', 'w', newline='')`, and write the parsed fields inside your iteration loop using a `csv.writer` object.

Related articles

Free tools