Custom Domains ​
This guide covers AYB's current custom-domain lifecycle: admin API setup, DNS ownership verification, certificate provisioning/renewal, route-table behavior, and status transitions.
Source of truth:
internal/server/custom_domains.gointernal/server/custom_domains_handler.gointernal/server/custom_domains_store.gointernal/server/custom_domains_verify.gointernal/server/custom_domains_cert.gointernal/server/custom_domains_health.gointernal/server/custom_domains_route.go- Tests:
custom_domains_handler_test.go,custom_domains_verify_test.go,custom_domains_cert_test.go,custom_domains_health_test.go,custom_domains_route_test.go
Persistence is in _ayb_custom_domains.
Lifecycle prerequisites ​
- Enable the jobs service/scheduler so AYB can register domain verification, route sync, re-verification, and tombstone-cleanup jobs.
- Certificate provisioning, renewal, and health-check jobs also require a wired
CertManager. - Without those background services, the admin API can store domain bindings, but the automated verification/certificate/routing lifecycle described below does not run.
Admin API ​
Admin-token routes:
GET /api/admin/domainsPOST /api/admin/domainsGET /api/admin/domains/{id}DELETE /api/admin/domains/{id}POST /api/admin/domains/{id}/verify
Create domain ​
Request fields:
hostname(required)environment(optional; defaults toproduction)redirectMode(optional; allowed:permanent,temporary)
Example:
curl -X POST http://localhost:8090/api/admin/domains \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"hostname": "app.example.com",
"environment": "production",
"redirectMode": "permanent"
}'Hostname validation enforced by normalizeAndValidateHostname:
- lowercases and trims input
- rejects wildcards (
*) - rejects IPv4/IPv6 literals
- rejects hostnames with ports
- requires at least two DNS labels
- max total length 253, max label length 63
- labels cannot start or end with
- - ASCII letters/digits/hyphens only
DNS verification ​
On create, AYB generates a 64-char hex verification token and returns a computed challenge record:
_ayb-challenge.<hostname> TXT <token>
Example:
_ayb-challenge.app.example.com TXT aabbccdd...Verification worker behavior (domain_dns_verify):
- polls TXT records at
_ayb-challenge.<hostname> - success transitions to
verified - retries while status is
pending_verification - timeout window is 24 hours from first attempt
Retry cadence (verifyRetryDelay):
- attempts 1-20: 30-32 seconds
- attempts 21+: 5m to 5m10s
After timeout, AYB sets verification_failed with lastError.
Manual re-trigger ​
POST /api/admin/domains/{id}/verify:
- enqueues verification when status is
pending_verificationorverification_failed - returns current binding unchanged for
verified,active, and other non-verifiable states - rate limited per domain: 10 attempts/hour
Rate-limit headers on verify endpoint:
X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-ResetRetry-After(on429)
Certificate lifecycle ​
Certificate jobs use certmagic-backed CertManager.
verified+ no error enqueuesdomain_cert_provision- provision success calls
SetDomainCertand transitions toactive - active domain stores:
certRef(cert serial hex)certExpiry
- provision failure keeps status
verifiedand updateslastError
Renewal flow (domain_cert_renew):
- scans active domains with
cert_expiry < now + 30 days - syncs refreshed cert metadata from cert cache
- warns when cert is still within 7 days of expiry
Health and re-verification jobs ​
Health check (domain_health_check):
- runs on active domains with certs
- sets
healthStatustohealthyorunhealthy - also warns when cert expiry is under 7 days
DNS re-verification (domain_reverify):
- runs daily on active domains
- increments
reverifyFailureswhen challenge no longer matches - resets failures when DNS is valid again
- after 3 consecutive failures, transitions to
verification_lapsed
Routing and status transitions ​
Route table includes only:
activeverification_lapsedtombstoned
Middleware behavior:
- unknown host: request passes through
tombstonedhost:421 Misdirected Requestactiveandverification_lapsed: route entry added to request context
Status values (DomainStatus):
pending_verificationverifiedactiveverification_failedverification_lapsedtombstoned
Cleanup windows from store queries/jobs:
verification_lapsedcleanup grace: 7 days (then tombstoned via reverify cleanup path)- tombstone reaping hard delete: 7 days after
tombstoned_at
Schedules ​
Registered cron schedules (UTC):
- route sync: every 5 minutes (
*/5 * * * *) - health check: every 15 minutes (
*/15 * * * *) - cert renew: every 12 hours (
0 */12 * * *) - DNS reverify: daily at 04:00 (
0 4 * * *) - tombstone reap: daily at 03:00 (
0 3 * * *)
Current redirect-mode limitation ​
redirectMode is validated and stored (permanent/temporary) and carried in route entries, but there is no built-in HTTP redirect response path in hostRouteMiddleware yet. Treat it as routing metadata for now.