๐ Home Network: DDNS, DNS, and External Monitoring
๐ Purpose
This setup ensures:
- Your home network is always reachable via a domain name
- DNS updates automatically when your public IP changes
- External access is continuously validated
- Internal and external perspectives are intentionally separated
๐ง High-Level Architecture
Internet
โ
Public DNS (Cloudflare / Google)
โ
acorn.williatf.net (A record via DDNS)
โ
Your Public IP (dynamic)
โ
Router (port forwarding)
โ
Nginx Proxy Manager
โ
Internal Services (Home Assistant, etc.)
๐ DNS Design
Split DNS Strategy (Intentional)
| System | DNS Used | Behavior |
|---|---|---|
| Client devices | Pi-hole | Local overrides + ad blocking |
| Mac mini | 1.1.1.1 | External / real-world resolution |
๐ข Pi-hole Configuration
Local DNS Records
macmini.lan โ 192.168.5.189
Local CNAME Records
ha.williatf.net โ macmini.lan
npm.williatf.net โ macmini.lan
pihole.williatf.net โ macmini.lan
...
Upstream DNS
- Cloudflare โ
1.1.1.1 - Google โ
8.8.8.8
Resolution Flow
Client โ Pi-hole
โ local record? โ return local IP
โ else โ forward to upstream DNS
๐ต Mac Mini DNS (Important)
File:
/etc/resolv.conf
Contents:
nameserver 1.1.1.1
Why this is intentional
- Bypasses Pi-hole completely
- Ensures external DNS resolution
- Prevents circular dependency (Pi-hole relying on itself)
- Allows accurate external monitoring
๐ Dynamic DNS (DDNS)
Method: cPanel Dynamic DNS
Uses a secure update URL:
https://williatf.net/cpanelwebcall/<token>
This updates:
acorn.williatf.net โ current public IP
๐งพ DDNS Script
Location:
~/bin/update-home-ddns.sh
Responsibilities
- Fetch current public IP
- Compare with last known IP
- Update DNS only if:
- IP changed, or
- 24 hours elapsed (forced refresh)
Script (reference)
#!/usr/bin/env bash
set -euo pipefail
umask 077
DDNS_URL="https://williatf.net/cpanelwebcall/<token>"
STATE_FILE="$HOME/.cache/home-ddns-ip.txt"
STAMP_FILE="$HOME/.cache/home-ddns-last-refresh.txt"
IP_SERVICE="https://api.ipify.org"
FORCE_AFTER_SECONDS=86400
mkdir -p "$(dirname "$STATE_FILE")"
CURRENT_IP="$(curl -s "$IP_SERVICE")"
LAST_IP="$(cat "$STATE_FILE" 2>/dev/null || true)"
NOW="$(date +%s)"
LAST_REFRESH="$(cat "$STAMP_FILE" 2>/dev/null || echo 0)"
AGE=$((NOW - LAST_REFRESH))
if [[ "$CURRENT_IP" != "$LAST_IP" || "$AGE" -ge "$FORCE_AFTER_SECONDS" ]]; then
echo "Updating DDNS โ $CURRENT_IP"
curl -s "$DDNS_URL" >/dev/null
echo "$CURRENT_IP" > "$STATE_FILE"
echo "$NOW" > "$STAMP_FILE"
else
echo "No change: $CURRENT_IP"
fi
โฑ๏ธ Automation (systemd)
Service
~/.config/systemd/user/home-ddns.service
[Unit]
Description=Update home Dynamic DNS
[Service]
Type=oneshot
ExecStart=/home/todd/bin/update-home-ddns.sh
StandardOutput=journal
StandardError=journal
Timer
~/.config/systemd/user/home-ddns.timer
[Unit]
Description=Run DDNS updater every 5 minutes
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
Persistent=true
[Install]
WantedBy=timers.target
Enable
systemctl --user daemon-reload
systemctl --user enable --now home-ddns.timer
sudo loginctl enable-linger todd
๐ก External Monitoring (Uptime Kuma)
Tool
- Uptime Kuma
Monitor Target
https://ha.williatf.net
What it verifies
End-to-end external path:
- DNS resolution
- DDNS correctness
- Public IP routing
- Router port forwarding
- Nginx Proxy Manager
- Backend service (Home Assistant)
Why this works
Even though Pi-hole has local overrides:
- Mac mini uses public DNS
- Domain resolves to public IP
- Traffic exits network and re-enters (hairpin NAT)
This simulates real external access.
โ ๏ธ Important Behavior
Pi-hole vs Mac mini difference
| Query Source | Result |
|---|---|
| Pi-hole client | ha.williatf.net โ 192.168.x.x |
| Mac mini | ha.williatf.net โ public IP |
This is intentional and correct.
๐งญ Design Decisions
Why not use Pi-hole on Mac mini
- Avoids circular dependency
- Keeps DNS working if Pi-hole fails
- Enables true external monitoring
Tradeoff
| Approach | Result |
|---|---|
| Internal DNS | Faster, but hides failures |
| External DNS | Slightly slower, but accurate |
Chosen: External DNS for correctness
๐งช Troubleshooting
Check DNS resolution
dig +short ha.williatf.net
Check Pi-hole resolution
dig @<pihole-ip> +short ha.williatf.net
Check public IP
curl -s https://api.ipify.org
Check DDNS logs
journalctl --user -u home-ddns.service -n 20 --no-pager
๐ Security Notes
- DDNS URL = secret (acts like an API key)
- Regenerate it if exposed
- Restrict script permissions
โ Final State
- DDNS updates automatically
- DNS resolves correctly internally and externally
- External access is continuously monitored
- System avoids circular dependencies
๐ง Future Reminder
The Mac mini intentionally uses public DNS (1.1.1.1).
This is required for accurate external monitoring.
Do not change this unless you understand the consequences.