chore: scrub sensitive data for public repo

This commit is contained in:
Brian Pooe 2026-03-07 22:07:21 +02:00
parent 7e81e4f8c4
commit 60280dc17b
8 changed files with 225 additions and 225 deletions

View file

@ -68,8 +68,8 @@ EMAIL_HOST_PASSWORD=
DEFAULT_FROM_EMAIL=
# DNS stack (Technitium + Dockhand)
# Bind IP for DNS service (use your DNS host IP, e.g. 192.168.60.5)
DNS_BIND_IP=192.168.60.5
# Bind IP for DNS service (use your DNS host IP, e.g. 10.60.0.5)
DNS_BIND_IP=10.60.0.5
TECHNITIUM_WEB_BIND_IP=0.0.0.0
TECHNITIUM_WEB_PORT=5380
TECHNITIUM_WEB_CONTAINER_PORT=5380

View file

@ -4,7 +4,7 @@
#### Shared Snippets
(common) {
tls brian.method@gmail.com {
tls admin@example.com {
dns cloudflare {env.CLOUDFLARE_DNS_TOKEN}
resolvers 1.1.1.1 1.0.0.1
}
@ -26,7 +26,7 @@
(content-security-general) { # Default setup
# Disable unsafe inline/eval and plugins, only load scripts and stylesheets from same origin, fonts from google,
# and images from same origin and imgur. Sites should aim for policies like this.
Content-Security-Policy "frame-ancestors 'self' https://*.home.brianpooe.com"
Content-Security-Policy "frame-ancestors 'self' https://*.home.example.com"
#"default-src 'self'; style-src 'self'; script-src 'self'; font-src 'self'; img-src 'self'; form-action 'self'; connect-src 'self'; frame-ancestors 'none';"
#default-src 'none'; font-src https://fonts.gstatic.com; img-src 'self' https://i.imgur.com; object-src 'none'; script-src 'self'; style-src 'self'
}
@ -41,25 +41,25 @@
}
#### Infrastructure
dns01.home.brianpooe.com {
dns01.home.example.com {
import common
reverse_proxy 192.168.60.5:5380
reverse_proxy 10.60.0.5:5380
}
dockhand.home.brianpooe.com {
dockhand.home.example.com {
import common
reverse_proxy 192.168.60.5:3000
reverse_proxy 10.60.0.5:3000
}
nas.home.brianpooe.com {
nas.home.example.com {
import common
reverse_proxy 192.168.10.24:5000
reverse_proxy 10.10.0.24:5000
}
# Proxmox Backup Server
pbs.home.brianpooe.com {
pbs.home.example.com {
import common
reverse_proxy 192.168.10.8:8007 {
reverse_proxy 10.10.0.8:8007 {
transport http {
tls_insecure_skip_verify
}
@ -77,9 +77,9 @@ pbs.home.brianpooe.com {
}
# Proxmox
proxmox.home.brianpooe.com {
proxmox.home.example.com {
import common
reverse_proxy 192.168.10.10:8006 {
reverse_proxy 10.10.0.10:8006 {
transport http {
tls_insecure_skip_verify
}
@ -96,109 +96,109 @@ proxmox.home.brianpooe.com {
encode gzip
}
switchlite8poe.home.brianpooe.com {
switchlite8poe.home.example.com {
import common
reverse_proxy 192.168.1.5
reverse_proxy 10.1.0.5
}
tplink16de.home.brianpooe.com {
tplink16de.home.example.com {
import common
reverse_proxy 192.168.1.8
reverse_proxy 10.1.0.8
}
#### Synology Media Stack
bazarr.home.brianpooe.com {
bazarr.home.example.com {
import common
reverse_proxy 192.168.10.24:6767
reverse_proxy 10.10.0.24:6767
}
emby.home.brianpooe.com {
emby.home.example.com {
import common
reverse_proxy 192.168.10.24:8096
reverse_proxy 10.10.0.24:8096
}
flaresolverr.home.brianpooe.com {
flaresolverr.home.example.com {
import common
reverse_proxy 192.168.10.24:8191
reverse_proxy 10.10.0.24:8191
}
gluetun.home.brianpooe.com {
gluetun.home.example.com {
import common
reverse_proxy 192.168.10.24:8000
reverse_proxy 10.10.0.24:8000
}
prowlarr.home.brianpooe.com {
prowlarr.home.example.com {
import common
reverse_proxy 192.168.10.24:9696
reverse_proxy 10.10.0.24:9696
}
qbittorrent.home.brianpooe.com {
qbittorrent.home.example.com {
import common
reverse_proxy 192.168.10.24:8080
reverse_proxy 10.10.0.24:8080
}
radarr.home.brianpooe.com {
radarr.home.example.com {
import common
reverse_proxy 192.168.10.24:7878
reverse_proxy 10.10.0.24:7878
}
sabnzbd.home.brianpooe.com {
sabnzbd.home.example.com {
import common
reverse_proxy 192.168.10.24:8282
reverse_proxy 10.10.0.24:8282
}
seerr.home.brianpooe.com {
seerr.home.example.com {
import common
reverse_proxy 192.168.10.24:5055
reverse_proxy 10.10.0.24:5055
}
sonarr.home.brianpooe.com {
sonarr.home.example.com {
import common
reverse_proxy 192.168.10.24:8989
reverse_proxy 10.10.0.24:8989
}
#### Proxmox Apps
bento-pdf.home.brianpooe.com {
bento-pdf.home.example.com {
import common
reverse_proxy 192.168.10.6:8080
reverse_proxy 10.10.0.6:8080
}
beszel.home.brianpooe.com {
beszel.home.example.com {
import common
reverse_proxy 192.168.10.13:8090
reverse_proxy 10.10.0.13:8090
}
gramps.home.brianpooe.com {
gramps.home.example.com {
import common
reverse_proxy 192.168.10.17:5000
reverse_proxy 10.10.0.17:5000
}
immich.home.brianpooe.com {
immich.home.example.com {
import common
reverse_proxy 192.168.10.15:2283
reverse_proxy 10.10.0.15:2283
}
it-tools.home.brianpooe.com {
it-tools.home.example.com {
import common
reverse_proxy 192.168.10.7:80
reverse_proxy 10.10.0.7:80
}
paperless-ngx.home.brianpooe.com {
paperless-ngx.home.example.com {
import common
reverse_proxy 192.168.10.11:8000 {
reverse_proxy 10.10.0.11:8000 {
header_up Host {host}
}
encode gzip
}
speedtest.home.brianpooe.com {
speedtest.home.example.com {
import common
reverse_proxy 192.168.10.16:80
reverse_proxy 10.10.0.16:80
}
ytd.home.brianpooe.com {
ytd.home.example.com {
import common
uri strip_prefix metube
reverse_proxy 192.168.10.14:3033
reverse_proxy 10.10.0.14:3033
}

View file

@ -11,12 +11,12 @@ Use this as the single flow document. Each step links to the deeper guide where
- [pfsense-pihole-technitium-analysis.md](./pfsense-pihole-technitium-analysis.md)
## 2) Prepare the new Pi 4 with a temporary DNS IP
1. Connect Pi 4 to the same Adblock VLAN (`192.168.60.0/24`).
2. In pfSense, create a temporary DHCP static mapping for Pi 4, for example `192.168.60.6`.
1. Connect Pi 4 to the same Adblock VLAN (`10.60.0.0/24`).
2. In pfSense, create a temporary DHCP static mapping for Pi 4, for example `10.60.0.6`.
3. Confirm Pi 4 gets the temporary IP and has network reachability.
Why temporary IP first:
- You can build and test everything without disturbing production DNS (`192.168.60.5`).
- You can build and test everything without disturbing production DNS (`10.60.0.5`).
### Compatibility preflight on Pi 4 (important)
Run these checks before deployment:
@ -35,7 +35,7 @@ Expected:
## 3) Configure compose environment and render stack
1. Fill required values in `.env` (or environment file you use):
- `DNS_BIND_IP=192.168.60.6` (temporary during staging)
- `DNS_BIND_IP=10.60.0.6` (temporary during staging)
- `TECHNITIUM_ADMIN_PASSWORD=<strong-password>`
- `DOCKHAND_ENCRYPTION_KEY=<base64 key from openssl rand -base64 32>`
- `TECHNITIUM_CONFIG_DIR=./appdata/technitium`
@ -57,15 +57,15 @@ Reference:
## 4) Configure Technitium to mirror current behavior
1. Open Technitium UI on Pi 4 temporary IP:
- `http://192.168.60.6:5380`
2. Create primary zone `home.brianpooe.com`.
- `http://10.60.0.6:5380`
2. Create primary zone `home.example.com`.
3. Add:
- `A` record inside that zone:
- Name: `caddy` (FQDN: `caddy.home.brianpooe.com`)
- Address: `192.168.10.5`
- Name: `caddy` (FQDN: `caddy.home.example.com`)
- Address: `10.10.0.5`
- wildcard `CNAME` inside that zone:
- Name: `*` (FQDN: `*.home.brianpooe.com`)
- Target: `caddy.home.brianpooe.com`
- Name: `*` (FQDN: `*.home.example.com`)
- Target: `caddy.home.example.com`
4. Configure blocklist:
- `https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts`
5. Add allowlist entry:
@ -75,11 +75,11 @@ Reference:
- [technitium-cutover-checklist.md](./technitium-cutover-checklist.md)
## 5) Pre-cutover validation on temporary IP
From clients (or directly from Pi 4), validate against `192.168.60.6`:
From clients (or directly from Pi 4), validate against `10.60.0.6`:
```bash
nslookup google.com 192.168.60.6
nslookup proxmox.home.brianpooe.com 192.168.60.6
nslookup switchlite8poe.home.brianpooe.com 192.168.60.6
nslookup google.com 10.60.0.6
nslookup proxmox.home.example.com 10.60.0.6
nslookup switchlite8poe.home.example.com 10.60.0.6
```
If these fail, do not proceed to IP swap.
@ -99,22 +99,22 @@ Reference (full explanation):
Important for Caddy DNS-01 + Cloudflare:
- If Caddy host is inside a force-redirected VLAN (for this setup, `OFFICE`), exclude that host from the interface's forced DNS NAT rule.
- Keep rule source as inverted `!CADDY_HOST` and source port as `any` to `any`.
- This prevents ACME failures like: `expected 1 zone, got 0 for home.brianpooe.com`.
- This prevents ACME failures like: `expected 1 zone, got 0 for home.example.com`.
Optional hardening for encrypted DNS bypass:
- [pfsense-dot-doh-blocking-quick-entry.md](./pfsense-dot-doh-blocking-quick-entry.md)
## 7) Safe same-IP handover (make Pi 4 become `192.168.60.5`)
## 7) Safe same-IP handover (make Pi 4 become `10.60.0.5`)
1. Schedule a short maintenance window.
2. Stop/disconnect old Pi Zero 2 W first (avoid duplicate IP and ARP conflict).
3. In pfSense DHCP static mappings (Adblock VLAN):
- Move `192.168.60.5` reservation from old Pi MAC to new Pi 4 MAC.
- Move `10.60.0.5` reservation from old Pi MAC to new Pi 4 MAC.
4. On Pi 4:
- Renew DHCP lease or reboot networking/container host.
5. Confirm Pi 4 now has `192.168.60.5`.
6. If needed, clear stale ARP entry for `192.168.60.5` on pfSense.
5. Confirm Pi 4 now has `10.60.0.5`.
6. If needed, clear stale ARP entry for `10.60.0.5` on pfSense.
7. Update `.env`:
- set `DNS_BIND_IP=192.168.60.5`
- set `DNS_BIND_IP=10.60.0.5`
8. Re-render and restart stack:
```bash
./substitute_env.sh docker-compose-files/technitium-dockhand_template.yaml docker-compose-files/technitium-dockhand.yaml .env
@ -124,10 +124,10 @@ docker compose -f docker-compose-files/technitium-dockhand.yaml up -d
## 8) Post-cutover validation (production IP)
Run tests from each VLAN segment:
```bash
nslookup google.com 192.168.60.5
nslookup google.com 10.60.0.5
nslookup cloudflare.com 1.1.1.1
nslookup proxmox.home.brianpooe.com 192.168.60.5
nslookup switchlite8poe.home.brianpooe.com 192.168.60.5
nslookup proxmox.home.example.com 10.60.0.5
nslookup switchlite8poe.home.example.com 10.60.0.5
```
Expected:
@ -138,7 +138,7 @@ Expected:
## 9) Rollback (if needed)
1. Stop Technitium stack on Pi 4.
2. In pfSense, move `192.168.60.5` DHCP reservation back to old Pi Zero 2 W MAC.
2. In pfSense, move `10.60.0.5` DHCP reservation back to old Pi Zero 2 W MAC.
3. Start old Pi Zero 2 W.
4. Validate DNS from clients.

View file

@ -1,19 +1,19 @@
# pfSense Rule Set: Force DNS on LAN + All VLANs
This runbook enforces DNS redirection to your local resolver (`192.168.60.5`) across:
- LAN (`192.168.1.0/24`)
- Office / OPT2 (`192.168.10.0/24`)
- Family / OPT3 (`192.168.20.0/24`)
- IoT / OPT4 (`192.168.30.0/24`)
- Media / OPT5 (`192.168.40.0/24`)
- Guest / OPT6 (`192.168.50.0/24`)
This runbook enforces DNS redirection to your local resolver (`10.60.0.5`) across:
- LAN (`10.1.0.0/24`)
- Office / OPT2 (`10.10.0.0/24`)
- Family / OPT3 (`10.20.0.0/24`)
- IoT / OPT4 (`10.30.0.0/24`)
- Media / OPT5 (`10.40.0.0/24`)
- Guest / OPT6 (`10.50.0.0/24`)
## Why this is needed
Right now, redirect NAT is only on LAN. VLAN clients can still bypass your DNS filter by hardcoding public DNS on port 53.
## Prerequisites
- DNS server IP remains `192.168.60.5` (Pi-hole now, Technitium later).
- Existing inter-VLAN firewall policy still allows DNS to `192.168.60.5`.
- DNS server IP remains `10.60.0.5` (Pi-hole now, Technitium later).
- Existing inter-VLAN firewall policy still allows DNS to `10.60.0.5`.
## Step 1: Create pfSense aliases
Path: `Firewall > Aliases`
@ -21,12 +21,12 @@ Path: `Firewall > Aliases`
Create host alias:
- Name: `LOCAL_DNS`
- Type: `Host(s)`
- Value: `192.168.60.5`
- Value: `10.60.0.5`
Recommended (if Caddy uses DNS-01 with Cloudflare):
- Name: `CADDY_HOST`
- Type: `Host(s)`
- Value: `192.168.10.5`
- Value: `10.10.0.5`
Optional (cleaner descriptions):
- Name: `DNS_CLIENT_INTERFACES`
@ -47,7 +47,7 @@ Use these exact fields for each rule:
- Source: `<that interface> net`
- Source Port: `any`
- Destination: `Single host or alias` = `LOCAL_DNS`
- Invert Match (destination): `checked` (this means destination is NOT `192.168.60.5`)
- Invert Match (destination): `checked` (this means destination is NOT `10.60.0.5`)
- Destination Port Range: `DNS (53)`
- Redirect target IP: `LOCAL_DNS`
- Redirect target port: `DNS (53)`
@ -74,7 +74,7 @@ For OPT3/OPT4/OPT5/OPT6:
## Step 3b: Add DNS redirect exception for Caddy (ACME DNS-01)
Path: `Firewall > NAT > Port Forward`
If Caddy runs on a VLAN that is also force-redirected for DNS (example: `OFFICE` / `192.168.10.0/24`), edit that interface's forced DNS rule and exclude the Caddy host.
If Caddy runs on a VLAN that is also force-redirected for DNS (example: `OFFICE` / `10.10.0.0/24`), edit that interface's forced DNS rule and exclude the Caddy host.
Example rule to edit:
- `Force DNS to LOCAL_DNS (OFFICE)`
@ -89,8 +89,8 @@ Set:
Root cause this prevents:
- Forced DNS NAT can intercept Caddy's resolver lookups to public resolvers.
- In split-DNS environments, this makes Caddy see local zone `home.brianpooe.com` instead of public authority chain.
- Cloudflare DNS plugin then fails zone detection with `expected 1 zone, got 0 for home.brianpooe.com`.
- In split-DNS environments, this makes Caddy see local zone `home.example.com` instead of public authority chain.
- Cloudflare DNS plugin then fails zone detection with `expected 1 zone, got 0 for home.example.com`.
## Step 4: Apply changes
- Save and `Apply Changes` in NAT and Rules tabs.
@ -110,17 +110,17 @@ Expected result:
Also verify internal name resolution:
```bash
nslookup proxmox.home.brianpooe.com
nslookup switchlite8poe.home.brianpooe.com
nslookup proxmox.home.example.com
nslookup switchlite8poe.home.example.com
```
If using Caddy DNS-01, verify from Caddy host:
```bash
dig +short SOA home.brianpooe.com @1.1.1.1
dig +short SOA home.example.com @1.1.1.1
```
Expected:
- Result should not be your local Technitium SOA (`dns01.home.brianpooe.com ...`).
- Result should not be your local Technitium SOA (`dns01.home.example.com ...`).
## Optional hardening (recommended)
Port 53 forcing does not stop encrypted DNS bypass:
@ -138,8 +138,8 @@ Advanced hardening:
```mermaid
flowchart LR
A["Client on LAN/OPT2-OPT6"] --> B["Tries DNS to 8.8.8.8:53"]
B --> C["pfSense NAT Port Forward\n(destination != 192.168.60.5)"]
C --> D["Redirect to 192.168.60.5:53"]
B --> C["pfSense NAT Port Forward\n(destination != 10.60.0.5)"]
C --> D["Redirect to 10.60.0.5:53"]
D --> E["Pi-hole or Technitium handles query"]
```

View file

@ -8,12 +8,12 @@ Path: `Firewall > Aliases`
Create:
- Name: `LOCAL_DNS`
- Type: `Host(s)`
- Value: `192.168.60.5`
- Value: `10.60.0.5`
Optional (recommended when using Caddy DNS-01 with Cloudflare):
- Name: `CADDY_HOST`
- Type: `Host(s)`
- Value: `192.168.10.5`
- Value: `10.10.0.5`
## 1) NAT Port Forward rules (enter these 6)
Path: `Firewall > NAT > Port Forward`
@ -125,14 +125,14 @@ Set:
Why this is needed:
- Without this exception, pfSense intercepts Caddy's outbound DNS lookups (even when targeting public resolvers like `1.1.1.1`) and redirects them to local Technitium.
- Caddy then sees local split-DNS zone `home.brianpooe.com`, but Cloudflare only has public zone `brianpooe.com`.
- ACME fails with: `expected 1 zone, got 0 for home.brianpooe.com`.
- Caddy then sees local split-DNS zone `home.example.com`, but Cloudflare only has public zone `example.com`.
- ACME fails with: `expected 1 zone, got 0 for home.example.com`.
Validation from Caddy host:
```bash
dig +short SOA home.brianpooe.com @1.1.1.1
dig +short SOA home.example.com @1.1.1.1
```
- This should not return Technitium SOA (`dns01.home.brianpooe.com ...`) once the exception is active.
- This should not return Technitium SOA (`dns01.home.example.com ...`) once the exception is active.
## 2) Rule order checks (critical)
@ -154,8 +154,8 @@ Test from one client per segment:
```bash
nslookup google.com 8.8.8.8
nslookup cloudflare.com 1.1.1.1
nslookup proxmox.home.brianpooe.com
nslookup switchlite8poe.home.brianpooe.com
nslookup proxmox.home.example.com
nslookup switchlite8poe.home.example.com
```
Expected:

View file

@ -6,31 +6,31 @@
## Executive summary
Your current design is solid and intentional:
- pfSense DHCP points clients to Pi-hole (`192.168.60.5`) on all major VLANs.
- Pi-hole serves local split-DNS for reverse-proxied apps via `caddy.home.brianpooe.com`.
- Pi-hole forwards upstream DNS to pfSense (`192.168.1.1`), then pfSense uses its configured upstream resolvers.
- pfSense DHCP points clients to Pi-hole (`10.60.0.5`) on all major VLANs.
- Pi-hole serves local split-DNS for reverse-proxied apps via `caddy.home.example.com`.
- Pi-hole forwards upstream DNS to pfSense (`10.1.0.1`), then pfSense uses its configured upstream resolvers.
- Cross-VLAN isolation is enforced, with explicit exceptions for DNS to Pi-hole and selective access to Caddy.
Technitium can replace Pi-hole cleanly. The biggest migration requirement is preserving the same service IP (`192.168.60.5`) or updating every dependent DHCP/firewall/NAT reference.
Technitium can replace Pi-hole cleanly. The biggest migration requirement is preserving the same service IP (`10.60.0.5`) or updating every dependent DHCP/firewall/NAT reference.
## Current network and DNS components
- VLAN interfaces on pfSense:
- LAN: `192.168.1.0/24`
- Office (OPT2): `192.168.10.0/24`
- Family (OPT3): `192.168.20.0/24`
- IoT (OPT4): `192.168.30.0/24`
- Media (OPT5): `192.168.40.0/24`
- Guest (OPT6): `192.168.50.0/24`
- Adblock (OPT7): `192.168.60.0/24`
- LAN: `10.1.0.0/24`
- Office (OPT2): `10.10.0.0/24`
- Family (OPT3): `10.20.0.0/24`
- IoT (OPT4): `10.30.0.0/24`
- Media (OPT5): `10.40.0.0/24`
- Guest (OPT6): `10.50.0.0/24`
- Adblock (OPT7): `10.60.0.0/24`
- Pi-hole static DHCP mapping on Adblock VLAN:
- `raspberrypihole` -> `192.168.60.5`
- `raspberrypihole` -> `10.60.0.5`
- Caddy static DHCP mapping on Office VLAN:
- `raspberrypiproxy` -> `192.168.10.5`
- `raspberrypiproxy` -> `10.10.0.5`
## How pfSense and Pi-hole are connected
### 1. DHCP dependency (clients -> Pi-hole)
pfSense DHCP advertises `192.168.60.5` as DNS server for:
pfSense DHCP advertises `10.60.0.5` as DNS server for:
- LAN
- Office
- Family
@ -42,13 +42,13 @@ This is the primary coupling between pfSense and Pi-hole.
### 2. DNS forwarding dependency (Pi-hole -> pfSense)
From Pi-hole backup:
- `PIHOLE_DNS_1=192.168.1.1`
- `PIHOLE_DNS_2=192.168.1.1`
- `server=192.168.1.1`
- `PIHOLE_DNS_1=10.1.0.1`
- `PIHOLE_DNS_2=10.1.0.1`
- `server=10.1.0.1`
So every query path is:
1. Client -> Pi-hole (`192.168.60.5`)
2. Pi-hole -> pfSense (`192.168.1.1`)
1. Client -> Pi-hole (`10.60.0.5`)
2. Pi-hole -> pfSense (`10.1.0.1`)
3. pfSense DNS path -> upstream resolvers (for example `1.1.1.1` / `8.8.8.8` as configured in this backup)
Note:
@ -57,16 +57,16 @@ Note:
### 3. NAT DNS interception on pfSense
There is one DNS redirect NAT rule:
- Interface: `LAN` only
- Match: any source, destination `NOT 192.168.60.5`, port `53` TCP/UDP
- Redirect target: `192.168.60.5:53`
- Match: any source, destination `NOT 10.60.0.5`, port `53` TCP/UDP
- Redirect target: `10.60.0.5:53`
Important implication: forced redirect is currently configured only for LAN traffic, not all VLANs.
### 4. Firewall policy that enables segmented DNS
For Family/IoT/Media/Guest VLANs, you have explicit pass rules allowing DNS to `192.168.60.5:53` plus RFC1918 block rules. This is why segmented VLANs can still use centralized DNS.
For Family/IoT/Media/Guest VLANs, you have explicit pass rules allowing DNS to `10.60.0.5:53` plus RFC1918 block rules. This is why segmented VLANs can still use centralized DNS.
### 4a. Per-segment behavior (what is effectively enforced)
| Segment | DHCP DNS handed out | Explicit allow to Pi-hole `192.168.60.5:53` | RFC1918 block rule | Caddy cross-VLAN allow (`192.168.10.5`) | DNS force-redirect NAT |
| Segment | DHCP DNS handed out | Explicit allow to Pi-hole `10.60.0.5:53` | RFC1918 block rule | Caddy cross-VLAN allow (`10.10.0.5`) | DNS force-redirect NAT |
|---|---|---|---|---|---|
| LAN | Yes | Associated NAT pass rule exists | Yes (`Block all rogue DNS requests`) | N/A (LAN allow-any) | Yes (LAN only) |
| Office (OPT2) | Yes | No dedicated rule (allow-any policy) | No | Implicit via allow-any | No |
@ -83,11 +83,11 @@ Interpretation:
### 5. Local service naming through Pi-hole
Pi-hole local DNS is doing two jobs:
- `custom.list` has:
- `caddy.home.brianpooe.com -> 192.168.10.5`
- `caddy.home.example.com -> 10.10.0.5`
- `05-pihole-custom-cname.conf` has 26 CNAME entries, e.g.:
- `proxmox.home.brianpooe.com -> caddy.home.brianpooe.com`
- `radarr.home.brianpooe.com -> caddy.home.brianpooe.com`
- `paperless-ngx.home.brianpooe.com -> caddy.home.brianpooe.com`
- `proxmox.home.example.com -> caddy.home.example.com`
- `radarr.home.example.com -> caddy.home.example.com`
- `paperless-ngx.home.example.com -> caddy.home.example.com`
### 6. Ad-blocking state
From Pi-hole teleporter:
@ -99,17 +99,17 @@ From Pi-hole teleporter:
## Diagram: current flow
```mermaid
flowchart LR
A["Clients on VLANs\n192.168.1/10/20/30/40/50"] --> B["pfSense DHCP\nDNS server handed out: 192.168.60.5"]
A --> C["Pi-hole\n192.168.60.5 (Adblock VLAN)"]
C --> D["pfSense DNS service\n192.168.1.1"]
A["Clients on VLANs\n10.1/10/20/30/40/50"] --> B["pfSense DHCP\nDNS server handed out: 10.60.0.5"]
A --> C["Pi-hole\n10.60.0.5 (Adblock VLAN)"]
C --> D["pfSense DNS service\n10.1.0.1"]
D --> E["Upstream resolvers\n(e.g. 1.1.1.1 / 8.8.8.8)"]
A --> F["DNS to external resolvers:53"]
F -. "LAN only NAT redirect" .-> C
C --> G["A record\ncaddy.home.brianpooe.com -> 192.168.10.5"]
C --> H["26 CNAMEs\n*.home.brianpooe.com -> caddy.home.brianpooe.com"]
A --> I["Caddy reverse proxy\n192.168.10.5"]
C --> G["A record\ncaddy.home.example.com -> 10.10.0.5"]
C --> H["26 CNAMEs\n*.home.example.com -> caddy.home.example.com"]
A --> I["Caddy reverse proxy\n10.10.0.5"]
```
## Where Gemini was correct vs needs correction
@ -118,7 +118,7 @@ flowchart LR
- Technitium can replace Pi-hole and keep most pfSense-side design.
- Your CNAME-based app routing can be moved cleanly to Technitium zones.
- VLAN segmentation + explicit DNS access pattern is present.
- Pi-hole upstream currently points at pfSense (`192.168.1.1`).
- Pi-hole upstream currently points at pfSense (`10.1.0.1`).
### Needs correction
- "Forced DNS intercept" is not global right now:
@ -133,40 +133,40 @@ flowchart LR
| Current function | Current implementation | Technitium equivalent | Migration action |
|---|---|---|---|
| Central DNS IP | Pi-hole at `192.168.60.5` | Technitium at `192.168.60.5` | Keep same IP to avoid touching multiple pfSense dependencies |
| DHCP DNS handout | pfSense DHCP -> `192.168.60.5` on VLANs | Same | No change needed if IP unchanged |
| Cross-VLAN DNS permit | Firewall pass rules to `192.168.60.5:53` | Same | No change needed |
| Forced DNS on LAN | NAT redirect to `192.168.60.5:53` | Same | No change needed |
| Local app DNS | `custom.list` + CNAME file | Authoritative zone in Technitium | Create `home.brianpooe.com` zone, add A for `caddy`, add CNAME records |
| Central DNS IP | Pi-hole at `10.60.0.5` | Technitium at `10.60.0.5` | Keep same IP to avoid touching multiple pfSense dependencies |
| DHCP DNS handout | pfSense DHCP -> `10.60.0.5` on VLANs | Same | No change needed if IP unchanged |
| Cross-VLAN DNS permit | Firewall pass rules to `10.60.0.5:53` | Same | No change needed |
| Forced DNS on LAN | NAT redirect to `10.60.0.5:53` | Same | No change needed |
| Local app DNS | `custom.list` + CNAME file | Authoritative zone in Technitium | Create `home.example.com` zone, add A for `caddy`, add CNAME records |
| Ad block list | StevenBlack in Pi-hole | Block List in Technitium | Add same list URL |
| Allowlist | `www.googleadservices.com` | Allow Zone / Whitelist | Add same domain |
| Upstream resolution | Pi-hole -> pfSense (`192.168.1.1`) -> upstream resolvers | Technitium recursive resolver or forwarders | Choose direct recursion or keep pfSense as upstream intentionally |
| Upstream resolution | Pi-hole -> pfSense (`10.1.0.1`) -> upstream resolvers | Technitium recursive resolver or forwarders | Choose direct recursion or keep pfSense as upstream intentionally |
## Diagram: proposed with Technitium
```mermaid
flowchart LR
A["Clients on VLANs"] --> B["pfSense DHCP\nDNS server: 192.168.60.5"]
A --> C["Technitium DNS\n192.168.60.5"]
A["Clients on VLANs"] --> B["pfSense DHCP\nDNS server: 10.60.0.5"]
A --> C["Technitium DNS\n10.60.0.5"]
C --> D["Technitium recursion\n(root/TLD authoritative path)"]
D --> E["Internet DNS hierarchy"]
C --> F["Zone: home.brianpooe.com"]
F --> G["A: caddy -> 192.168.10.5"]
F --> H["CNAME: app hosts -> caddy.home.brianpooe.com"]
C --> F["Zone: home.example.com"]
F --> G["A: caddy -> 10.10.0.5"]
F --> H["CNAME: app hosts -> caddy.home.example.com"]
A --> I["Caddy reverse proxy\n192.168.10.5"]
A --> I["Caddy reverse proxy\n10.10.0.5"]
```
## Practical migration checklist
1. Deploy Technitium on Raspberry Pi with IP `192.168.60.5` (or cutover with brief IP swap).
2. Recreate `home.brianpooe.com` zone:
- Add `A` record `caddy.home.brianpooe.com -> 192.168.10.5`
1. Deploy Technitium on Raspberry Pi with IP `10.60.0.5` (or cutover with brief IP swap).
2. Recreate `home.example.com` zone:
- Add `A` record `caddy.home.example.com -> 10.10.0.5`
- Add all 26 CNAME records from current Pi-hole file.
3. Import StevenBlack block list and add allow entry for `www.googleadservices.com`.
4. Validate DNS from each VLAN (`dig/nslookup`) for:
- public domain resolution
- blocked ad domain
- `proxmox.home.brianpooe.com` and other internal names
- `proxmox.home.example.com` and other internal names
5. Cut traffic to Technitium (same IP preferred).
6. Optional hardening after cutover:
- add DNS redirect/block on VLANs beyond LAN if you want true forced DNS everywhere
@ -174,7 +174,7 @@ flowchart LR
- decide whether to allow/deny direct DNS-over-TLS and DNS-over-HTTPS egress
## Key risks to keep in mind
- If Technitium is not on `192.168.60.5`, you must update DHCP options, firewall rules, and NAT target references.
- If Technitium is not on `10.60.0.5`, you must update DHCP options, firewall rules, and NAT target references.
- DNS interception in your current config is partial (LAN-focused), not universal across all VLANs.
- IPv6 DNS policy is separate; current forced DNS logic is IPv4 (`inet`) only.

View file

@ -7,31 +7,31 @@ Start-here master flow:
No, not if all your app hostnames should resolve to Caddy.
You can replace most manual CNAMEs with:
- `A` record: `caddy.home.brianpooe.com -> 192.168.10.5`
- wildcard `CNAME`: `*.home.brianpooe.com -> caddy.home.brianpooe.com`
- `A` record: `caddy.home.example.com -> 10.10.0.5`
- wildcard `CNAME`: `*.home.example.com -> caddy.home.example.com`
This removes the need to add a new DNS record every time you add a new proxied service.
Technitium UI tip:
- When editing records inside zone `home.brianpooe.com`, use relative names (`caddy`, `*`) rather than full FQDN labels.
- When editing records inside zone `home.example.com`, use relative names (`caddy`, `*`) rather than full FQDN labels.
## Wildcard caveats (important)
- Wildcard does not cover the zone apex itself (`home.brianpooe.com`).
- Wildcard does not cover the zone apex itself (`home.example.com`).
- Explicit records still override wildcard (this is good; you can keep special cases).
- A typo hostname will still resolve to Caddy; Caddy may return 404/cert mismatch until that host is configured there.
- Keep your reverse-proxy security policy in Caddy (host matching/auth), because DNS wildcard is broad by design.
## What your current data says
From Pi-hole backup:
- `caddy.home.brianpooe.com -> 192.168.10.5`
- 26 CNAMEs currently pointing to `caddy.home.brianpooe.com`
- `caddy.home.example.com -> 10.10.0.5`
- 26 CNAMEs currently pointing to `caddy.home.example.com`
From your Caddyfile host blocks:
- 24 hostnames are defined in file.
- 1 active hostname exists in Caddy but is missing from Pi-hole CNAME list:
- `switchlite8poe.home.brianpooe.com`
- `switchlite8poe.home.example.com`
- 1 hostname is in Pi-hole CNAME list but not currently in Caddy:
- `tplink8pe.home.brianpooe.com`
- `tplink8pe.home.example.com`
A wildcard record closes this drift gap permanently.
@ -42,64 +42,64 @@ A wildcard record closes this drift gap permanently.
- Deployment guide: [technitium-dockhand-deployment.md](./technitium-dockhand-deployment.md)
### 1. Keep network references stable
- Keep Technitium service IP as `192.168.60.5`.
- Keep Technitium service IP as `10.60.0.5`.
- This avoids changing pfSense DHCP DNS settings, firewall allow rules, and LAN DNS redirect NAT target.
### 2. Deploy Technitium on Raspberry Pi
- Run Technitium container on the Adblock VLAN network reachable as `192.168.60.5`.
- Run Technitium container on the Adblock VLAN network reachable as `10.60.0.5`.
- Ensure TCP/UDP `53` reaches this container from all intended VLANs.
### 3. Create primary zone
- Zone name: `home.brianpooe.com`
- Zone name: `home.example.com`
- Zone type: Primary
### 4. Add DNS records (choose one model)
#### Model A: wildcard-first (recommended)
Create only these core records first:
- `A` record inside zone `home.brianpooe.com`:
- Name: `caddy` (FQDN `caddy.home.brianpooe.com`)
- Address: `192.168.10.5`
- `CNAME` record inside zone `home.brianpooe.com`:
- Name: `*` (FQDN `*.home.brianpooe.com`)
- Target: `caddy.home.brianpooe.com`
- `A` record inside zone `home.example.com`:
- Name: `caddy` (FQDN `caddy.home.example.com`)
- Address: `10.10.0.5`
- `CNAME` record inside zone `home.example.com`:
- Name: `*` (FQDN `*.home.example.com`)
- Target: `caddy.home.example.com`
Optional explicit records for readability (not required if wildcard exists):
- `CNAME` `pihole.home.brianpooe.com` -> `caddy.home.brianpooe.com`
- `CNAME` `proxmox.home.brianpooe.com` -> `caddy.home.brianpooe.com`
- `CNAME` `pihole.home.example.com` -> `caddy.home.example.com`
- `CNAME` `proxmox.home.example.com` -> `caddy.home.example.com`
- etc.
#### Model B: explicit-only (current behavior equivalent)
If you prefer explicit control, create:
- `A` `caddy.home.brianpooe.com` -> `192.168.10.5`
- `A` `caddy.home.example.com` -> `10.10.0.5`
- CNAME records for each service:
- `pihole.home.brianpooe.com`
- `proxmox.home.brianpooe.com`
- `nas.home.brianpooe.com`
- `bazarr.home.brianpooe.com`
- `emby.home.brianpooe.com`
- `flaresolverr.home.brianpooe.com`
- `gluetun.home.brianpooe.com`
- `prowlarr.home.brianpooe.com`
- `qbittorrent.home.brianpooe.com`
- `radarr.home.brianpooe.com`
- `sabnzbd.home.brianpooe.com`
- `sonarr.home.brianpooe.com`
- `tplink8pe.home.brianpooe.com`
- `tplink16de.home.brianpooe.com`
- `pbs.home.brianpooe.com`
- `paperless-ngx.home.brianpooe.com`
- `it-tools.home.brianpooe.com`
- `bento-pdf.home.brianpooe.com`
- `beszel.home.brianpooe.com`
- `immich.home.brianpooe.com`
- `speedtest.home.brianpooe.com`
- `gramps.home.brianpooe.com`
- `ytd.home.brianpooe.com`
- `seerr.home.brianpooe.com`
- `pihole.home.example.com`
- `proxmox.home.example.com`
- `nas.home.example.com`
- `bazarr.home.example.com`
- `emby.home.example.com`
- `flaresolverr.home.example.com`
- `gluetun.home.example.com`
- `prowlarr.home.example.com`
- `qbittorrent.home.example.com`
- `radarr.home.example.com`
- `sabnzbd.home.example.com`
- `sonarr.home.example.com`
- `tplink8pe.home.example.com`
- `tplink16de.home.example.com`
- `pbs.home.example.com`
- `paperless-ngx.home.example.com`
- `it-tools.home.example.com`
- `bento-pdf.home.example.com`
- `beszel.home.example.com`
- `immich.home.example.com`
- `speedtest.home.example.com`
- `gramps.home.example.com`
- `ytd.home.example.com`
- `seerr.home.example.com`
If using explicit-only, also add active Caddy hosts that currently have no Pi-hole entry:
- `switchlite8poe.home.brianpooe.com`
- `switchlite8poe.home.example.com`
### 5. Recreate filtering behavior
- Add blocklist URL:
@ -109,27 +109,27 @@ If using explicit-only, also add active Caddy hosts that currently have no Pi-ho
### 6. Choose upstream strategy in Technitium
Pick one:
- Least-change: forward to pfSense (`192.168.1.1`) first.
- Least-change: forward to pfSense (`10.1.0.1`) first.
- Cleaner path: enable direct recursive resolution in Technitium.
### 7. Validate before full cutover
From a client in each VLAN, test:
- `nslookup proxmox.home.brianpooe.com 192.168.60.5`
- `nslookup switchlite8poe.home.brianpooe.com 192.168.60.5`
- `nslookup google.com 192.168.60.5`
- `nslookup www.googleadservices.com 192.168.60.5` (should be allowed)
- `nslookup proxmox.home.example.com 10.60.0.5`
- `nslookup switchlite8poe.home.example.com 10.60.0.5`
- `nslookup google.com 10.60.0.5`
- `nslookup www.googleadservices.com 10.60.0.5` (should be allowed)
Also test one blocked ad/tracker domain from query logs.
### 8. Cutover
- Stop Pi-hole service on `192.168.60.5`.
- Start Technitium on `192.168.60.5`.
- Stop Pi-hole service on `10.60.0.5`.
- Start Technitium on `10.60.0.5`.
- Confirm DNS responses and Caddy app access from each VLAN.
### 9. Rollback plan
If anything fails:
- Stop Technitium.
- Start Pi-hole back on `192.168.60.5`.
- Start Pi-hole back on `10.60.0.5`.
- Existing pfSense integration should recover immediately since IP dependencies are unchanged.
## Decision recommendation for your setup
@ -142,9 +142,9 @@ Reason:
## DNS + proxy interaction (with wildcard)
```mermaid
flowchart LR
A["Client queries any host\n*.home.brianpooe.com"] --> B["Technitium wildcard CNAME"]
B --> C["caddy.home.brianpooe.com"]
C --> D["A record 192.168.10.5"]
A["Client queries any host\n*.home.example.com"] --> B["Technitium wildcard CNAME"]
B --> C["caddy.home.example.com"]
C --> D["A record 10.10.0.5"]
D --> E["Caddy routes by Host header\nto matching app"]
```

View file

@ -10,7 +10,7 @@ Compatibility note:
## 1) Prepare env values
Update your `.env` with these required values:
- `DNS_BIND_IP=192.168.60.5`
- `DNS_BIND_IP=10.60.0.5`
- `TECHNITIUM_ADMIN_PASSWORD=<strong-password>`
- `DOCKHAND_ENCRYPTION_KEY=<base64 key that decodes to exactly 32 bytes>`
- `TECHNITIUM_CONFIG_DIR=./appdata/technitium`
@ -54,20 +54,20 @@ Open:
- `http://<DNS_BIND_IP>:5380`
Then configure:
1. Create primary zone `home.brianpooe.com`.
1. Create primary zone `home.example.com`.
2. Add `A` record inside that zone:
- Name: `caddy` (FQDN: `caddy.home.brianpooe.com`)
- Address: `192.168.10.5`
- Name: `caddy` (FQDN: `caddy.home.example.com`)
- Address: `10.10.0.5`
3. Add wildcard `CNAME` inside that zone:
- Name: `*` (FQDN: `*.home.brianpooe.com`)
- Target: `caddy.home.brianpooe.com`
- Name: `*` (FQDN: `*.home.example.com`)
- Target: `caddy.home.example.com`
4. Add allowlist equivalent for `www.googleadservices.com`.
Reference details:
- [technitium-cutover-checklist.md](./technitium-cutover-checklist.md)
## 5) pfSense integration
Keep DNS server IP as `192.168.60.5` to avoid changing existing DHCP and firewall dependencies.
Keep DNS server IP as `10.60.0.5` to avoid changing existing DHCP and firewall dependencies.
For forced DNS across all VLANs:
- [pfsense-forced-dns-quick-entry.md](./pfsense-forced-dns-quick-entry.md)
@ -78,16 +78,16 @@ For DoT/DoH hardening:
## 6) Validation
Run from clients on each VLAN:
```bash
nslookup google.com 192.168.60.5
nslookup proxmox.home.brianpooe.com 192.168.60.5
nslookup switchlite8poe.home.brianpooe.com 192.168.60.5
nslookup google.com 10.60.0.5
nslookup proxmox.home.example.com 10.60.0.5
nslookup switchlite8poe.home.example.com 10.60.0.5
```
## 7) Common gotchas
- If Technitium fails to bind port 53, check for other DNS services on the host (for example `systemd-resolved`, `dnsmasq`, old Pi-hole container).
- If Dockhand cannot manage containers, verify `DOCKER_GID` and docker socket mount.
- If Dockhand shows `Invalid ENCRYPTION_KEY`, regenerate with `openssl rand -base64 32`, re-render the compose file, and restart Dockhand.
- If Caddy ACME DNS-01 fails with `expected 1 zone, got 0 for home.brianpooe.com`, your forced-DNS NAT is likely intercepting Caddy's resolver lookups.
- If Caddy ACME DNS-01 fails with `expected 1 zone, got 0 for home.example.com`, your forced-DNS NAT is likely intercepting Caddy's resolver lookups.
- Add `CADDY_HOST` alias and exclude it in the forced DNS NAT rule on Caddy's interface (source `!CADDY_HOST`, source port `any`).
- Keep destination invert `!LOCAL_DNS`, destination port `53`, redirect to `LOCAL_DNS:53`.
- See: [pfsense-forced-dns-all-vlans.md](./pfsense-forced-dns-all-vlans.md)