BIND Operations Quick Reference

Add DNS Records

This section covers adding A records to BIND zone file. Use for new infrastructure deployments.

Workflow Summary

Step Action

1

Backup zone file

2

Add record(s)

3

Increment serial (YYYYMMDDNN)

4

Validate zone

5

Reload zone

6

Verify resolution

Step 1: Backup + View Current Zone

ssh bind-01 "sudo cp /var/named/inside.domusdigitalis.dev.zone /var/named/inside.domusdigitalis.dev.zone.bak.$(date +%Y%m%d%H%M)"
ssh bind-01 "sudo tail -50 /var/named/inside.domusdigitalis.dev.zone"

Step 2: Add A Record

Template: hostname IN A ip-address

# Template: hostname IN A ip-address
ssh bind-01 "echo 'vyos-02         IN A    10.50.1.3' | sudo tee -a /var/named/inside.domusdigitalis.dev.zone"

Step 3: Increment Serial

CRITICAL: Serial must increment or changes won’t propagate.

Check current:

ssh bind-01 "grep -E '^[[:space:]]+[0-9]{10}' /var/named/inside.domusdigitalis.dev.zone"

Edit manually (recommended):

ssh bind-01 "sudo vi /var/named/inside.domusdigitalis.dev.zone"

Or calculate + sed:

CURRENT=$(ssh bind-01 "grep -oP '[0-9]{10}' /var/named/inside.domusdigitalis.dev.zone | head -1")
NEW=$((CURRENT + 1))
echo "Current: $CURRENT → New: $NEW"
ssh bind-01 "sudo sed -i 's/$CURRENT/$NEW/' /var/named/inside.domusdigitalis.dev.zone"

Step 4: Validate

ssh bind-01 "sudo named-checkzone inside.domusdigitalis.dev /var/named/inside.domusdigitalis.dev.zone"
Expected
zone inside.domusdigitalis.dev/IN: loaded serial 2026030202
OK

Step 5: Reload

ssh bind-01 "sudo rndc reload inside.domusdigitalis.dev"

Step 6: Verify

dig @10.50.1.90 vyos-02.inside.domusdigitalis.dev +short

Multi-host verification:

for h in vyos-01 vyos-02 kvm-01 kvm-02 vault-01 ise-01; do
  echo -n "$h: "
  dig @10.50.1.90 $h.inside.domusdigitalis.dev +short
done

Bulk Add: VyOS HA

ssh bind-01 << 'EOF'
echo 'vyos-01         IN A    10.50.1.2' | sudo tee -a /var/named/inside.domusdigitalis.dev.zone
echo 'vyos-02         IN A    10.50.1.3' | sudo tee -a /var/named/inside.domusdigitalis.dev.zone
echo 'vyos            IN A    10.50.1.1' | sudo tee -a /var/named/inside.domusdigitalis.dev.zone
EOF

Then: increment serial → validate → reload → verify.

Bulk Add: All Infrastructure

ssh bind-01 << 'EOF'
sudo tee -a /var/named/inside.domusdigitalis.dev.zone << 'RECORDS'
; === VyOS Firewall HA ===
vyos-01         IN A    10.50.1.2
vyos-02         IN A    10.50.1.3
vyos            IN A    10.50.1.1

; === Hypervisors ===
kvm-01          IN A    10.50.1.110
kvm-02          IN A    10.50.1.111
ipmi-01         IN A    10.50.1.200
ipmi-02         IN A    10.50.1.201

; === Identity ===
home-dc01       IN A    10.50.1.50
home-dc02       IN A    10.50.1.51
keycloak-01     IN A    10.50.1.80
keycloak-02     IN A    10.50.1.81
ipa-01          IN A    10.50.1.100
ipa-02          IN A    10.50.1.101

; === Security ===
vault-01        IN A    10.50.1.60
vault-02        IN A    10.50.1.61
vault-03        IN A    10.50.1.62
ise-01          IN A    10.50.1.20
ise-02          IN A    10.50.1.21
ipsk-mgr-01     IN A    10.50.1.30
ipsk-mgr-02     IN A    10.50.1.31
ipsk-mgr        IN A    10.50.1.32

; === DNS ===
bind-01         IN A    10.50.1.90
bind-02         IN A    10.50.1.91

; === Network ===
9800-wlc-01     IN A    10.50.1.40
9800-wlc-02     IN A    10.50.1.41
3560-cx         IN A    10.50.1.10
c9300-01        IN A    10.50.1.11

; === Storage ===
nas-01          IN A    10.50.1.70
nas-02          IN A    10.50.1.71
gitea-01        IN A    10.50.1.72
minio-01        IN A    10.50.1.73

; === k3s Cluster ===
k3s-master-01   IN A    10.50.1.120
k3s-master-02   IN A    10.50.1.121
k3s-master-03   IN A    10.50.1.122
k3s-worker-01   IN A    10.50.1.123
k3s-worker-02   IN A    10.50.1.124
k3s-worker-03   IN A    10.50.1.125

; === Monitoring VIPs (MetalLB/Traefik) ===
traefik         IN A    10.50.1.130
wazuh-indexer   IN A    10.50.1.131
wazuh-dashboard IN A    10.50.1.132
wazuh-workers   IN A    10.50.1.133
wazuh-manager   IN A    10.50.1.134
zabbix-01       IN A    10.50.1.135

; === CNAMEs for convenience ===
dc              IN CNAME home-dc01
vault           IN CNAME vault-01
ise             IN CNAME ise-01
wlc             IN CNAME 9800-wlc-01
nas             IN CNAME nas-01
wazuh           IN CNAME wazuh-manager
prometheus      IN CNAME traefik
grafana         IN CNAME traefik
alertmanager    IN CNAME traefik
RECORDS
EOF

After bulk add:

ssh bind-01 "sudo vi /var/named/inside.domusdigitalis.dev.zone"
ssh bind-01 "sudo named-checkzone inside.domusdigitalis.dev /var/named/inside.domusdigitalis.dev.zone"
ssh bind-01 "sudo rndc reload inside.domusdigitalis.dev"
for h in vyos-01 vyos-02 kvm-01 kvm-02 vault-01 ise-01; do
  echo -n "$h: "
  dig @10.50.1.90 $h.inside.domusdigitalis.dev +short
done

Reverse DNS (PTR)

Zone file: /var/named/10.50.1.rev

Single PTR

ssh bind-01 "echo '2    IN PTR  vyos-02.inside.domusdigitalis.dev.' | sudo tee -a /var/named/10.50.1.rev"
ssh bind-01 "sudo named-checkzone 1.50.10.in-addr.arpa /var/named/10.50.1.rev"
ssh bind-01 "sudo rndc reload 1.50.10.in-addr.arpa"
dig @10.50.1.90 -x 10.50.1.2 +short

Bulk PTR Records

ssh bind-01 << 'EOF'
sudo tee -a /var/named/10.50.1.rev << 'PTR'
; === VyOS ===
1    IN PTR  vyos.inside.domusdigitalis.dev.
2    IN PTR  vyos-01.inside.domusdigitalis.dev.
3    IN PTR  vyos-02.inside.domusdigitalis.dev.

; === Switches ===
10   IN PTR  3560-cx.inside.domusdigitalis.dev.
11   IN PTR  c9300-01.inside.domusdigitalis.dev.

; === ISE ===
20   IN PTR  ise-01.inside.domusdigitalis.dev.
21   IN PTR  ise-02.inside.domusdigitalis.dev.

; === iPSK HA ===
30   IN PTR  ipsk-mgr-01.inside.domusdigitalis.dev.
31   IN PTR  ipsk-mgr-02.inside.domusdigitalis.dev.
32   IN PTR  ipsk-mgr.inside.domusdigitalis.dev.

; === WLC HA ===
40   IN PTR  9800-wlc-01.inside.domusdigitalis.dev.
41   IN PTR  9800-wlc-02.inside.domusdigitalis.dev.

; === Identity ===
50   IN PTR  home-dc01.inside.domusdigitalis.dev.
51   IN PTR  home-dc02.inside.domusdigitalis.dev.

; === Vault ===
60   IN PTR  vault-01.inside.domusdigitalis.dev.
61   IN PTR  vault-02.inside.domusdigitalis.dev.
62   IN PTR  vault-03.inside.domusdigitalis.dev.

; === Storage HA ===
70   IN PTR  nas-01.inside.domusdigitalis.dev.
71   IN PTR  nas-02.inside.domusdigitalis.dev.
72   IN PTR  gitea-01.inside.domusdigitalis.dev.
73   IN PTR  minio-01.inside.domusdigitalis.dev.

; === Keycloak HA ===
80   IN PTR  keycloak-01.inside.domusdigitalis.dev.
81   IN PTR  keycloak-02.inside.domusdigitalis.dev.

; === DNS ===
90   IN PTR  bind-01.inside.domusdigitalis.dev.
91   IN PTR  bind-02.inside.domusdigitalis.dev.

; === FreeIPA ===
100  IN PTR  ipa-01.inside.domusdigitalis.dev.
101  IN PTR  ipa-02.inside.domusdigitalis.dev.

; === Hypervisors ===
110  IN PTR  kvm-01.inside.domusdigitalis.dev.
111  IN PTR  kvm-02.inside.domusdigitalis.dev.

; === k3s ===
120  IN PTR  k3s-master-01.inside.domusdigitalis.dev.
121  IN PTR  k3s-master-02.inside.domusdigitalis.dev.
122  IN PTR  k3s-master-03.inside.domusdigitalis.dev.
123  IN PTR  k3s-worker-01.inside.domusdigitalis.dev.
124  IN PTR  k3s-worker-02.inside.domusdigitalis.dev.
125  IN PTR  k3s-worker-03.inside.domusdigitalis.dev.

; === Monitoring VIPs ===
130  IN PTR  traefik.inside.domusdigitalis.dev.
131  IN PTR  wazuh-indexer.inside.domusdigitalis.dev.
132  IN PTR  wazuh-dashboard.inside.domusdigitalis.dev.
133  IN PTR  wazuh-workers.inside.domusdigitalis.dev.
134  IN PTR  wazuh-manager.inside.domusdigitalis.dev.
135  IN PTR  zabbix-01.inside.domusdigitalis.dev.

; === IPMI ===
200  IN PTR  ipmi-01.inside.domusdigitalis.dev.
201  IN PTR  ipmi-02.inside.domusdigitalis.dev.
PTR
EOF

Then: increment serial → validate → reload.

Validation

Pre-Deployment Checks

Run before making changes:

echo "=== PRE-DEPLOYMENT: BIND Connectivity ==="
ssh bind-01 "hostname && uptime"
echo "=== PRE-DEPLOYMENT: Current Zone Status ==="
ssh bind-01 "sudo rndc zonestatus inside.domusdigitalis.dev | grep -E 'serial|expires'"
echo "=== PRE-DEPLOYMENT: Current Record Count ==="
ssh bind-01 "sudo grep -cE '^[a-z]' /var/named/inside.domusdigitalis.dev.zone"

Post-Deployment Validation

After reload, validate ALL records:

echo "=== POST-DEPLOYMENT: Validating ALL Records ==="
DOMAIN="inside.domusdigitalis.dev"
DNS="10.50.1.90"
FAILED=0

# All infrastructure hosts
HOSTS=(
  "vyos-01:10.50.1.2"
  "vyos-02:10.50.1.3"
  "vyos:10.50.1.1"
  "kvm-01:10.50.1.110"
  "kvm-02:10.50.1.111"
  "vault-01:10.50.1.60"
  "vault-02:10.50.1.61"
  "vault-03:10.50.1.62"
  "ise-01:10.50.1.20"
  "ise-02:10.50.1.21"
  "home-dc01:10.50.1.50"
  "home-dc02:10.50.1.51"
  "keycloak-01:10.50.1.80"
  "keycloak-02:10.50.1.81"
  "ipa-01:10.50.1.100"
  "ipa-02:10.50.1.101"
  "bind-01:10.50.1.90"
  "bind-02:10.50.1.91"
  "9800-wlc-01:10.50.1.40"
  "9800-wlc-02:10.50.1.41"
  "nas-01:10.50.1.70"
  "nas-02:10.50.1.71"
  "gitea-01:10.50.1.72"
  "minio-01:10.50.1.73"
  "ipsk-mgr-01:10.50.1.30"
  "ipsk-mgr-02:10.50.1.31"
  "ipsk-mgr:10.50.1.32"
  "3560-cx:10.50.1.10"
  "c9300-01:10.50.1.11"
  "ipmi-01:10.50.1.200"
  "ipmi-02:10.50.1.201"
  "k3s-master-01:10.50.1.120"
  "k3s-master-02:10.50.1.121"
  "k3s-master-03:10.50.1.122"
  "k3s-worker-01:10.50.1.123"
  "k3s-worker-02:10.50.1.124"
  "k3s-worker-03:10.50.1.125"
  "traefik:10.50.1.130"
  "wazuh-indexer:10.50.1.131"
  "wazuh-dashboard:10.50.1.132"
  "wazuh-workers:10.50.1.133"
  "wazuh-manager:10.50.1.134"
  "zabbix-01:10.50.1.135"
)

for entry in "${HOSTS[@]}"; do
  HOST="${entry%%:*}"
  EXPECTED="${entry##*:}"
  RESULT=$(dig @$DNS $HOST.$DOMAIN +short)
  if [[ "$RESULT" == "$EXPECTED" ]]; then
    echo "✓ $HOST → $RESULT"
  else
    echo "✗ $HOST: expected $EXPECTED, got $RESULT"
    ((FAILED++))
  fi
done

echo ""
echo "=== SUMMARY ==="
echo "Total: ${#HOSTS[@]} records"
echo "Failed: $FAILED"
[[ $FAILED -eq 0 ]] && echo "STATUS: ALL RECORDS VALID" || echo "STATUS: VALIDATION FAILED"
Expected Output
✓ vyos-01 → 10.50.1.2
✓ vyos-02 → 10.50.1.3
...
=== SUMMARY ===
Total: 43 records
Failed: 0
STATUS: ALL RECORDS VALID

Validate CNAMEs:

echo "=== POST-DEPLOYMENT: Validating CNAMEs ==="
DOMAIN="inside.domusdigitalis.dev"
DNS="10.50.1.90"

CNAMES=(
  "dc:home-dc01"
  "vault:vault-01"
  "ise:ise-01"
  "wlc:9800-wlc-01"
  "nas:nas-01"
  "wazuh:wazuh-manager"
)

for entry in "${CNAMES[@]}"; do
  ALIAS="${entry%%:*}"
  TARGET="${entry##*:}"
  RESULT=$(dig @$DNS $ALIAS.$DOMAIN CNAME +short)
  if [[ "$RESULT" == "$TARGET.$DOMAIN." ]]; then
    echo "✓ $ALIAS → $TARGET"
  else
    echo "✗ $ALIAS: expected $TARGET.$DOMAIN., got $RESULT"
  fi
done

Validate PTR records (sample):

echo "=== POST-DEPLOYMENT: Validating PTR Records (sample) ==="
DNS="10.50.1.90"

PTRS=(
  "10.50.1.1:vyos"
  "10.50.1.20:ise-01"
  "10.50.1.60:vault-01"
  "10.50.1.110:kvm-01"
  "10.50.1.120:k3s-master-01"
)

for entry in "${PTRS[@]}"; do
  IP="${entry%%:*}"
  EXPECTED="${entry##*:}"
  RESULT=$(dig @$DNS -x $IP +short)
  if [[ "$RESULT" == *"$EXPECTED"* ]]; then
    echo "✓ $IP → $RESULT"
  else
    echo "✗ $IP: expected *$EXPECTED*, got $RESULT"
  fi
done

Confirm serial incremented:

echo "=== POST-DEPLOYMENT: Confirm Serial Incremented ==="
ssh bind-01 "sudo rndc zonestatus inside.domusdigitalis.dev | grep serial"

Zone Status

ssh bind-01 "sudo rndc zonestatus inside.domusdigitalis.dev"
ssh bind-01 "sudo named-checkzone inside.domusdigitalis.dev /var/named/inside.domusdigitalis.dev.zone"

Zone Reload

After editing zone files:

ssh bind-01 "sudo rndc reload inside.domusdigitalis.dev"

Reload all zones:

ssh bind-01 "sudo rndc reload"

Configuration Validation

ssh bind-01 "sudo named-checkconf /etc/named.conf"

Service Management

ssh bind-01 "sudo systemctl status named"
ssh bind-01 "sudo systemctl restart named"

Query Logging

Enable (for debugging):

ssh bind-01 "sudo rndc querylog on"

Disable (production):

ssh bind-01 "sudo rndc querylog off"

Check status:

ssh bind-01 "sudo rndc status | grep 'query logging'"

Cache Management

Flush entire cache:

ssh bind-01 "sudo rndc flush"

Flush specific domain:

ssh bind-01 "sudo rndc flushname malicious.domain.com"

Dump cache for analysis:

ssh bind-01 "sudo rndc dumpdb -cache && sudo head -100 /var/named/data/cache_dump.db"

Logs

Live tail:

ssh bind-01 "sudo journalctl -u named -f"

Recent entries:

ssh bind-01 "sudo journalctl -u named --since '1 hour ago'"

Client DNS Queries

Basic Lookups

A record:

dig ise-01.inside.domusdigitalis.dev +short

Query specific server:

dig @10.50.1.90 ise-01.inside.domusdigitalis.dev +short

Service Discovery (SRV)

Kerberos:

dig _kerberos._tcp.inside.domusdigitalis.dev SRV +short

LDAP:

dig _ldap._tcp.inside.domusdigitalis.dev SRV +short

Reverse Lookup

dig -x 10.50.1.20 +short

CNAME Resolution

dig dc.inside.domusdigitalis.dev +short

Zone Info

SOA (serial, TTL):

dig inside.domusdigitalis.dev SOA +short

Name servers:

dig inside.domusdigitalis.dev NS +short

Security Checks

Version (should be hidden):

dig @10.50.1.90 version.bind chaos txt +short

Zone transfer (should fail):

dig @10.50.1.90 inside.domusdigitalis.dev AXFR

NetworkManager DNS Config

List 802.1X connections:

nmcli connection show | grep -E "802\.1X|ethernet|wifi"

Configure DNS:

nmcli connection modify "Wired-802.1X-Vault" ipv4.dns "10.50.1.90"
nmcli connection modify "Wired-802.1X-Vault" ipv4.dns-search "inside.domusdigitalis.dev"
nmcli connection modify "Wired-802.1X-Vault" ipv4.ignore-auto-dns yes
nmcli connection up "Wired-802.1X-Vault"

Verify:

nmcli connection show "Wired-802.1X-Vault" | grep -E "ipv4\.(dns|ignore)"

Troubleshooting

NXDOMAIN for Internal Host

# NXDOMAIN for internal host - check:
dig @10.50.1.90 host.inside.domusdigitalis.dev  # Direct to BIND
dig @10.50.1.1 host.inside.domusdigitalis.dev   # Via VyOS
ssh bind-01 "grep host /var/named/inside.domusdigitalis.dev.zone"

Kerberos "Cannot Find KDC"

# "Cannot find KDC" - SRV records missing
dig _kerberos._tcp.inside.domusdigitalis.dev SRV +short
# Should return: 0 100 88 home-dc01.inside.domusdigitalis.dev.
# If empty: add SRV records to zone file

Stale Cache

# Clear negative cache (NXDOMAIN cached)
ssh bind-01 "sudo rndc flushname missing.inside.domusdigitalis.dev"
# Or flush entire cache
ssh bind-01 "sudo rndc flush"

Permission Issues

ssh bind-01 "ls -la /var/named/*.zone /var/named/*.rev"
# Should be: -rw-r----- root named
ssh bind-01 "sudo chown root:named /var/named/inside.domusdigitalis.dev.zone"
ssh bind-01 "sudo chmod 640 /var/named/inside.domusdigitalis.dev.zone"

Update Existing DNS Records

Complete workflow for updating A record IPs with pre/post validation.

Phase 1: Pre-Validation

echo "=== 1.1 CURRENT STATE ==="
ssh $DNS_SERVER "sudo grep -E 'HOSTNAME_PATTERN' $FORWARD_ZONE"

echo "=== 1.2 CURRENT SERIAL ==="
ssh $DNS_SERVER "sudo rndc zonestatus $DOMAIN | grep serial"

echo "=== 1.3 CURRENT DNS RESOLUTION ==="
for h in host1 host2 host3; do
  echo -n "$h: "
  dig @$DNS_IP $h.$DOMAIN +short
done

Phase 2: Backup

TIMESTAMP=$(date +%Y%m%d%H%M)

echo "=== 2.1 CREATE BACKUP ==="
ssh $DNS_SERVER "sudo cp $FORWARD_ZONE ${FORWARD_ZONE}.bak.${TIMESTAMP}"

echo "=== 2.2 VERIFY BACKUP EXISTS ==="
ssh $DNS_SERVER "sudo ls -la ${FORWARD_ZONE}.bak.${TIMESTAMP}"

Phase 3: Capture Serial

CURRENT_SERIAL=$(dig @$DNS_IP $DOMAIN SOA +short | awk '{print $3}')
NEW_SERIAL=$((CURRENT_SERIAL + 1))

echo "=== 3.1 SERIAL VALUES ==="
echo "Current: $CURRENT_SERIAL"
echo "New:     $NEW_SERIAL"

Phase 4: Apply Changes

echo "=== 4.1 UPDATE IPS ==="
ssh $DNS_SERVER "sudo sed -i '/hostname/s/OLD_IP/NEW_IP/' $FORWARD_ZONE"

echo "=== 4.2 UPDATE SERIAL ==="
ssh $DNS_SERVER "sudo sed -i 's/$CURRENT_SERIAL/$NEW_SERIAL/' $FORWARD_ZONE"

echo "=== 4.3 VERIFY CHANGES ==="
ssh $DNS_SERVER "sudo grep -E 'HOSTNAME_PATTERN' $FORWARD_ZONE"
ssh $DNS_SERVER "sudo grep -E '[0-9]{10}.*[Ss]erial' $FORWARD_ZONE"

Phase 5: Validate & Reload

echo "=== 5.1 CHECK ZONE SYNTAX ==="
ssh $DNS_SERVER "sudo named-checkzone $DOMAIN $FORWARD_ZONE"

echo "=== 5.2 RELOAD ZONE ==="
ssh $DNS_SERVER "sudo rndc reload $DOMAIN"

echo "=== 5.3 VERIFY NEW SERIAL LOADED ==="
ssh $DNS_SERVER "sudo rndc zonestatus $DOMAIN | grep serial"

Phase 6: Post-Validation

echo "=== 6.1 VERIFY DNS RESOLUTION ==="
for h in host1 host2 host3; do
  result=$(dig @$DNS_IP $h.$DOMAIN +short)
  if [[ "$result" == "EXPECTED_IP" ]]; then
    echo "✓ $h → $result"
  else
    echo "✗ $h: expected EXPECTED_IP, got '$result'"
  fi
done

echo "=== 6.2 SUMMARY ==="
echo "Backup: ${FORWARD_ZONE}.bak.${TIMESTAMP}"
echo "Serial: $CURRENT_SERIAL → $NEW_SERIAL"
Example: Monitoring Records Fix (2026-03-04)
# Fix grafana, prometheus, alertmanager: 10.50.1.120 → 10.50.1.130
TIMESTAMP=$(date +%Y%m%d%H%M)
CURRENT_SERIAL=$(dig @$DNS_IP $DOMAIN SOA +short | awk '{print $3}')
NEW_SERIAL=$((CURRENT_SERIAL + 1))

ssh $DNS_SERVER "sudo cp $FORWARD_ZONE ${FORWARD_ZONE}.bak.${TIMESTAMP}"
ssh $DNS_SERVER "sudo sed -i '/grafana/s/10.50.1.120/10.50.1.130/' $FORWARD_ZONE"
ssh $DNS_SERVER "sudo sed -i '/prometheus/s/10.50.1.120/10.50.1.130/' $FORWARD_ZONE"
ssh $DNS_SERVER "sudo sed -i '/alertmanager/s/10.50.1.120/10.50.1.130/' $FORWARD_ZONE"
ssh $DNS_SERVER "sudo sed -i 's/$CURRENT_SERIAL/$NEW_SERIAL/' $FORWARD_ZONE"
ssh $DNS_SERVER "sudo named-checkzone $DOMAIN $FORWARD_ZONE"
ssh $DNS_SERVER "sudo rndc reload $DOMAIN"

# Result: Serial 2026030101 → 2026030102, all records → 10.50.1.130 ✓

Advanced CLI Patterns

Production-grade awk/jq patterns for DNS operations. Build muscle memory with these.

pfSense DNS Overrides (netapi + jq)

List all overrides with formatted table:

netapi pfsense dns list --format json | jq -r '
  ["HOST","IP","DESCRIPTION"],
  ["----","--","-----------"],
  (.[] | [.host, .ip, .descr // ""])
  | @tsv' | column -t

Filter overrides by pattern:

# Find all vyos-related overrides
netapi pfsense dns list --format json | jq -r '.[] | select(.host | test("vyos")) | "\(.host) → \(.ip)"'

# Find overrides pointing to specific IP
netapi pfsense dns list --format json | jq -r '.[] | select(.ip == "10.50.1.130") | .host'

Compare pfSense overrides vs BIND (find conflicts):

# Process substitution: compare two sources
diff <(netapi pfsense dns list --format json | jq -r '.[] | "\(.host) \(.ip)"' | sort) \
     <(ssh bind-01 "sudo awk '/IN[[:space:]]+A[[:space:]]+/ {print \$1, \$NF}' /var/named/inside.domusdigitalis.dev.zone" | sort)

BIND Zone Parsing (awk)

Extract A records with formatted output:

ssh bind-01 "sudo awk '/IN[[:space:]]+A[[:space:]]+/ {printf \"%-20s %s\n\", \$1, \$NF}' /var/named/inside.domusdigitalis.dev.zone"

Count records by type:

ssh bind-01 "sudo awk '/IN[[:space:]]+(A|CNAME|PTR)[[:space:]]/ {type[\$4]++} END {for (t in type) print t, type[t]}' /var/named/inside.domusdigitalis.dev.zone"

Find duplicate IPs (potential conflicts):

ssh bind-01 "sudo awk '/IN[[:space:]]+A[[:space:]]+/ {ip[\$NF]++; host[\$NF]=host[\$NF] \" \" \$1} END {for (i in ip) if (ip[i]>1) print i, \"→\", host[i]}' /var/named/inside.domusdigitalis.dev.zone"

Bulk DNS Validation (awk + dig + command substitution)

Validate all A records return expected IPs:

ssh bind-01 "sudo awk '/IN[[:space:]]+A[[:space:]]+/ {print \$1, \$NF}' /var/named/inside.domusdigitalis.dev.zone" | \
while read host ip; do
  result=$(dig @10.50.1.90 ${host}.inside.domusdigitalis.dev +short)
  [[ "$result" == "$ip" ]] && echo "✓ $host" || echo "✗ $host: expected $ip, got $result"
done

Parallel validation with xargs (faster):

ssh bind-01 "sudo awk '/IN[[:space:]]+A[[:space:]]+/ {print \$1}' /var/named/inside.domusdigitalis.dev.zone" | \
xargs -P4 -I{} sh -c 'r=$(dig @10.50.1.90 {}.inside.domusdigitalis.dev +short); echo "{} → $r"'

JSON Transform Patterns (jq)

Pivot DNS list to IP-grouped format:

netapi pfsense dns list --format json | jq -r 'group_by(.ip) | .[] | "=== \(.[0].ip) ===\n\([.[].host] | join(", "))\n"'

Export to CSV for spreadsheet:

netapi pfsense dns list --format json | jq -r '["host","ip","domain","descr"], (.[] | [.host, .ip, .domain, .descr // ""]) | @csv'

Create shell variables from JSON:

# Command substitution: JSON → shell vars
eval $(netapi pfsense dns list --format json | jq -r '.[] | select(.host == "vyos-01") | "VYOS01_IP=\(.ip)"')
echo $VYOS01_IP

Process Substitution Patterns

Compare two DNS servers' responses:

diff <(dig @10.50.1.90 vault-01.inside.domusdigitalis.dev +short) <(dig @10.50.1.1 vault-01.inside.domusdigitalis.dev +short)

Validate zone file against live DNS:

# Process substitution: file vs live
paste <(ssh bind-01 "sudo awk '/IN[[:space:]]+A[[:space:]]+/ {print \$1, \$NF}' /var/named/inside.domusdigitalis.dev.zone" | sort) \
      <(ssh bind-01 "sudo awk '/IN[[:space:]]+A[[:space:]]+/ {print \$1}' /var/named/inside.domusdigitalis.dev.zone" | while read h; do echo "$h $(dig @10.50.1.90 $h.inside.domusdigitalis.dev +short)"; done | sort) | \
awk '{if ($2==$4) print "✓", $1; else print "✗", $1, $2, "vs", $4}'

Feed multiple hosts to loop without subshell:

# Process substitution avoids subshell (variables persist)
count=0
while read host ip; do
  ((count++))
done < <(ssh bind-01 "sudo awk '/IN[[:space:]]+A[[:space:]]+/ {print \$1, \$NF}' /var/named/inside.domusdigitalis.dev.zone")
echo "Total records: $count"

One-Liners Quick Reference

Task Command

List pfSense overrides (vyos only)

netapi pfsense dns list --format json | jq -r '.[] | select(.host | test("vyos"))'

Count BIND A records

ssh bind-01 "sudo grep -c 'INA' /var/named/.zone"

Show zone serial

dig @10.50.1.90 inside.domusdigitalis.dev SOA +short | awk '{print $3}'

Find stale overrides (IP not in BIND)

comm -23 <(netapi pfsense dns list --format json | jq -r '.[].ip' | sort -u) <(ssh bind-01 "sudo awk '/IN A/ {print \$NF}' /var/named/*.zone" | sort -u)

Reverse lookup all k3s nodes

for i in 120 121 122; do echo -n "10.50.1.$i: "; dig @10.50.1.90 -x 10.50.1.$i +short; done