# AdGuard DNS Mesh Network-wide ad blocking and split-horizon DNS for all Headscale mesh nodes. ## Architecture ``` Headscale (Calypso) pushes DNS config to all nodes │ ┌──────┴──────┐ ▼ ▼ Calypso AdGuard Atlantis AdGuard 100.103.48.78:53 100.83.230.112:53 (primary) (backup) │ │ └──────┬──────┘ ▼ All Tailscale nodes (ad blocking + split-horizon) ``` ## How It Works 1. **Headscale** pushes two DNS nameservers (Tailscale IPs) to all connected nodes 2. Each node resolves DNS through **Calypso AdGuard** (primary) or **Atlantis AdGuard** (fallback) 3. AdGuard provides **ad blocking** (filter lists) and **split-horizon DNS** (38 rewrites for internal services) 4. Upstream DNS uses DoH: AdGuard DNS, Cloudflare, Quad9, Google, LibreDNS ## Headscale DNS Config File: `/volume1/docker/headscale/config/config.yaml` on Calypso Repo: `hosts/synology/calypso/headscale-config.yaml` ```yaml dns: magic_dns: true base_domain: tail.vish.gg nameservers: global: - 100.103.48.78 # Calypso AdGuard (Tailscale IP) - 100.83.230.112 # Atlantis AdGuard (Tailscale IP) ``` Using Tailscale IPs (not LAN IPs) ensures remote nodes (Seattle, Jellyfish, mobile devices, GL.iNet routers) can reach the DNS servers. ## Split-Horizon DNS Rewrites (38 rules) Both Calypso and Atlantis AdGuard instances have identical rewrites: | Domain | Answer | Purpose | |--------|--------|---------| | `*.vish.gg` | `100.85.21.51` | Wildcard → matrix-ubuntu (NPM) | | `pt.vish.gg` | `192.168.0.154` | Portainer (direct, not proxied) | | `sso.vish.gg` | `192.168.0.154` | Authentik SSO | | `git.vish.gg` | `192.168.0.154` | Gitea | | `dash.vish.gg` | `192.168.0.154` | Homarr dashboard | | `gf.vish.gg` | `192.168.0.154` | Grafana | | `headscale.vish.gg` | `192.168.0.250` | Headscale server | | `derp.vish.gg` | `192.168.0.250` | DERP relay (Calypso) | | `derp-atl.vish.gg` | `192.168.0.200` | DERP relay (Atlantis) | | `derp-sea.vish.gg` | `100.82.197.124` | DERP relay (Seattle) | | `turn.thevish.io` | `192.168.0.200` | TURN server | | `*.tail.vish.gg` | per-node Tailscale IP | MagicDNS node records (12) | | `*.vish.local` | per-node Tailscale IP | Local aliases (8) | | `*.crista.home` | per-node Tailscale IP | Crista network aliases (2) | | `*.thevish.io` | `100.85.21.51` | thevish.io wildcard | | `*.crista.love` | `100.85.21.51` | crista.love wildcard | ## Node Status | Node | accept-dns | Split-Horizon | Ad Blocking | Notes | |------|:---:|:---:|:---:|-------| | homelab-vm | enabled | Yes | Yes | | | atlantis | enabled | Yes | Yes | | | seattle | enabled | Yes | Yes | Remote VPS | | nuc | enabled | Yes | Yes | | | pi-5 | enabled | Yes | Yes | | | matrix-ubuntu | enabled | Yes | Yes | | | jellyfish | enabled | Yes | Yes | No dig/nslookup, verified via python | | calypso | enabled | N/A | N/A | IS the AdGuard server; Synology can't override resolv.conf | | setillo | enabled | No | Yes (local) | Synology limitation; has own local AdGuard | | guava | N/A | N/A | Via LAN | TrueNAS app; uses Calypso AdGuard at 192.168.0.250 on LAN | Mobile devices, GL.iNet routers, and Home Assistant use default `--accept-dns=true` and inherit DNS from Headscale automatically. ## Rollback ### Immediate (~2 min) - revert Headscale DNS to LAN IPs ```bash ssh calypso 'sed -i "s/100.103.48.78/192.168.0.250/" /volume1/docker/headscale/config/config.yaml && \ sed -i "s/100.83.230.112/192.168.68.100/" /volume1/docker/headscale/config/config.yaml' # Restart headscale via Portainer or: ssh calypso '/usr/local/bin/docker restart headscale' ``` ### Per-node override ```bash # On the affected node: tailscale set --accept-dns=false ``` ### Nuclear - bypass Tailscale DNS entirely ```bash echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf ``` ### Symptoms | Symptom | Cause | Fix | |---------|-------|-----| | DNS resolution hangs | Both AdGuard containers down | Restart AdGuard on Calypso/Atlantis | | Wrong split-horizon (public IP) | Rewrite missing on responding AdGuard | Add missing rewrite via AdGuard UI | | One node can't resolve | Tailscale tunnel down | `tailscale ping calypso` to diagnose | ## AdGuard Instances | Instance | Host | LAN IP | Tailscale IP | Web UI | Role | |----------|------|--------|-------------|--------|------| | Calypso | Synology | 192.168.0.250:9080 | 100.103.48.78:9080 | http://192.168.0.250:9080 | Primary | | Atlantis | Synology | 192.168.0.200:9080 | 100.83.230.112:9080 | http://192.168.0.200:9080 | Backup | | NUC | Intel NUC | 192.168.68.100:9080 | 100.72.55.21:9080 | http://192.168.68.100:9080 | Local (Concord subnet) | | Setillo | Synology | 192.168.69.x | 100.125.0.20 | host network | Local (remote site) | ## Maintenance When adding a new split-horizon rewrite, add it to **both** Calypso and Atlantis AdGuard instances to keep them in sync. Use the AdGuard web UI or API: ```bash # Calypso (via MCP tool or API) curl -u vish:PASSWORD -X POST http://192.168.0.250:9080/control/rewrite/add \ -H 'Content-Type: application/json' \ -d '{"domain":"new.vish.gg","answer":"192.168.0.154"}' # Atlantis (same, different host) curl -u vish:PASSWORD -X POST http://192.168.0.200:9080/control/rewrite/add \ -H 'Content-Type: application/json' \ -d '{"domain":"new.vish.gg","answer":"192.168.0.154"}' ``` --- *Deployed: 2026-03-31* *Config: `hosts/synology/calypso/headscale-config.yaml`*