Cirrusync: Cloudflare Dynamic DNS Client
A lightweight, auditable Dynamic DNS client for Cloudflare. Automatically discovers public IPv4 and IPv6 addresses, compares them against target DNS zone records, and modifies only out-of-date entries under systemd or automated cron schedules.
Designed for Transparency & Least Privilege
Unlike opaque monolithic DDNS daemons, Cirrusync is built with clean, auditable logic focusing exclusively on Cloudflare DNS state synchronization:
Dynamic DNS State Reconciliation Cycle
Dual-Stack Discovery
Determines the host's true public IPv4 and IPv6 addresses via redundant external endpoints, preventing incorrect LAN address updates.
Scoped API Token Security
Uses a Cloudflare API token with Zone Read and DNS Edit permissions limited to the managed zones. Global API Keys are not supported.
Differential Updates Only
Reads existing Cloudflare records and writes when managed content, TTL, or proxy state differs. Missing records can optionally be created, and retry logic handles rate limits and transient failures.
Multi-Zone & Multi-Record
Synchronizes multiple domain names across different Cloudflare DNS zones in a single execution loop with isolated per-zone error reporting.
systemd Native Service
Includes production systemd service definitions with security hardening (sandboxing, private temporary directories, and non-root execution).
One-Shot Test Mode
Supports manual single-run sync via command line for verification, CI pipelines, or lightweight cron-based task runners.
Configuration Workflow
Use the repository installer to prepare the service and restricted token file, then edit its TOML configuration. The example below manages an existing A record. AAAA records require IPv6 discovery to be enabled and a separate record entry:
# Scoped token: Zone Read + DNS Edit for the target zone
[cloudflare]
api_token_file = "/etc/cirrusync/token"
# Account-owned tokens also require cloudflare.account_id.
# Target DNS records to synchronize
[[records]]
zone = "example.com"
name = "vpn.example.com"
type = "A"
proxied = false
# Run a single synchronization test
cirrusync once --config /etc/cirrusync/config.toml