Technitium DNS: Mijn Geavanceerde Setup & High Availability
In dit document beschrijf ik de geavanceerde inrichting van mijn Technitium DNS-cluster. Ik leg hierin vast hoe ik de Access Control Lists (ACL) voor mijn VLAN's heb ingericht, hoe mijn actieve clustering werkt, hoe ik High Availability via keepalived toepas en hoe ik alles integreer met mijn Nginx Proxy Manager (NPM).
1. Network Access Control List (ACL) & Recursie
Standaard weigert Technitium DNS-aanvragen (recursie) van IP-adressen die buiten het eigen subnet vallen. Om ervoor te zorgen dat al mijn lokale VLAN's en WireGuard-clients namen kunnen resolven, moet ik deze subnets expliciet toevoegen aan de ACL.
Mijn configuratie (Voorbeeldnetwerken):
Ik navigeer in mijn Technitium dashboards naar Settings > Recursion, zoek de Network Access Control List (ACL) op en voeg daar mijn netwerken aan toe.
Hieronder staat het overzicht van de subnets die ik als voorbeeld gebruik voor mijn VLAN-structuur:
192.168.11.0/24(Mijn Management- / Hoofdnetwerk - Voorbeeld)192.168.21.0/24(Mijn IoT / VLAN 21 - Voorbeeld)192.168.31.0/24(Mijn Gadgets / VLAN 31 - Voorbeeld)192.168.41.0/24(Mijn Gasten / VLAN 41 - Voorbeeld)10.6.0.0/24(Mijn WireGuard VPN Subnet - Voorbeeld)
!!! note "Belangrijk voor mijn setup" Als ik dit vergeet, merken mijn clients op het VPN-subnet of de overige VLAN's dat ze wel een actieve netwerkverbinding hebben, maar ze kunnen geen enkele website of lokale domeinnaam laden omdat de DNS-aanvragen simpelweg gedropt worden.
2. Technitium Clustering & Node-Verdeling
Om absolute redundantie te garanderen tegen het uitvallen van een complete hypervisor, heb ik mijn twee Technitium DNS-servers fysiek gescheiden over mijn Proxmox-omgevingen:
- DNS 1 (Primary Node -
192.168.11.50): Draait als LXC-container binnen mijn hoofd-cluster (PVE1 / PVE2). - DNS 2 (Secondary Node -
192.168.11.51): Draait als LXC-container op mijn standalone PVE3 node.
Hoe ik de synchronisatie instel:
- Op mijn Secondary Node op PVE3 configureer ik mijn hoofdserver (
192.168.11.50) als cluster-partner. - Ik maak onder Settings > API Keys een API-sleutel aan, zodat de servers via HTTPS veilig met elkaar praten.
- Zodra ik een nieuwe lokale DNS-zone of een blocklist-wijziging toepas op mijn Primary, pusht Technitium dit binnen enkele seconden automatisch over het netwerk naar de standalone PVE3 node.
3. High Availability met keepalived (Virtual IP & Priority)
Hoewel ik twee fysiek gescheiden DNS-servers heb draaien, wil ik in de DHCP-opties van mijn router en op mijn Proxmox-hosts niet handmatig twee aparte DNS IP-adressen hoeven mee te geven.
Om dit op te lossen gebruik ik keepalived. Deze service draait actief op allebei de DNS-containers om samen één Virtual IP (VIP) op 192.168.11.53 te beheren.
Mijn Prioriteitsconfiguratie (VRRP Logic):
Om te zorgen dat DNS 1 (op het hoofdcluster) altijd de voorkeur krijgt zolang hij online is, werk ik met een duidelijke prioriteitsverdeling (priority) en een gezondheidscontrole via een script.
Wees altijd zeer voorzichtig met wijzigingen in deze code.
Stap 1: Configureer the Primary DNS (192.168.11.50)
Zorg dat je keepalived.conf op de hoofdserver er exact zo uitziet:
global_defs {
enable_script_security
script_user root
vrrp_garp_master_delay 1
vrrp_garp_master_repeat 3
}
vrrp_script check_technitium {
script "/usr/bin/pgrep dotnet"
interval 2
weight -20
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 53
priority 150 # De Primary krijgt de hoogste basisprioriteit
nopreempt
authentication {
auth_type PASS
auth_pass homelabDNS
}
virtual_ipaddress {
192.168.11.53/24
}
track_script {
check_technitium
}
}
Stap 2: Configureer de nieuwe Secondary DNS (192.168.11.51)
Omdat de secundaire container gloednieuw is, moeten we zorgen dat de prioriteit hier lager staat (op 140). Als Technitium op de Primary server dan omvalt, trekt het script 20 punten van de prioriteit af (150 - 20 = 130). Omdat 130 lager is dan de 140 van de Secondary, neemt de Secondary het VIP direct over!
Open nano /etc/keepalived/keepalived.conf op de .51 server en plak exact dit bestand:
global_defs {
enable_script_security
script_user root
vrrp_garp_master_delay 1
vrrp_garp_master_repeat 3
}
vrrp_script check_technitium {
script "/usr/bin/pgrep dotnet"
interval 2
weight -20
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 53 # Moet exact gelijk zijn (53)
priority 140 # Lager dan de Primary (150)
nopreempt
authentication {
auth_type PASS
auth_pass homelabDNS # Moet exact gelijk zijn
}
virtual_ipaddress {
192.168.11.53/24
}
track_script {
check_technitium
}
}
Waarom dit zo krachtig is:
Dankzij keepalived hoef ik in de DHCP-instellingen van mijn router (Asus-router) en in de /etc/resolv.conf van al mijn Proxmox-hosts (PVE1, PVE2 en PVE3) slechts één DNS-server in te stellen: 192.168.11.53.
Mocht de PVE-node herstarten waar DNS 1 op draait, dan ziet DNS 2 op PVE3 dat de heartbeat wegvalt. Hij activeert direct zijn eigen stand-by status en trekt het VIP (.53) binnen een fractie van een seconde naar zich toe. Zodra DNS 1 weer opstart, zorgt zijn hogere prioriteit (150) ervoor dat hij het VIP netjes en automatisch weer overneemt.
4. Zones Inrichten & Koppeling met Nginx Proxy Manager (NPM)
Om mijn lokale services binnen het netwerk bereikbaar te maken via mooie URL's en gecodeerd DNS-verkeer (DNS-over-HTTPS / DoH) te ondersteunen via Firefox (Max Protection), richt ik binnen Technitium een Primary Zone in voor mijn domein patad.nl.
Stap 1: Hoe ik de Zone aanmaak
- Ik ga naar Zones > Add Zone.
- Ik maak een zone aan voor mijn domein:
patad.nl.
Stap 2: Hoe ik mijn records naar NPM verwijs
In plaats van elke app een eigen lokaal IP-adres te geven in mijn DNS-zone, kies ik ervoor om alle A-records te verwijzen naar het IP-adres van mijn Nginx Proxy Manager (NPM). Mijn NPM regelt vervolgens op basis van de subdomeinnaam (HTTP-header) naar welke LXC of VM het verkeer daadwerkelijk moet worden doorgestuurd.
Mijn NPM draait als Docker-container in host mode op een specifieke Proxmox VM met het lokale IP-adres 192.168.11.139.
Ik maak hiervoor records aan (of een Wildcard *.patad.nl) en verwijs deze naar de proxy:
* Name: proxy.patad.nl (en eventuele andere hosts)
* Type: A
* TTL: 3600
* IP Address: 192.168.11.139
Hierdoor stuurt Technitium elke aanvraag voor een van mijn subdomeinen (inclusief de DoH query's van mijn browsers) direct door naar mijn proxy-poortwachter (NPM) op poort 80/443. NPM zorgt met een Wildcard Let's Encrypt-certificaat (*.patad.nl) voor de volledige SSL/TLS-versleuteling en stuurt het intern door naar de web/DoH-poort (8053) van het Technitium-VIP (192.168.11.53).
5. Waar ik extra rekening mee moet houden (Mijn Aandachtspunten)
1. Het "Kip-en-Ei" probleem met de Proxy (DNS Loopback)
Omdat NPM via een proxy-host DoH-verkeer moet doorsturen naar het Technitium-VIP (192.168.11.53), mag de onderliggende VM van NPM nooit afhankelijk zijn van datzelfde Technitium-cluster voor zijn eigen internettoegang. Als het DNS-cluster namelijk offline zou gaan, krijgt de NPM-VM geen DNS-resolutie meer, waardoor NPM geen SSL-certificaten kan vernieuwen of Cloudflare-API's kan bereiken.
Oplossing:
De netwerkconfiguratie van de NPM Docker-VM (/etc/netplan/50-cloud-init.yaml) is expliciet zo ingesteld dat deze het Technitium-cluster volledig omzeilt en altijd direct praat met externe upstream DNS-servers:
nameservers:
addresses:
- 1.1.1.1 # Cloudflare DNS (waar tevens de patad.nl zone leeft)
- 9.9.9.9 # Quad9 DNS Fallback
2. Wat als het hΓ©le interne cluster platligt?
Dankzij de fysieke scheiding over het PVE-cluster en de standalone PVE3 node is de kans dat beide Technitium-servers tegelijk uitvallen minimaal. Mocht er zich echter een totale stroom- of netwerkstoring voordoen waardoor het complete interne cluster down gaat, dan zal de naamresolutie in het hele huis stoppen.
Mijn Rampenplan (Disaster Recovery):
Mocht het volledige interne cluster onverhoopt offline gaan, dan herstel ik de internettoegang voor de clients in huis handmatig via de router:
1. Log in op de beheerpagina van de Asus-router.
2. Navigeer naar de DHCP / LAN DNS instellingen.
3. Overschrijf het lokale VIP (192.168.11.53) tijdelijk met het DNS IP-adres van mijn Internet Service Provider (ISP) of een publieke resolver (1.1.1.1).
4. Zodra het Technitium-cluster weer is opgebouwd en stabiel draait, wissel ik de DHCP-DNS instelling in de router weer terug naar het VIP.
Om de stabiliteit en snelheid van mijn DNS-over-HTTPS (DoH) routing wereldwijd aan de tand te kunnen voelen, gebruik ik een op maat gemaakt PowerShell-script. Dit script vuurt 120 unieke live recursie-verzoeken af (verdeeld over 6 wereldwijde regio's), past actieve cache-busting toe en exporteert de resultaten direct naar een Excel-grafiek.
Kanttekening: Optimalisatie voor Nederlandstalige Excel
!!! warning "Belangrijk voor Excel-export"
De onderliggende ImportExcel-module genereert XML-formules in het Engels. Wanneer deze data wordt geopend in een Nederlandstalige versie van Excel, kan de rekenmachine van Excel foutmeldingen geven ("Probleem met formuleverwijzingen") en weigeren de grafiek in te kleuren.
Dank je wel microsoft(briljant)
Om dit definitief op te lossen, is de exportstructuur in onderstaand script volledig gesplitst: de 120 regels platte tekst worden weggeschreven op het tabblad `"Ruwe Data"`, terwijl de samenvattingstabel en de grafiek direct via de pure `-ColumnChart` engine op het tabblad `"Grafiek Samenvatting"` worden gegenereerd. Dit voorkomt pop-ups en garandeert een direct ingevulde, blauwe staafgrafiek.
Het PowerShell Benchmark Script (doh-test.ps1)
Om dit script direct via ./doh-test.ps1 in de terminal te kunnen starten, dient eenmalig de Execution Policy te worden aangepast via een Administrator-terminal:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force
# ==============================================================================
# TECHNITIUM DNS-OVER-HTTPS (DoH) STRESSTEST & EXCEL BENCHMARK
# ==============================================================================
# --- SCHOONMAKEN ---
\$Global:Results = @()
# --- CONFIGURATIE ---
\$DohDomain = "proxy.patad.nl"
BaseUrl = "https://DohDomain/dns-query"
\(ThresholdMs = 75\)DelayMs = 250
# Pad waar het Excel-bestand wordt opgeslagen (Op je Bureaublad)
ExcelPad = "([Environment]::GetFolderPath('Desktop'))\DoH_Benchmark_Resultaten.xlsx"
\$DomainList = @(
# --- Nederland (.nl) - 20 domeinen ---
"tweakers.net", "nu.nl", "nos.nl", "buienradar.nl", "bol.com",
"rijksoverheid.nl", "rabobank.nl", "ing.nl", "kpn.com", "ziggo.nl",
"coolblue.nl", "marktplaats.nl", "belastingdienst.nl", "nlnetlabs.nl", "surf.nl",
"tudelft.nl", "uva.nl", "volkskrant.nl", "telegraaf.nl", "albertheijn.nl",
# --- Duitsland (.de) - 20 domeinen ---
"amazon.de", "spiegel.de", "bild.de", "tagesschau.de", "heise.de",
"welt.de", "zeit.de", "focus.de", "golem.de", "computerbase.de",
"ebay.de", "bahn.de", "otto.de", "zalando.de", "check24.de",
"adac.de", "telekom.com", "bundesregierung.de", "dfb.de", "kit.edu",
# --- Frankrijk (.fr) - 20 domeinen ---
"lemonde.fr", "lefigaro.fr", "leparisien.fr", "lesechos.fr", "liberation.fr",
"allocine.fr", "cdiscount.com", "fnac.com", "vinted.fr", "carrefour.fr",
"sncf-connect.com", "impots.gouv.fr", "ameli.fr", "gouvernement.fr", "edf.fr",
"orange.fr", "free.fr", "sfr.fr", "sorbonne-universite.fr", "lequipe.fr",
# --- United States (.com / .org / .gov) - 20 domeinen ---
"cloudflare.com", "ietf.org", "paypal.com", "verisign.com", "google.com",
"microsoft.com", "wikipedia.org", "github.com", "amazon.com", "apple.com",
"meta.com", "netflix.com", "nasa.gov", "usa.gov", "whitehouse.gov",
"nytimes.com", "cnn.com", "mit.edu", "harvard.edu", "stanford.edu",
# --- United Kingdom (.uk) - 20 domeinen ---
"gov.uk", "bbc.co.uk", "nhs.uk", "ox.ac.uk", "cam.ac.uk",
"coop.co.uk", "theguardian.com", "telegraph.co.uk", "hmv.com", "ba.com",
"sky.com", "bt.com", "ee.co.uk", "vodafone.co.uk", "tesco.com",
"sainsburys.co.uk", "asda.com", "argos.co.uk", "tfl.gov.uk", "manutd.com",
# --- AziΓ« (.cn / .jp / .in / .kr / .sg) - 20 domeinen ---
"baidu.com", "taobao.com", "jd.com", "weibo.com", "zhihu.com",
"amazon.co.jp", "rakuten.co.jp", "yahoo.co.jp", "line.me", "nintendo.co.jp",
"jio.com", "flipkart.com", "indiatimes.com", "irctc.co.in", "tcs.com",
"naver.com", "daum.net", "coupang.com", "shopee.sg", "lazada.sg"
)
Clear-Host
Write-Host "==========================================================================" -ForegroundColor Cyan
Write-Host " Start Windows-Curl DoH Snelheidstest & HA Failover Benchmark" -ForegroundColor Cyan
Write-Host "==========================================================================" -ForegroundColor Cyan
\$Index = 0
foreach (Domain in DomainList) {
# De 'break' zorgt voor een unieke en foutloze regiotelling
Country = switch (Index) {
{ \$_ -lt 20 } { "NL"; break }
{ \$_ -lt 40 } { "DE"; break }
{ \$_ -lt 60 } { "FR"; break }
{ \$_ -lt 80 } { "US"; break }
{ \$_ -lt 100 } { "UK"; break }
Default { "AS"; break }
}
# Cache-busting: Genereer unieke subdomeinen voor live recursie
\(RandomNumber = Get-Random -Minimum 100000 -Maximum 900000\)UniqueDomain = "RandomNumber.{Domain}"
\$TargetUrl = "BaseUrl?name={UniqueDomain}&type=A"
\(Latency = 0\)Status = "TIMEOUT/ERROR"
\$Color = "Red"
try {
\$CurlResponse = & curl.exe -k -s -X GET -H "Accept: application/dns-json" -w "%{time_total}" "TargetUrl" -o "env:TEMP\doh_null.txt"
if (null -ne CurlResponse) {
CleanResponse = CurlResponse.ToString().Trim() -replace ',', '.'
if (\$CleanResponse -match '^[0-9.]+\(') {\)RawTime = [double]\(CleanResponse\)Latency = [Math]::Round(\(RawTime * 1000, 2) } } } catch {\)Latency = 0 }
if (\(Latency -gt 0) {\)Status = "FAST REAL DoH"
\$Color = "Green"
if (Latency -gt ThresholdMs) { Status = "SLOW RECURSION"; Color = "Yellow" }
}
DisplayCount = (Index + 1).ToString().PadLeft(3)
Write-Host " [\(Country] (\)DisplayCount/120) Status - Latency: Latency ms | Target: Domain" -ForegroundColor Color
# Sla uitgebreide data op voor Excel
\$Global:Results += [PSCustomObject]@{
Nummer = \$Index + 1
Regio = \$Country
Domein = \$Domain
Latency_ms = \$Latency
Status = \$Status
Tijdstip = (Get-Date -Format "HH:mm:ss")
}
\$Index++
Start-Sleep -Milliseconds \$DelayMs
}
# --- AUTOMATISCHE EXCEL & GRAFIEK EXPORT ---
Write-Host "`n[Excel] Resultaten verwerken en grafiek genereren..." -ForegroundColor Cyan
if (Get-Module -ListAvailable -Name ImportExcel) {
# Verwijder het oude bestand om overschrijffouten te voorkomen
if (Test-Path $ExcelPad) { Remove-Item $ExcelPad -Force }
# 1. Exporteer de ruwe data naar het eerste tabblad (Platte data voorkomt foutmeldingen)
$Global:Results | Export-Excel -Path $ExcelPad -WorksheetName "Ruwe Data" -AutoSize -FreezeTopRow
# 2. Bereken de schone regio-gemiddelden
$RegioSamenvatting = @()
$Regions = @("NL", "DE", "FR", "US", "UK", "AS")
foreach ($R in $Regions) {
$ValidQueries = $Global:Results | Where-Object { $_.Regio -eq $R -and $_.Latency_ms -gt 0 }
if ($ValidQueries) {
$Avg = [Math]::Round(($ValidQueries | Measure-Object Latency_ms -Average).Average, 2)
$RegioSamenvatting += [PSCustomObject]@{
Regio = $R
Latency_ms = $Avg
}
}
}
# 3. Exporteer de samenvatting en genereer automatisch de staafgrafiek op tabblad 2
# Dit is volledig geoptimaliseerd voor de Nederlandstalige Excel-engine
$RegioSamenvatting | Export-Excel -Path $ExcelPad `
-WorksheetName "Grafiek Samenvatting" `
-AutoSize `
-ColumnChart `
-NoLegend `
-Title "Gemiddelde DoH Latency per Regio (patad.nl)"
Write-Host "[Excel] Succes! Je volledige rapport inclusief werkende grafiek staat klaar:" -ForegroundColor Green
Write-Host "-> \$ExcelPad" -ForegroundColor Yellow
} else {
Write-Host "[Excel] Fout: De 'ImportExcel' module is niet geΓ―nstalleerd." -ForegroundColor Red
}