Skip to content

Optional: keepalived VIP for seamless AdGuard DNS failover (server ↔ Pi) #62

Description

@ulises-c

Summary

Add an optional keepalived + Virtual IP (VIP) layer so LAN DNS fails over
seamlessly between the primary AdGuard (linux-server/adguard) and the secondary
AdGuard on the Pi (linux-pi/adguard).

This is a potential follow-up, not urgent. The current setup already survives a
full main-server outage — the Pi resolver is self-sufficient (public DoH upstreams,
host-networked :53, filtering active, decoupled from Tailscale/the server). The
only rough edge is client steering: the router hands out DNS as
[<server-ip> primary, <pi-ip> secondary], and secondary-DNS failover is not
deterministic — some clients keep hitting the dead primary until a timeout before
falling back. Verdict today: works-but-degraded.

Proposal

Introduce a floating VIP (a spare LAN address outside the DHCP pool, e.g.
<vip>) managed by keepalived/VRRP across both resolver hosts:

  • Primary host = MASTER (higher priority), normally holds the VIP.
  • Pi = BACKUP; claims the VIP within ~1–3s if the master's VRRP heartbeats stop.
  • A vrrp_script health check probes AdGuard on :53 so the VIP also moves when
    AdGuard itself dies while the host stays up (not just on a full crash).
  • Router DHCP then hands out only the VIP as DNS — no primary/secondary racing,
    so failover is invisible to clients.

Keeps the same AdGuard software on both ends, so filtering + split-horizon behavior
is preserved regardless of which node holds the VIP. LAN-only; tailnet HTTPS admin
access via the sidecars is unaffected.

Suggested shape (mirror existing per-service layout)

  • New linux-server/keepalived/ and a Pi counterpart under linux-pi/, each with a
    tracked keepalived.conf template + .env.example (VIP, interface, router id,
    auth pass, priority) and a :53 health-check script; real values in gitignored
    .env, placeholders committed.
  • Document the router change (hand out the VIP as the sole DNS) in the UPS/DNS
    runbooks and docs/.

Acceptance criteria

  • keepalived deployed on both resolver hosts; VIP held by the primary at rest.
  • Stopping AdGuard (or the whole host) on the primary relocates the VIP to the
    Pi within a few seconds; DNS keeps resolving + filtering via the VIP throughout.
  • Recovery: primary reclaims the VIP when it returns; no split-brain (matching
    virtual_router_id, distinct priorities, shared auth).
  • Router DHCP issues the VIP as the sole DNS; no real IPs/hostnames/tailnet
    names in any tracked file.

Notes / caveats

  • Requires both nodes on the same L2 subnet (they are) and VRRP allowed on the LAN
    (multicast 224.0.0.18, IP proto 112 — fine on typical home switches).
  • Overkill for most home LANs; the degraded secondary-DNS path usually self-recovers
    within seconds. This buys zero-hiccup failover, nothing more.
  • Alternative considered: make the Pi the sole DHCP DNS (moves the SPOF to the Pi) —
    rejected in favor of an active/backup VIP.

Context: verified DNS-resilience discussion during the PR #61 live-verification work.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions