Gravity-Sync Is Archived: High-Availability DNS Options in 2026

Gravity-Sync is archived and Pi-hole v6 broke most sync tools. Compare nebula-sync + keepalived, router secondary DNS, and EliHole's native clustering.

TL;DR: Gravity-Sync, the de-facto tool for keeping two Pi-holes in sync, was archived in July 2024 and never gained Pi-hole v6 support. In 2026 you have three realistic paths to redundant filtered DNS: Pi-hole plus nebula-sync and keepalived, a plain secondary DNS entry on your router, or a sinkhole with replication built in. EliHole takes the third path: native master-slave clustering with config push and aggregated stats, no external sync tools.

When your only DNS server dies, “the internet is down”

A DNS sinkhole sits in the hot path of every connection your network makes. Each page load, app refresh, and smart-bulb phone-home starts with a DNS query to it. That makes a single sinkhole instance a single point of failure for the entire network: when it goes down, nothing resolves, and to everyone in the house the internet is simply broken.

And it will go down — not because the software is fragile, but because you maintain it. An OS upgrade reboots the host. A Docker image rebuild takes the container offline for two minutes. An SD card dies quietly overnight. During any of those windows, every device on the network loses name resolution at once.

The fix is the same as for any critical service: run two instances. But two ad-blocking DNS servers immediately raise a harder question — how do you keep their blocklists, whitelists, and local DNS records identical, so that both nodes give the same answers? That’s the problem Gravity-Sync used to solve.

What Gravity-Sync did, and where it stands now

Gravity-Sync was a community shell-script project that replicated the DNS configuration of two Pi-hole 5.x instances over SSH: adlists, domain whitelists and blacklists with status and comments, clients, and group assignments. For years it was the standard answer to “how do I run two Pi-holes?” — a cron job copied the gravity database from primary to secondary, and the pair stayed in lockstep.

The repository was archived by its owner on July 26, 2024 and is now read-only. When Pi-hole v6 shipped in early 2025 with configuration moved into pihole.toml and a redesigned API, the issue asking for v6 compatibility was closed as not planned. Gravity-Sync still technically works on Pi-hole 5.x, but on a current Pi-hole it does nothing, and no fixes are coming.

That left the homelab community to reassemble high availability from parts. Three approaches dominate in 2026.

Option A: Pi-hole with a third-party syncer and keepalived

The direct successor to the Gravity-Sync workflow is to keep running Pi-hole and bolt on two extra tools:

  • A config syncer. nebula-sync is the actively maintained option: it synchronizes multiple Pi-hole v6.x instances through the official API, with full or selective sync of adlists, domains, clients, and groups, typically run as a small Docker container on a schedule. orbital-sync filled the same role for Pi-hole v5 via teleporter backups, but it was archived on March 31, 2025 without v6 support — treat it as legacy. (The AdGuard Home world has the equivalent adguardhome-sync, which exists for the same reason: no built-in replication.)
  • A failover mechanism. keepalived runs on both hosts and manages a shared virtual IP (VIP) using VRRP. Clients point at the VIP; if the primary host stops answering, the VIP moves to the secondary within seconds. Techno Tim’s Pi-hole sync guide walks through a typical nebula-sync deployment if you want a worked example.

This stack genuinely works, and thousands of homelabs run it. Be honest about what you’re signing up for, though:

  • Three or four moving parts. Two Pi-holes, a syncer container, and keepalived — each with its own config, logs, and upgrade cycle. The sync tool talks to Pi-hole’s API, so a Pi-hole upgrade can break sync until the tool catches up, which is exactly how the v6 transition stranded orbital-sync users.
  • Sync is scheduled, not event-driven. Changes propagate on the next cron tick, not when you make them. Add a whitelist entry on the primary and the secondary keeps blocking that domain until the next run.
  • No unified statistics. Each Pi-hole keeps its own query log and dashboard. To answer “what did my network block today?” you check two web UIs and add the numbers yourself.

Option B: secondary DNS on the router

The zero-extra-software approach: most routers let you hand out two DNS servers via DHCP. Point DNS 1 at your sinkhole and DNS 2 at a second resolver, and clients have a fallback when the first is down.

The caveat is bigger than it looks: clients do not treat the second entry as “backup only.” DNS client behavior varies by OS, and many systems — Windows in particular, and anything doing its own round-robin — will happily send queries to the secondary even while the primary is healthy. If that secondary is a plain resolver like 1.1.1.1, a meaningful slice of your traffic bypasses filtering entirely, all the time. Ads reappear “randomly,” trackers leak, and the per-client stats on your sinkhole quietly stop reflecting reality.

The only safe version of this option is when both DNS entries point at filtering instances with identical config — which brings you right back to the synchronization problem. Secondary DNS is a useful delivery mechanism for redundancy; it is not a substitute for it.

Option C: a sinkhole with clustering built in

The third approach removes the glue entirely: make replication a feature of the DNS sinkhole itself. EliHole is one of the only self-hosted DNS sinkholes with clustering built into the core: the master pushes configuration to slaves the moment it changes and aggregates their query statistics, with no external sync tools to install or maintain. (Among full DNS servers, Technitium also offers native clustering — it’s a heavier general-purpose DNS server rather than a sinkhole-first tool, but if you’re evaluating that category, it deserves a fair mention.)

Here’s what EliHole’s master-slave clustering does concretely:

  • Push-based config replication. When you change anything on the master, it pushes the new config to every slave. Rapid edits are debounced into a single push within a 3-second window — no cron schedule, no waiting for the next sync run.
  • Synced data: adlists, custom blocklist entries, whitelist, local DNS records, upstream resolvers, and cache TTL. Slaves run gravity (adlist downloads) independently using the synced adlist URLs, so a slave never depends on the master to refresh its blocklists.
  • Aggregated statistics. Slaves push their query stats to the master every 30 seconds, so the master’s dashboard shows the whole cluster — one place to see what your network blocked, regardless of which node answered.
  • Auto-registration and health monitoring. A slave registers itself with the master on startup and pulls the current config immediately. The master tracks every slave’s heartbeat and marks a node offline after 120 seconds of silence.

Setup is three environment variables per node — a shared secret, a role, and (on slaves) the master’s address:

# master .env
INSTANCE_ROLE=master
CLUSTER_API_KEY=<shared-secret>

# slave .env
INSTANCE_ROLE=slave
CLUSTER_API_KEY=<shared-secret>          # identical to master
CLUSTER_MASTER_URL=http://<master-host>:4410
INSTANCE_URL=http://<this-host>:4410     # reachable by master

Restart both containers and the slave logs Registered with master successfully. Cluster status, manual pushes, and node management live in the admin UI at /admin/cluster. All cluster API calls authenticate with the shared key in both directions. The Docker install guide covers the base setup each node starts from, and if you’re coming from a Pi-hole pair, the migration guide imports your existing adlists, blocklists, and local DNS records via teleporter files.

For client-side failover you have the same two choices as before: hand out both node IPs as DNS 1 and DNS 2 — safe here, because both nodes filter identically — or run keepalived for a single VIP. Clustering solves the synchronization and visibility half of HA; the VIP question stays yours either way.

The three approaches side by side

Pi-hole + nebula-sync + keepalivedRouter secondary DNSEliHole native clustering
Components to maintain4 (2× Pi-hole, syncer, keepalived)1–2 instances, router config2+ EliHole nodes
Sync triggerScheduled (cron interval)None — manual or noneOn change, 3s debounce
Unified statsNo — per-instance dashboardsNoYes — slaves report to master every 30s
Unfiltered-leak riskLow (if both filtered)High if secondary is a plain resolverLow — all nodes filter identically
Survives sinkhole upgradesSyncer may lag breaking API changesYesYes — clustering ships with the release
Health visibilitykeepalived logs, manual checksNoneMaster marks nodes offline after 120s
Setup effortModerate–highTrivialLow — 3 env vars per node

Which one should you pick?

  • You already run two Pi-holes and don’t want to migrate: nebula-sync plus keepalived is the proven path. Accept the moving parts and the split dashboards.
  • You just want a quick safety net and accept leaks: router secondary DNS pointing at a public resolver. Know that “backup” DNS gets real traffic even when the primary is healthy.
  • You’re choosing a sinkhole today, or your Gravity-Sync setup just broke: pick a tool where replication is a core feature instead of an ecosystem of scripts. See how EliHole compares to Pi-hole feature by feature in the EliHole vs Pi-hole comparison, or go straight to the Docker install — a second node is the same compose file with three extra environment variables.

The era of SSH-script synchronization ended when Gravity-Sync went read-only. High availability for filtered DNS shouldn’t depend on a third-party cron job keeping pace with someone else’s API changes — it should be a checkbox in the resolver you already run.

Frequently asked questions

Does Gravity-Sync work with Pi-hole v6?
No. The Gravity-Sync repository was archived in July 2024 and the request for Pi-hole v6 compatibility was closed as not planned. Pi-hole v6 moved configuration into pihole.toml and a new API, which Gravity-Sync never supported. For v6 instances, nebula-sync is the actively maintained replacement.
What replaced Gravity-Sync for syncing two Pi-holes?
nebula-sync is the main successor. It synchronizes multiple Pi-hole v6 instances through the official API, with full or selective sync of adlists, domains, clients, and groups. orbital-sync was another popular option, but it was archived in March 2025 and never gained v6 support.
Do I still need keepalived if I use EliHole clustering?
Not necessarily. EliHole clustering keeps configuration and statistics in sync, so you can simply hand clients both instance IPs as DNS 1 and DNS 2 — every node filters identically, so there are no unfiltered leaks. Add keepalived only if you want a single virtual IP that fails over automatically.