Introduction

The SOA (Start of Authority) serial number is the heartbeat of DNS zone management. It tells slave servers when a zone has changed and triggers zone transfers. When serial numbers are mishandled - whether decreased, not incremented, or set to invalid values - slave servers won't update, and your DNS changes won't propagate. Understanding serial number rules is essential for reliable DNS operations.

Symptoms

  • Zone changes not reflected on slave servers
  • Serial number lower on slave than master
  • rndc retransfer needed to sync zones
  • Different answers from master vs slave servers
  • DNS changes not propagating globally
  • Slave logs show "zone is up to date" when it isn't
  • Zone transfers not happening after record changes

Common Causes

  • SOA serial not incremented after zone change
  • Serial number decreased (invalid operation)
  • Serial number wrapped around (exceeded max value)
  • Serial format inconsistency (date-based vs sequential)
  • Manual edits forgetting to update serial
  • Automation scripts not updating serial
  • Multiple admins editing zone with serial conflicts

Step-by-Step Fix

  1. 1.Check current SOA serial on all servers.

```bash # Get serial from master master_serial=$(dig @master-ip example.com SOA +short | awk '{print $1}') echo "Master serial: $master_serial"

# Get serial from all slaves for slave in slave1-ip slave2-ip; do serial=$(dig @$slave example.com SOA +short | awk '{print $1}') echo "$slave serial: $serial" done

# Quick comparison echo -e "\n=== All Servers ===" for server in master-ip slave1-ip slave2-ip; do echo -n "$server: " dig @$server example.com SOA +short | awk '{print $1}' done ```

  1. 1.Understand SOA serial rules.

```bash # SOA serial rules: # 1. Serial must increase for zone changes # 2. Serial must be greater than previous # 3. Decreasing serial breaks slave updates # 4. Serial wraps at 2^32 (4294967295)

# Common serial formats: # Sequential: 1, 2, 3, 4... # Date-based: YYYYMMDDNN (e.g., 2026040401 = April 4, 2026, change 01)

# Example SOA record: # example.com. IN SOA ns1.example.com. admin.example.com. ( # 2026040401 ; Serial <- This must increment! # 3600 ; Refresh # 600 ; Retry # 86400 ; Expire # 3600 ) ; Minimum

# Check current SOA details dig example.com SOA

# Full output shows: # example.com. 3600 IN SOA ns1.example.com. admin.example.com. ( # 2026040401 ; serial # 3600 ; refresh (1 hour) # 600 ; retry (10 minutes) # 86400 ; expire (1 day) # 3600 ; minimum (1 hour) # ) ```

  1. 1.Fix a decreased serial number.

```bash # Problem: Serial was decreased, slaves won't update

# Check serial history if available # If serial went 2026040401 -> 2026040301 (decreased)

# Solution 1: Reset to higher value # New serial must be greater than both old and new # If old was 2026040401 and new is 2026040301 # Set serial to 2026040402 (increment from highest)

# Solution 2: Force retransfer on all slaves (BIND) # On each slave: rndc retransfer example.com

# Solution 3: Increment past the wrap # Add 2^31 (2147483648) to current serial # This ensures slaves see it as newer # Example: 100 + 2147483648 = 2147483748

# After fixing, verify slaves updated for server in slave1-ip slave2-ip; do echo "$server:" dig @$server example.com SOA +short | awk '{print $1}' done ```

  1. 1.Fix forgotten serial increment.

```bash # Problem: Changed records but didn't update serial

# Check if zone needs serial update # Compare record timestamps vs serial

# Fix: Increment serial and reload # In zone file, update serial: # Old: 2026040401 # New: 2026040402

# For date-based serials: # Format: YYYYMMDDNN # Today: 20260404 # Daily changes: 01, 02, 03... # So first change today: 2026040401 # Second change today: 2026040402

# After editing zone file: # Check zone syntax named-checkzone example.com /etc/bind/zones/example.com.zone

# Reload zone rndc reload example.com

# Verify serial updated dig @localhost example.com SOA +short | awk '{print $1}' ```

  1. 1.Handle serial number wrap-around.

```bash # Serial wraps at 4294967295 (2^32 - 1)

# If serial is near max: current_serial=$(dig @localhost example.com SOA +short | awk '{print $1}') echo "Current: $current_serial"

if [ $current_serial -gt 4200000000 ]; then echo "WARNING: Serial near wrap point" fi

# When approaching wrap: # Reset to 1 after coordinating with all slaves

# Process: # 1. Stop all slaves # 2. Set master serial to 1 # 3. Delete slave zone files # 4. Start slaves (they'll do full transfer) # 5. Or use rndc retransfer on each slave

# Safer approach: Add large number to wrap past max # Serial arithmetic handles wrap correctly # 4294967295 + 1 = 0 (wrap) # Slaves see 0 as newer than max ```

  1. 1.Automate serial number management.

```bash # BIND dynamic updates handle serial automatically nsupdate <<EOF server localhost zone example.com update add newrecord.example.com 3600 A 192.0.2.1 send EOF

# For static zone files, use scripts:

# Increment serial script: #!/bin/bash ZONE_FILE="/etc/bind/zones/example.com.zone"

# Get current serial current=$(grep -E "^\s+[0-9]+\s*;" $ZONE_FILE | head -1 | awk '{print $1}')

# Increment new=$((current + 1))

# Update file sed -i "s/$current/$new/" $ZONE_FILE

echo "Serial: $current -> $new"

# Date-based serial script: #!/bin/bash ZONE_FILE="/etc/bind/zones/example.com.zone" TODAY=$(date +%Y%m%d)

# Get current serial current=$(grep -E "^\s+[0-9]+\s*;" $ZONE_FILE | head -1 | awk '{print $1}') current_date=${current:0:8} current_seq=${current:8:2}

if [ "$current_date" = "$TODAY" ]; then # Same day, increment sequence new_seq=$((10#$current_seq + 1)) new_serial="${TODAY}$(printf '%02d' $new_seq)" else # New day, start at 01 new_serial="${TODAY}01" fi

sed -i "s/$current/$new_serial/" $ZONE_FILE echo "Serial: $current -> $new_serial" ```

  1. 1.Verify zone transfer after serial update.

```bash # After incrementing serial, verify slaves update

# Check refresh timer in SOA # This is how often slaves check for updates dig example.com SOA +short # Second field is refresh (seconds)

# Force immediate transfer # On master: rndc notify example.com

# On slaves: rndc refresh example.com

# Monitor transfer # On slave, watch for: journalctl -u named -f | grep example.com

# Should see: # "received notify for zone 'example.com'" # "transfer of 'example.com' from master completed"

# Verify serial propagated for server in master-ip slave1-ip slave2-ip; do echo -n "$server: " dig @$server example.com SOA +short | awk '{print $1}' done ```

  1. 1.Debug serial number issues.

```bash # Enable detailed logging for transfers # In named.conf: logging { channel transfer_debug { file "/var/log/named/transfer.log" versions 3 size 5m; severity debug 3; print-time yes; print-category yes; }; category xfer-out { transfer_debug; }; category xfer-in { transfer_debug; }; category notify { transfer_debug; }; };

# Reload rndc reload

# Watch logs tail -f /var/log/named/transfer.log

# Check for: # - "serial = X" in transfer messages # - "zone is up to date" (slave thinks serial is current) # - "refused" (ACL issue) # - "not authoritative" (zone not loaded on master) ```

  1. 1.Fix inconsistent serials across servers.

```bash # When servers have different serials

# List all serials echo "=== Current Serials ===" for server in master slave1 slave2; do ip=$(getent hosts $server | awk '{print $1}') echo -n "$server ($ip): " dig @$ip example.com SOA +short | awk '{print $1}' done

# If master has highest: # 1. Force notify rndc notify example.com

# 2. Force refresh on slaves for slave in slave1 slave2; do ssh $slave "rndc refresh example.com" done

# If slave has higher serial than master: # This is a problem - slave thinks it's newer # Reset slave by deleting zone and retransferring ssh slave1 "systemctl stop named" ssh slave1 "rm /var/named/slaves/example.com.zone" ssh slave1 "systemctl start named" # Slave will request fresh transfer

# Or use retransfer ssh slave1 "rndc retransfer example.com" ```

  1. 1.Implement serial number monitoring.

```bash #!/bin/bash # Monitor SOA serial consistency

ZONE="example.com" MASTER="master-ip" SLAVES="slave1-ip slave2-ip"

master_serial=$(dig @$MASTER $ZONE SOA +short | awk '{print $1}')

echo "Master serial: $master_serial"

for slave in $SLAVES; do slave_serial=$(dig @$slave $ZONE SOA +short | awk '{print $1}') if [ "$slave_serial" != "$master_serial" ]; then echo "WARNING: $slave serial ($slave_serial) != master ($master_serial)" # Send alert else echo "OK: $slave is in sync" fi done

# Check serial hasn't decreased previous_serial_file="/tmp/${ZONE}.serial" if [ -f "$previous_serial_file" ]; then previous=$(cat $previous_serial_file) if [ "$master_serial" -lt "$previous" ]; then echo "ERROR: Serial decreased from $previous to $master_serial" fi fi echo $master_serial > $previous_serial_file ```

Verification

Complete serial number verification:

```bash # 1. Check all servers have same serial echo "=== Serial Consistency ===" master=$(dig @master-ip example.com SOA +short | awk '{print $1}') echo "Master: $master" for slave in slave1-ip slave2-ip; do s=$(dig @$slave example.com SOA +short | awk '{print $1}') echo "$slave: $s" [ "$s" != "$master" ] && echo " WARNING: Out of sync!" done

# 2. Verify zone transfer works echo -e "\n=== Zone Transfer Test ===" dig @master-ip example.com AXFR 2>&1 | tail -3

# 3. Check SOA timers echo -e "\n=== SOA Timers ===" dig example.com SOA +short | { read serial refresh retry expire minimum echo "Serial: $serial" echo "Refresh: $refresh seconds ($(($refresh/60)) minutes)" echo "Retry: $retry seconds" echo "Expire: $expire seconds ($(($expire/86400)) days)" echo "Minimum: $minimum seconds" }

# 4. Verify specific record on all servers echo -e "\n=== Record Consistency ===" for server in master-ip slave1-ip slave2-ip; do echo -n "$server: " dig @$server www.example.com A +short done ```

Serial Best Practices

```bash # 1. Use date-based format: YYYYMMDDNN # 2026040401 = April 4, 2026, first change # Easy to read, hard to forget

# 2. Automate serial increment # Use scripts or dynamic DNS updates

# 3. Monitor serial consistency # Alert when slaves don't match master

# 4. Keep serial in zone file comments # ; Serial: 2026040401 - Added www record

# 5. Use version control # Track zone file changes with git

# 6. Coordinate serial changes # Multiple admins = serial conflicts ```