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:
- Recommended (secure): Export your root CA certificate from AD and trust it on the Pi-hole.
- On a Windows machine: Open MMC → Certificates → Trusted Root Certification Authorities → find your domain CA → Export as Base-64 encoded X.509 (.CER).
- Copy the .cer file to Pi-hole:
/usr/local/share/ca-certificates/ad-root-ca.crt - Run:
update-ca-certificates
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
- Save as /usr/local/bin/ad-pihole-dns-sync.sh
- Make executable: chmod +x /usr/local/bin/ad-pihole-dns-sync.sh
- Create the password file securely:
echo 'YourActualPassword' > /root/.ad_bind_pass chmod 600 /root/.ad_bind_pass - Update AD_DC and BIND_DN if needed.
- Test: sudo /usr/local/bin/ad-pihole-dns-sync.sh
- Add to cron (example: every hour):
0 * * * * /usr/local/bin/ad-pihole-dns-sync.sh >> /var/log/ad-dns-sync.log 2>&1