Troubleshooting
Common Domains failures, why they happen, and where to check.
A domain sits in pending_dns for weeks
Symptom: The domain never leaves pending_dns, no matter how long you wait.
Cause: The A record doesn't point at the server, or it resolves to an internal/blocked address — the SSRF guard treats that identically to "not pointing here yet" and refuses to provision against it.
Check: provisioning_error on the domain row, and check_dns() in
core/domain_provision.py.
DNS looks right but verify never turns green
Symptom: Your DNS host confirms the CNAME/A record is correct, but /verify still returns
pending_dns.
Cause: For an external domain (not on our Cloudflare zone), the CNAME/A match alone
isn't enough — the TXT ownership token at _tease-challenge.<host> is missing or stale, so
domain control was never actually proven.
Check: connect_service.verify_domain → check_txt_token
(core/domains/connect_service.py, core/domains/dns_verify.py) — the issue comes back with
class: "dcv_missing".
409 on delete or rename (domain carries public addresses / has QR codes)
Symptom: Deleting or renaming an address fails with a 409 even though it looks unused.
Cause: The domain is still a live carrier — either another channel's public address is an alias pointing through it, or QR codes have been printed referencing it.
Check: domain_safety.assert_domain_host_mutable
(api/admin/domain_safety.py).
409 domain_busy on an ordinary edit
Symptom: A routine edit to a domain fails with 409 domain_busy.
Cause: A concurrent operation already holds the advisory lock on this domain (or a domain in the same rotation chain). The lock waits up to 5 seconds before giving up — this is a real in-flight conflict, not a stuck state.
Check: lock_domain_mutations / LOCK_WAIT_SECONDS
(core/domain_mutation_lock.py). If you just added the domain, a certificate may still be
issuing (up to ~2 minutes) — that alone can trigger this.
Rotation refuses to start
Symptom: Moving to a reserve domain fails immediately with 409 reserve_not_ready.
Cause: Preflight found a blocker on the reserve — wrong ownership scope, objects already on it, or it isn't TLS-ready yet.
Check: preflight_domain_rotation / _assert_rotation_ready
(core/domain_rotation.py) — see Move off a burned
domain for the full checklist.
A domain row is stuck "provisioning" forever
Symptom: The domain shows provisioning indefinitely, with no error and no progress.
Cause: The backend crashed mid-call to the provisioning helper. The background poller only revives a row like this after 2× the helper's own timeout, to avoid racing a helper call that's still genuinely running.
Check: should_retry_pending (core/domain_provision.py).
What's next
Still stuck? See API reference gaps in Domains for endpoints not covered above, or Domains for the full task list.