Skip to main content

Pi-Hole - Add DNS to your Pi-Hole from Active Directory LDAPS

This pulls DNS node names from Active Directory over LDAPS, resolves A records against the AD DNS server (dig), imports into Pi-hole's custom.list, and restarts DNS.

Make sure you have the ROOT cert on the pi-hole side:

Certificate Trust Issue (by far the #1 cause with AD LDAPS) Active Directory DCs usually use certificates issued by your internal Enterprise CA (or a self-signed one). Your Pi-hole doesn't trust it by default.

Setup option:

How it works:

  • Queries both DomainDnsZones and ForestDnsZones
  • Handles hostnames with multiple IP addresses
  • Improved skipping of junk records
  • Proper error handling and logging
  • Compatible with Pi-hole v6 (uses systemctl restart pihole-FTL or fallback)
  • Uses your domain onling.com as example (easily changeable)
  • Safer password handling via file
  • Backup of previous records
#!/bin/bash
# =============================================================================
# AD-to-Pi-hole DNS Sync Script (via LDAPS) - Updated for Pi-hole v6
# Pulls DNS nodes from Active Directory (DomainDnsZones + ForestDnsZones),
# resolves A records, imports into Pi-hole, and restarts DNS.
#
# Requirements: ldap-utils dnsutils
# Run as root or with sudo.
# =============================================================================

set -euo pipefail

# ========================== CONFIGURATION ==========================
AD_DC="dom-001.local.com"                  # Your AD Domain Controller FQDN

BIND_DN="cn=pihole-dns-sync,ou=Service Accounts,dc=onling,dc=com"
BIND_PASS_FILE="/root/.ad_bind_pass"            # chmod 600 this file

DOMAIN="local.com"

CUSTOM_LIST="/etc/pihole/custom.list"
BACKUP_DIR="/root/dns_backups"

# Skip common AD junk records
SKIP_PATTERNS="@ . _msdcs _tcp _udp _sites _gc _kerberos _kpasswd _ldap _smtp gc _domaincontroller"

# Optional: Set to "never" only for testing if you have cert issues
export LDAPTLS_REQCERT=demand
# =================================================================

# Create backup directory
mkdir -p "$BACKUP_DIR"

TIMESTAMP=$(date +"%Y%m%d-%H%M%S")
BACKUP_FILE="${BACKUP_DIR}/custom.list.${TIMESTAMP}.bak"

echo "[$(date '+%Y-%m-%d %H:%M:%S')] === Starting Active Directory → Pi-hole DNS sync ==="

# Read password securely
if [ -f "$BIND_PASS_FILE" ]; then
    BIND_PASS=$(cat "$BIND_PASS_FILE")
else
    echo "ERROR: Password file $BIND_PASS_FILE not found!"
    echo "Create it with: echo 'YourPassword' > $BIND_PASS_FILE && chmod 600 $BIND_PASS_FILE"
    exit 1
fi

# Temporary files
TEMP_NAMES=$(mktemp)
TEMP_DOMAIN=$(mktemp)
TEMP_FOREST=$(mktemp)
TEMP_RECORDS=$(mktemp)

trap 'rm -f "$TEMP_NAMES" "$TEMP_DOMAIN" "$TEMP_FOREST" "$TEMP_RECORDS"' EXIT

# 1. Query DNS nodes from both DomainDnsZones and ForestDnsZones
echo "Querying DNS nodes via LDAPS from DomainDnsZones and ForestDnsZones..."

DOMAIN_DN="DC=$(echo "$DOMAIN" | sed 's/\./,DC=/g')"

echo "Searching DomainDnsZones..."
ldapsearch -H "ldaps://${AD_DC}" \
  -x -D "${BIND_DN}" -w "${BIND_PASS}" \
  -b "CN=MicrosoftDNS,DC=DomainDnsZones,${DOMAIN_DN}" \
  -s sub "(objectClass=dnsNode)" dc 2>/dev/null | grep -E "^dc:" | awk '{print $2}' | sort -u > "$TEMP_DOMAIN"

echo "Searching ForestDnsZones..."
ldapsearch -H "ldaps://${AD_DC}" \
  -x -D "${BIND_DN}" -w "${BIND_PASS}" \
  -b "CN=MicrosoftDNS,DC=ForestDnsZones,${DOMAIN_DN}" \
  -s sub "(objectClass=dnsNode)" dc 2>/dev/null | grep -E "^dc:" | awk '{print $2}' | sort -u > "$TEMP_FOREST"

# Combine and deduplicate
cat "$TEMP_DOMAIN" "$TEMP_FOREST" 2>/dev/null | sort -u > "$TEMP_NAMES"

NAME_COUNT=$(wc -l < "$TEMP_NAMES")
echo "Found ${NAME_COUNT} unique DNS node names."

if [ "$NAME_COUNT" -eq 0 ]; then
    echo "ERROR: No DNS nodes found. Check credentials and search bases."
    exit 1
fi

# 2. Resolve A records (supports multiple IPs per hostname)
echo "Resolving A records via dig against ${AD_DC}..."
> "$TEMP_RECORDS"

while IFS= read -r name || [ -n "$name" ]; do
    [ -z "$name" ] && continue

    # Skip junk patterns
    if [[ " ${SKIP_PATTERNS} " == *" ${name} "* ]]; then
        continue
    fi

    # Try short name and FQDN
    for fqdn in "${name}.${DOMAIN}" "${name}"; do
        # Get ALL valid IPv4 addresses
        mapfile -t ips < <(dig @"${AD_DC}" +short +time=3 +tries=2 "${fqdn}" A 2>/dev/null | \
                          grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$')

        if [ ${#ips[@]} -gt 0 ]; then
            for ip in "${ips[@]}"; do
                echo "${ip} ${name}" >> "$TEMP_RECORDS"
                # Add FQDN version too (helps with resolution)
                if [ "${fqdn}" != "${name}" ]; then
                    echo "${ip} ${fqdn}" >> "$TEMP_RECORDS"
                fi
            done
            break
        fi
    done
done < "$TEMP_NAMES"

sort -u "$TEMP_RECORDS" -o "$TEMP_RECORDS"

RECORD_COUNT=$(wc -l < "$TEMP_RECORDS")
echo "Resolved ${RECORD_COUNT} A record entries."

# 3. Backup and import into Pi-hole
echo "Backing up current custom.list..."
if [ -f "$CUSTOM_LIST" ]; then
    cp -f "$CUSTOM_LIST" "$BACKUP_FILE"
    echo "   Backup: ${BACKUP_FILE}"
fi

echo "Writing new records to ${CUSTOM_LIST}..."
cp "$TEMP_RECORDS" "$CUSTOM_LIST"
chown pihole:pihole "$CUSTOM_LIST" 2>/dev/null || true
chmod 644 "$CUSTOM_LIST"

# 4. Restart Pi-hole DNS (Pi-hole v6 compatible)
echo "Restarting Pi-hole DNS service..."
if command -v systemctl >/dev/null 2>&1; then
    systemctl restart pihole-FTL
    echo "   Restarted via: systemctl restart pihole-FTL"
else
    service pihole-FTL restart
    echo "   Restarted via: service pihole-FTL restart"
fi

echo "[$(date '+%Y-%m-%d %H:%M:%S')] Sync completed successfully!"
echo "   Imported ${RECORD_COUNT} entries from AD."
echo "   Verify a record:   pihole -q <hostname>"
echo "   Check logs:        sudo journalctl -u pihole-FTL -n 100 --no-pager"

exit 0

 

How to Deploy

  1. Save as /usr/local/bin/ad-pihole-dns-sync.sh
  2. Make executable: chmod +x /usr/local/bin/ad-pihole-dns-sync.sh
  3. Create the password file securely:
    echo 'YourActualPassword' > /root/.ad_bind_pass
    chmod 600 /root/.ad_bind_pass
  4. Update AD_DC and BIND_DN if needed.
  5. Test: sudo /usr/local/bin/ad-pihole-dns-sync.sh
  6. Add to cron (example: every hour):
    0 * * * * /usr/local/bin/ad-pihole-dns-sync.sh >> /var/log/ad-dns-sync.log 2>&1