Repository navigation
Home
One verified DoH upstream for RouterOS, a second DoH endpoint as failover, and a health check that judges answers by content — not by "did an IP come back".
Current: v0.4.3 (2026-11-02); previous tag v0.4.2.
Verified on RouterOS 7.23.1 and 7.24.5 — those are the only two versions
recorded in the repository's verification notes. This wiki is a second copy of
README.md and docs/: when they disagree, the repository wins.
| Page | What it answers |
|---|---|
| How it works — full flowchart | seven Mermaid diagrams: components, install order, query path, monitor algorithm, states, failover in time, diagnosis |
| Getting started | what to upload, in which order, and what will break if you skip a step |
| Configuration | every tunable: endpoints, probes, filtering, bootstrap, CAPsMAN |
| Health monitor | states, failover, rollback, how to test it in five minutes |
| Troubleshooting | the failures seen in the field, including "sites do not open but DNS is fine" |
| Verification | commands with expected output, and the measured results |
| Upgrading from 0.1.0 | what changed and what to re-import |
| Monitoring the fleet in Zabbix | DoH and VPN state from every router, triggers, service tree |
| Changelog | what changed in each release and why it mattered |
Repository: https://github.com/paulmann/mikrotik-doh-failover · Release: https://github.com/paulmann/mikrotik-doh-failover/releases/tag/v0.4.3
-
Default upstream is the standard resolver (
cloudflare-dns.com, 1.1.1.1/1.0.0.1), not Family. Filtering is an explicit opt-in. - Failover is DoH → DoH. Plain DNS is bootstrap only, never a working fallback — measured plain answers are substituted on the path.
-
The check judges answers. Empty,
0.0.0.0and the stub pair8.6.112.0/8.47.69.0are unusable; v1 accepted any valid IP. -
The check does not disturb the resolver. Both endpoints are probed with
/tool fetchagainst their DNS-JSON API. - Failover is verified and reversible — the fallback is probed before the switch, verified after it, and rolled back if it does not resolve.
-
Clock guard — a wrong date makes
verify-doh-certimpossible and is now reported as such. -
Narrow scheduler policy (
read,write,policy,test).
-
Explicit state machine —
HEALTHY → SUSPECT → FALLBACK → RECOVERING → HEALTHY, plusSTUB,DEGRADED,CLOCK_INVALIDandMANUAL-ONLY, logged with UPPERCASE tags for alerting. -
Hysteresis instead of one-shot flapping —
failureThreshold(2) bad runs to leave the primary,recoveryThreshold(2) good runs to come home. - Configurable interval — 60 s default (was a hardcoded 3 m).
-
enforcement=strict— blocks DoT (853) and known public DoH endpoints; the residual risk of client DoH over 443 is documented. -
device-mode=homeis diagnosed —MONITOR_DISABLED_BY_DEVICE_MODE+manual-onlyinstead of "both endpoints unreachable". - "Managed failover" wording — the fallback is a different operator, so answers, filtering and geo may change on a switch.
- Endpoint-driven failover — a failover now triggers on endpoint health as well as on the content check, so a primary that dies while the cache still has answers is detected before the cache TTL expires.
-
Concurrency guard — an overlapping scheduler run is skipped, and the flag
is cleared in
on-error(a crashed run cannot lock the monitor). -
Recovery gated on the primary — the monitor returns to the primary only
when
probeA == "ok", so it cannot flap A↔B. -
Clock range — dates outside
2025..2035are rejected (past and future).
-
In-memory probe — the endpoint probe uses
/tool fetch ... as-value output=user, so it writes no temporary files to/fileand cannot leave stale ones behind. The JSON check accepts both"Status":0and"Status": 0. -
Ordered chain — primary → secondary → tertiary (
dohUrlC, optional) → plain DNS (plainFallback, optional, off by default, loggedPRIVACY DEGRADED). -
New
plainstate — on the plain-DNS last resort; recovers back to the primary once it answers.
-
Hard timeout — every probe fetch carries
duration=5s, so a hung endpoint cannot block the run past the scheduler interval. -
Symmetric tertiary — the tertiary switch now verifies after the switch and
rolls back (like the secondary), and every switch increments
DNSFailoverAttempts. - Plain→DoH climb — recovery from plain now climbs back to any DoH endpoint (not only the primary) when the secondary answers.
A branch site stopped opening CDN pages. The router was healthy: DoH answered
Status:0, a valid A record came back. The address was
8.6.112.0 / 8.47.69.0 — a pair Cloudflare really serves, but with no path
from some providers. Where the path is missing, every name that resolves to the
pair stops working, whichever Cloudflare endpoint you choose.
Details, measurements and the three fixes that actually work: Troubleshooting → "Sites do not open while the resolver looks healthy".
# 1. Upload certs/cloudflare-doh-ca.crt to the router (Files), then:
/import file-name=00-install-ca.rsc
/import file-name=01-configure-doh.rsc
/import file-name=02-security-policies.rsc
/import file-name=05-dns-health-monitor.rsc
# 2. Check
/ip dns cache flush
:put [:resolve example.com] # 104.20.x / 172.66.x = real
# 8.6.112.0 / 8.47.69.0 = stub pair (see Troubleshooting)
/log print where message~"DNS monitor"Wiki for mikrotik-doh-failover 0.4.1 (2026-10-28, RouterOS 7.23.1 / 7.24.5).
The repository is the source of truth: when a page here disagrees with
README.md or
docs/, the
repository wins. Found a wrong statement? Open an issue with the command you ran
and its output — a claim without a measurement is not accepted.
mikrotik-doh-failover — wiki for version 0.4.3
- Getting started
- Configuration
- How it works — full flowchart
- Health monitor
- Verification
- Troubleshooting
- Changelog
- Upgrading from 0.1.0
- Zabbix monitoring
Package: PyPI — mikrotik-doh-failover · Repository: paulmann/mikrotik-doh-failover · README · Releases · Changelog