4.3 KiB
4.3 KiB
| project | type | status | path | tags | created | updated | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| dns-knot-pihole-end-to-end | session-notes | completed | Tech/Sessions |
|
2026-05-08 | 2026-05-08 |
DNS — Knot + Pi-hole End-to-End
Outcome
Phase 1 of the Traefik project closed. Internal DNS architecture from
the homelab DNS decision doc is live: Knot DNS authoritative for
herbylab.dev on LXC 103, Pi-hole conditionally forwarding the zone,
OpenWrt passing answers through. All six initial records resolve
cleanly from a normal LAN client through the full chain.
Architecture (as built)
Client → OpenWrt (10.0.11.1) → Pi-hole (10.0.11.50) ├─ herbylab.dev → Knot (10.0.11.10) └─ everything else → Unbound → root
Records served
| Name | IP | VLAN |
|---|---|---|
| ns1 | 10.0.11.10 | Lab |
| immich | 10.0.11.62 | Lab |
| pihole | 10.0.11.50 | Lab |
| plex | 10.0.11.54 | Lab |
| pve | 10.0.21.188 | Private |
| traefik | 10.0.21.181 | Private |
Migrated from .lan to .dev to enable LE wildcard via Cloudflare
DNS-01 in Phase 2.
Key Learnings
knotc reloaddoes not re-bind sockets. It reloads the config file but listening sockets only get re-evaluated on full daemon restart. Iflisten:directives change (or were missing on first start),systemctl restart knotis required. Symptom: zone loads,zone-statusreports correctly, butdigreturns "connection refused."- Knot config order matters for references.
aclblocks must be defined beforezoneblocks that reference them — YAML validation is single-pass, no forward references. Same pattern likely applies to other ID-based references. - Fresh PVE LXCs inherit Tailscale resolvers from the host.
/etc/resolv.confgets populated with100.100.100.100by default. If the LXC isn't on the tailnet, name resolution silently fails until pointed at a reachable resolver. Permanent fix: configure DNS in the LXC's PVE config or, for a DNS server LXC, point it at itself once it's serving. - OpenWrt rebind protection silently filters RFC1918 answers. When
dnsmasq has
rebind_protection='1', any DNS answer pointing to a private IP gets stripped — query returns NOERROR with ANSWER:0 and EDE 15 (Blocked). The fix is whitelisting the domain viarebind_domain. Easy to miss because the block happens after the upstream answers correctly, so direct queries to Pi-hole or Knot work fine while client-path queries fail. - EDE 15 (Blocked) doesn't always mean a blocklist match. It's a
generic "this answer was filtered" code. Pi-hole's blocklists,
OpenWrt's rebind protection, and several other filters all use it.
Pi-hole's query log saying "Allowed" while
digreports EDE 15 is the tell — block is happening downstream of Pi-hole. - Pi-hole v6 renamed conditional forwarding. It's now under
reverse-server config with format
,,,. Same plumbing, different UI surface. - Direct-vs-chain queries are the diagnostic shortcut. When a DNS
chain fails, querying each link directly with
dig @immediately localizes the broken hop. Saved real time tonight isolating the OpenWrt rebind issue.
Decisions Locked
- Zone file lives at
/var/lib/knot/herbylab.dev.zoneon LXC 103, owned byknot:knot - Single NS record (
ns1) for now; secondary will be added when NAS instance comes up - TTL strategy: 300 during buildout (current), raise to 3600 once stable
- SOA serial format:
YYYYMMDDNN(current:2026050801) - ACL allows transfer/notify from localhost only — will expand when secondary is added
- Conditional forwarding scoped to
10.0.0.0/8in Pi-hole — VLAN/firewall layer enforces actual access boundaries
Open Items
- LXC
/etc/resolv.confis a manual edit pointing at Pi-hole — repoint at itself (10.0.11.10) with Pi-hole as fallback, make persistent - Knot zone file into git as source of truth
- Ansible role for Knot primary deployment
- Knot secondary on the NAS (after primary proven stable)
- Second Pi-hole instance for redundancy
- Pi-hole sync method decision (gravity-sync vs Teleporter)
- Move Traefik VM to Lab VLAN (currently on Private from original bridge setup)
- Raise TTLs to 3600 once buildout settles
Next Session
Phase 2 — wildcard cert via Traefik + Cloudflare DNS-01. DNS layer is done.