Nebula-sync - Pi-hole Configuration Synchronization

When running multiple Pi-hole instances in a homelab environment, keeping configurations synchronized becomes crucial for maintaining consistent DNS behavior across your network. Nebula-sync provides an elegant solution for automatically synchronizing Pi-hole configurations between primary and replica instances.

Nebula-sync is a lightweight tool that uses Pi-hole’s API to replicate settings, blocklists, and custom DNS records between multiple Pi-hole deployments. This ensures that changes made to your primary Pi-hole instance are automatically propagated to your backup instances.

Overview

The synchronization setup works as follows:

Primary Pi-hole (192.168.7.250)
           ↓
    Nebula-sync Container
           ↓
Replica Pi-hole (192.168.7.251)

Nebula-sync runs on a scheduled basis and synchronizes specific configuration elements from the primary to replica instances, ensuring consistency without affecting read-only configurations that are managed by Docker environment variables.

Why Nebula-sync?

High Availability: Ensures backup Pi-hole instances have identical configurations, making failover seamless.
Automation: Eliminates manual configuration replication across multiple instances.
Selective Sync: Allows fine-grained control over which settings are synchronized.
API-Based: Uses Pi-hole’s built-in API for reliable configuration management.
Lightweight: Minimal resource usage with configurable scheduling.

Configuration Strategy

Since some Pi-hole settings are managed through Docker environment variables (which are read-only via API), nebula-sync focuses on synchronizing the dynamic configurations:

  • Custom DNS host records
  • CNAME records
  • Blocklists and allowlists
  • Domain lists and group assignments
  • Ad list configurations

Static configurations like upstream DNS servers, interface bindings, and network settings remain managed through individual container configurations.

Docker Compose Setup

docker-compose.yml

---
services:
  nebula-sync:
    image: ghcr.io/lovelaze/nebula-sync:latest
    container_name: nebula-sync
    restart: unless-stopped
    env_file: .env

Environment Configuration

Create .env file with synchronization settings:

# Primary Pi-hole instance (source)
PRIMARY="http://192.168.7.250|"

# Replica Pi-hole instances (destinations)
REPLICAS="http://192.168.7.251|"

# Synchronization behavior
FULL_SYNC=false
RUN_GRAVITY=false
CRON=*/15 * * * *
CLIENT_SKIP_TLS_VERIFICATION=true
TZ=Europe/Berlin

# DNS-specific synchronization
SYNC_CONFIG_DNS=true
# Specify exactly what to sync to avoid read-only key conflicts
SYNC_CONFIG_DNS_INCLUDE=hosts,cnameRecords

# Disable other config sync to avoid conflicts
SYNC_CONFIG_DHCP=false
SYNC_CONFIG_NTP=false
SYNC_CONFIG_RESOLVER=false
SYNC_CONFIG_DATABASE=false
SYNC_CONFIG_MISC=false
SYNC_CONFIG_DEBUG=false

# Gravity database synchronization
SYNC_GRAVITY_DHCP_LEASES=false
SYNC_GRAVITY_GROUP=false
SYNC_GRAVITY_AD_LIST=true
SYNC_GRAVITY_AD_LIST_BY_GROUP=true
SYNC_GRAVITY_DOMAIN_LIST=true
SYNC_GRAVITY_DOMAIN_LIST_BY_GROUP=true
SYNC_GRAVITY_CLIENT=false
SYNC_GRAVITY_CLIENT_BY_GROUP=false

Configuration Parameters

Connection Settings

PRIMARY: The source Pi-hole instance URL and optional API token.

  • Format: "http://ip-address|api-token"
  • Example: "http://192.168.7.250|abc123"

REPLICAS: Comma-separated list of destination Pi-hole instances.

  • Format: "http://ip1|token1,http://ip2|token2"
  • Multiple replicas: "http://192.168.7.251|,http://192.168.7.252|"

Scheduling

CRON: Standard cron expression for sync frequency.

  • Every 15 minutes: */15 * * * *
  • Hourly: 0 * * * *
  • Daily at 2 AM: 0 2 * * *

Sync Scope Control

SYNC_CONFIG_DNS_INCLUDE: Specific DNS settings to synchronize.

  • hosts - Custom DNS A records
  • cnameRecords - CNAME alias records
  • Avoids read-only settings like dns.upstream or dns.interface

SYNC_GRAVITY_AD_LIST: Synchronizes blocklist configurations. SYNC_GRAVITY_DOMAIN_LIST: Synchronizes allowlist configurations.

Deployment

Start the nebula-sync service:

# Deploy the container
docker compose up -d

# Verify it's running
docker compose ps

# Check synchronization logs
docker compose logs -f nebula-sync

Authentication Setup

For production environments, configure API tokens:

  1. Generate API tokens on each Pi-hole instance:
    # Access Pi-hole container
    docker exec -it pihole /bin/bash
       
    # Generate API token
    pihole -a -p
    
  2. Update environment configuration:
     PRIMARY="http://192.168.7.250|your-primary-token"
     REPLICAS="http://192.168.7.251|your-replica-token"
    
  3. Restart the service:
    docker compose down && docker compose up -d
    

Monitoring and Troubleshooting

Log Analysis

Monitor synchronization status:

# View recent sync operations
docker compose logs --tail 50 nebula-sync

# Follow live sync activity
docker compose logs -f nebula-sync

# Check for API errors
docker compose logs nebula-sync | grep -i error

Common Issues

API Authentication Failures:

  • Verify API tokens are correct
  • Ensure Pi-hole instances are accessible
  • Check network connectivity between containers

Read-Only Configuration Errors:

  • These occur when trying to sync Docker environment variables
  • Solution: Use SYNC_CONFIG_DNS_INCLUDE to specify only writable settings

Sync Conflicts:

  • Nebula-sync overwrites replica configurations with primary settings
  • Ensure primary instance contains the desired configuration state

Manual Sync Trigger

Force immediate synchronization:

# Execute one-time sync
docker exec nebula-sync /usr/local/bin/nebula-sync

Integration with Ansible

For automated DNS record management, I use Ansible playbooks to update the primary Pi-hole instance, which then propagates changes via nebula-sync:

# Update primary Pi-hole with Ansible
ansible-playbook -i inventories/hosts playbooks/pihole_local_dns/add-pihole-dns.yml

# Changes automatically sync to replicas within 15 minutes

This creates a workflow where Ansible manages the primary instance, and nebula-sync ensures replicas stay synchronized.

Best Practices

Backup Before Sync: Always backup Pi-hole databases before implementing synchronization.
Test Configuration: Validate sync settings with non-production instances first.
Monitor Logs: Regular log review helps identify sync issues early.
Selective Sync: Only synchronize necessary settings to avoid conflicts.
Network Isolation: Run nebula-sync in the same network context as Pi-hole for optimal connectivity.

Limitations

One-Way Sync: Changes must be made on the primary instance; replica modifications will be overwritten.
Read-Only Settings: Docker environment variables cannot be synchronized via API.
Network Dependencies: Requires reliable network connectivity between instances.
API Rate Limits: High-frequency sync may impact Pi-hole performance.

Learnings

API Limitations: Pi-hole’s API has read-only restrictions on certain configuration keys. Understanding which settings can be modified via API is crucial for avoiding errors.
Sync Frequency: A 15-minute sync interval provides a good balance between responsiveness and system load. More frequent syncing may not be necessary for most homelab environments.
Container Networking: Running nebula-sync in the same Docker network as Pi-hole instances simplifies connectivity and reduces potential network issues.
Configuration Management: Treating the primary Pi-hole as the single source of truth simplifies configuration management and reduces conflicts.

Nebula-sync provides a robust solution for maintaining consistent Pi-hole configurations across multiple instances. With proper configuration and monitoring, it ensures that your DNS infrastructure remains synchronized and highly available without manual intervention.