A configurable Node.js service that synchronizes Proxmox guests (LXC + VMs) into AdGuard Home DNS rewrites, with optional support for Persistent Clients.
Designed to be environment-agnostic: configure via .env, run in Docker, and let it continuously reconcile DNS records and client identities.
- Overview
- Features
- Architecture
- Requirements
- Installation
- Configuration (.env)
- How Discovery Works
- Naming & Overrides
- Reconciliation Model
- Persistent Clients Sync (Optional)
- State Management
- Running the Service
- Testing DNS
- Proxmox API Setup
- Guest Agent Guidance (VMs)
- AdGuard Home Integration
- Troubleshooting
- Security Notes
- Recommended Defaults
- Limitations
- License
The service:
- Reads Proxmox inventory via API
- Discovers each guest's IP and desired DNS name
- Syncs entries into AdGuard Home DNS rewrites
- (Optional) Syncs guests into AdGuard Persistent Clients
- Repeats continuously on a configurable interval
Example result:
lxc-pulse.internal -> 172.20.0.3
edge-vm.internal -> 172.20.0.2
devbox-vm.internal -> 172.20.20.10
- Supports LXC and QEMU/KVM VMs
- Works with static and DHCP/dynamic (VM) setups
- Config-driven behavior via
.env - Multiple discovery strategies with configurable order
- Metadata overrides via guest description
- Safe reconciliation (add/update/delete only managed records)
- Dockerized deployment
- Continuous sync loop with healthcheck
- Optional Persistent Clients sync
Proxmox API
↓
Sync Service (Node.js)
↓
AdGuard Home API
↓
DNS Rewrites + Persistent Clients (optional)
- Proxmox VE with API access
- AdGuard Home
- Docker + Docker Compose plugin
- Network access from the container to Proxmox and AdGuard
- (Recommended) QEMU Guest Agent for VMs
git clone https://github.com/<your-username>/proxmox-adguard-sync.git
cd proxmox-adguard-synccp .env.example .env
nano .envSet at minimum:
PROXMOX_BASE_URL=...
PROXMOX_TOKEN_ID=...
PROXMOX_TOKEN_SECRET=...
ADGUARD_BASE_URL=...
ADGUARD_USERNAME=...
ADGUARD_PASSWORD=...docker compose up -d --buildView logs:
docker compose logs -fSYNC_INTERVAL_SECONDS=60
LOG_LEVEL=info
TZ=Europe/CopenhagenLOG_JSON=false Example:
{
"ts": "2026-04-07T10:00:00Z",
"level": "info",
"service": "proxmox-adguard-sync",
"msg": "Sync completed",
"meta": { "adds": 5 }
}DNS_SUFFIX=internalSYNC_PERSISTENT_CLIENTS=false
PERSISTENT_CLIENTS_NAME_MODE=guest
PERSISTENT_CLIENTS_TAG=proxmox-adguard-syncFILTER_INCLUDE_TYPES=qemu,lxc
FILTER_REQUIRE_RUNNING=false
FILTER_INCLUDE_TAGS=
FILTER_EXCLUDE_TAGS=
FILTER_INCLUDE_NAMES=
FILTER_EXCLUDE_NAMES=DISCOVERY_VM_ORDER=guest-agent,description,cloudinit
DISCOVERY_LXC_ORDER=config,descriptionDESCRIPTION_IP_KEYS=dns_ip,ip
DESCRIPTION_NAME_KEYS=dns_name,namePROXMOX_BASE_URL=https://your-proxmox:8006/api2/json
PROXMOX_TOKEN_ID=...
PROXMOX_TOKEN_SECRET=...
PROXMOX_VERIFY_TLS=false
NODE_TLS_REJECT_UNAUTHORIZED=0ADGUARD_BASE_URL=http://127.0.0.1:3000
ADGUARD_USERNAME=admin
ADGUARD_PASSWORD=...STATE_FILE=/app/data/state.json- QEMU Guest Agent (best for DHCP)
- Description metadata
- Cloud-init config
- LXC config (
net0,net1, etc.) - Description metadata
Default behavior:
<proxmox-name>.<DNS_SUFFIX>
Examples:
edge-vm.internal
lxc-pulse.internal
dns_name=app
dns_ip=10.0.0.10
Result:
app.internal -> 10.0.0.10
Each cycle:
-
Fetch guests from Proxmox
-
Apply filters
-
Resolve IP + name
-
Compare with:
- DNS rewrites
- (Optional) Persistent Clients
-
Apply changes
| Action | Behavior |
|---|---|
| Add | Missing entry |
| Update | IP changed |
| Delete | Stale managed entry |
When enabled, the service will also manage AdGuard Persistent Clients.
- Adds each guest as a client using its IP
- Updates client name/IP when changed
- Removes stale clients when guests are removed
- Skips existing unmanaged clients (safe by default)
| Mode | Result |
|---|---|
guest |
devbox-vm |
hostname |
devbox |
fqdn |
devbox.internal |
- Uses IP as identifier
- Tracks ownership via state file
- Does not override manually created clients
- Adds optional tag (
proxmox-adguard-syncby default)
File:
data/state.json
Used to:
- Track managed entries
- Track managed persistent clients
- Avoid deleting manual DNS entries
- Ensure safe reconciliation
docker compose up -d --buildLogs:
docker compose logs -fHealthcheck:
node src/index.js --healthchecknslookup edge-vm.internal 127.0.0.1
nslookup lxc-pulse.internal 127.0.0.1Create:
- User:
dns-sync@pve - API Token
Create custom role with:
Sys.AuditVM.AuditVM.Monitor
Create custom role with:
Sys.AuditVM.Audit(deprecated — will be removed in a future release)VM.GuestAgent.Audit
VM.Monitorwas removed in PVE 9 and replaced with more granular permissions.
Enable in Proxmox:
VM → Options → QEMU Guest Agent → Enabled
Inside VM:
sudo apt install -y qemu-guest-agent
sudo systemctl enable --now qemu-guest-agentUses API endpoints:
/control/rewrite/list/control/rewrite/add/control/rewrite/delete/control/clients(Persistent Clients)/control/clients/add/control/clients/update/control/clients/delete
- Check TLS settings
- Verify connectivity
VM.Monitor missing
Fix: ensure role includes VM.Monitor
VM.GuestAgent.Audit missing
Fix: ensure role includes:
Sys.AuditVM.AuditVM.GuestAgent.Audit
- Guest agent missing
- No metadata fallback
- Using DHCP without metadata
- Use API tokens (not passwords)
- Limit privileges
- Protect
.env - Prefer trusted TLS over disabling verification
SYNC_INTERVAL_SECONDS=60
DNS_SUFFIX=internal
FILTER_INCLUDE_TYPES=qemu,lxc
FILTER_REQUIRE_RUNNING=true
DISCOVERY_VM_ORDER=guest-agent,description,cloudinit
DISCOVERY_LXC_ORDER=config,description- DHCP IP discovery for LXCs is not universally available
- Requires guest agent for reliable dynamic VM detection
THE BEER-WARE LICENSE (Revision 42)
As long as you retain this notice, you can do whatever you want with this stuff. If we meet someday, and you think this stuff is worth it, you can buy me a beer in return.
-ka0s