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 retransferneeded 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.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.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.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.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.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.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.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.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.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.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 ```