# Shared zone-loading + recursive-forwarding config. # CoreDNS snippets are textually expanded by `import`, so we keep anything # that's not transport-specific (TLS) in here. (common) { auto { directory /zones (.*)\.zone {1} reload 30s } # AXFR authorization is `to *` at this layer, with HE-only filtering # done by the FortiWiFi firewall (source IP restriction on the # TCP/53 DNAT rule). Reasons we don't filter at CoreDNS: # # 1. CoreDNS plugin quirk: `to ` (any form — single, # multi-line, space-separated) silently fails to start server # blocks. Reproduced on 1.11.3 and 1.12.2. Only `to *` works. # 2. Docker port publishing with userland-proxy rewrites source # IPs to the bridge gateway, so IP filtering wouldn't see HE's # real address anyway (without network_mode: host). # 3. Filtering at the perimeter (FortiWiFi) is correct-layered # defense: bad packets don't reach the host at all. # # Required FortiWiFi rule: # VIP "coredns-tcp" — src in {216.218.130.2, 216.218.131.2, # 216.218.132.2, 216.218.133.2, 216.66.1.2} — # dst WAN:53/tcp → 172.16.1.15:5353/tcp transfer { to * } forward . 1.1.1.1 1.0.0.1 9.9.9.9 { max_concurrent 1000 } cache 30 errors log loop reload 10s } # Plain DNS — UDP/TCP :53. Health + metrics live here only (one binding). . { import common health :8080 prometheus :9153 } # DNS-over-TLS — RFC 7858. Port 853 is the IANA-assigned DoT port. tls://.:853 { tls /etc/coredns/certs/cert.pem /etc/coredns/certs/key.pem import common } # DNS-over-HTTPS — RFC 8484. Default path is /dns-query. # Clients: curl -H 'accept: application/dns-message' https://host:8443/dns-query?dns=... https://.:443 { tls /etc/coredns/certs/cert.pem /etc/coredns/certs/key.pem import common }