Guides
Custom-domain setup wizard
Run Authio's hosted sign-in on your customer's hostname (auth.acme.com), with TLS auto-issued, and magic-link emails sent from their domain.
Custom domains lift Authio off the platform-default hostname. Customers see auth.acme.com instead of auth.authio.com; magic-link emails arrive from noreply@acme.com; and the panel is fully branded. This guide walks through the wizard in the dashboard, then the DNS work at the customer's registrar, then the verify-cert-activate sequence.
Concept-level background and the data model live at Custom domains, branded email, and redirect URIs. This page is the operator walkthrough.
1. Add the hostname in the Authio dashboard
Sign in as an owner or admin of the project, then open dashboard / Settings / Custom domains and click Add domain. Enter the hostname your users will land on — typically auth.acme.com.
Authio writes a row to custom_domains with status = pending and cert_status = not_attempted. The hostname is globally unique across all projects — adding a host another project already owns returns 409 domain_in_use.
2. Copy the DNS records Authio surfaces
Once the row is created, the dashboard renders the records the customer needs to publish at their DNS provider:
- Routing CNAME — points the customer's hostname at Authio's Cloudflare-for-SaaS fallback origin. Example:
auth.acme.com CNAME custom.authio.com. Always required. - Ownership TXT — proves the customer controls the domain (catches typo-squatting). Example:
_authio-verify.auth.acme.com TXT "authio-verify=acme-9f3b...4421". Always required. - SSL DCV records — domain control validation records Cloudflare uses to issue the Let's Encrypt cert. Sometimes a TXT, sometimes a CNAME under
_acme-challenge.…. Shown only when the platform Cloudflare zone has SSL for SaaS enabled. - CAA record (sometimes) — required only if the customer's root domain already publishes a CAA record that doesn't list Let's Encrypt (
letsencrypt.org). If so, the dashboard will surface a CAA addendum:acme.com CAA 0 issue "letsencrypt.org".
Each row has a one-click copy affordance next to both the host and the value. Hand these to whoever runs DNS at the customer.
3. Add the records at the customer's registrar
DNS UX varies by provider; the records themselves are portable.
Cloudflare
DNS → Records → Add record. Crucial: the routing CNAME must have the orange-cloud proxy disabled (DNS-only). If the proxy is on, Cloudflare for SaaS will see a Cloudflare IP for the routing CNAME and reject validation.
Route 53
Hosted zones → acme.com → Create record. Choose Simple routing, record type CNAME (or TXT), paste the value Authio gave you. Route 53's default TTL of 300 seconds is fine.
GoDaddy / Namecheap / others
Look for “DNS Management” or “Advanced DNS.” The pattern is the same: hostname + type + value. Some registrars suffix the apex domain to host names; if so, the host field for auth.acme.com is just auth.
4. Click Verify in the dashboard
Back in Settings / Custom domains, click Verify now on the row. Authio runs three concurrent checks:
- CNAME lookup against the customer's hostname. The dashboard's status moves to
verifiedonce the CNAME resolves tocustom.authio.com(or the Cloudflare for SaaS equivalent in your region). - TXT lookup on
_authio-verify.…. The token from the row must be the exact TXT value. - Cloudflare for SaaS pre-check. If SSL for SaaS is enabled, Authio registers the hostname with Cloudflare and starts the cert flow; the row moves to
cert_status = cert-pending.
Status transitions you'll see, in order:
pending→ the row exists, DNS records surfaced, but the customer hasn't published them yet (or hasn't clicked Verify).verified→ CNAME + TXT both passed. Routing works; if SSL-for-SaaS is off, this is also the terminal state.cert-pending→ Cloudflare is issuing the cert. Typically 1–5 minutes; rarely up to 15.active→ cert is live; the customer's users can sign in athttps://auth.acme.com/….
pending or cert-pending, so the dashboard updates without anyone clicking Verify again once DNS propagates.5. What changes once the domain is active
From the cutover moment, every reference to the customer's sign-in surface points at their domain:
- Hosted-UI is served from
https://auth.acme.com/login, etc. - OAuth redirect URIs and callbacks are emitted under
auth.acme.com. - Magic-link email URLs contain Acme's domain — users never see
authio.comin the URL they click. - Webhooks the customer receives still come from Authio (we don't spoof your domain on outbound traffic), but the embedded sign-in URL inside the payload uses the custom-domain hostname.
- WebAuthn passkeys are scoped to
auth.acme.comvia RPID — see WebAuthn RPID for custom domains before the customer enrolls passkeys here.
Optional: enable branded email
From the domain detail page, click Enable branded email with a from-address like noreply@acme.com. Authio mints a per-customer SES identity and surfaces three DKIM CNAMEs to publish. Click Verify DKIM after publication. Once SES marks DKIM SUCCESS (usually a few minutes to ~24 hours), sign-in emails go out from Acme's domain immediately. See the branded-email setup guide for full details.
Troubleshooting
My CNAME isn't propagating
Test from a few independent resolvers:
# Public resolvers
dig +short CNAME auth.acme.com @1.1.1.1
dig +short CNAME auth.acme.com @8.8.8.8
# Authoritative resolver for the customer's zone
dig +trace CNAME auth.acme.comThe expected answer is custom.authio.com.followed by Cloudflare's IPs. If different resolvers return different answers, the customer's DNS is in propagation; wait one TTL (often 300 s) and click Verify again.
Common gotchas:
- On Cloudflare, the orange-cloud proxy was left on. Toggle to DNS-only and re-verify.
- The customer published
www.auth.acme.cominstead ofauth.acme.com. Compare host fields exactly. - The customer has an existing record at the same hostname (an old
Arecord from a prior auth provider). DNS only allows one CNAME per host — delete the old record first.
The cert has been pending for >15 minutes
Three causes, in order of frequency:
- CAA blocks Let's Encrypt. Run
dig CAA acme.com. If the response lists any issuer that isn'tletsencrypt.organd no explicit allow for it, ask the customer to addacme.com CAA 0 issue "letsencrypt.org"and re-verify. - DCV records missing. If SSL for SaaS asked for an
_acme-challenge.auth.acme.comCNAME and the customer skipped it, the cert won't issue. Check the dashboard row — the missing-records badge surfaces this. - Cloudflare for SaaS not enabled on the platform zone. The dashboard surfaces “CF SaaS pending” with
cert_status = not_attempted. DNS verification still works (status reachesverified), but the cert never starts. Contact Authio support to flip on SSL for SaaS, then re-verify.
I get “Hostname owned by another tenant”
That hostname already has a custom_domains row in another project. Either the same customer registered it under a different Authio project (route them to the right one) or a previous test left the row behind (Authio support can release it via the admin app).
WebAuthn passkeys stopped working after I added the domain
Passkeys are RPID-bound to the origin they were enrolled at. Users who enrolled on auth.authio.com can't reuse them on auth.acme.com — different RPID, by spec. The hosted-UI handles this by re-prompting for enrollment on the new domain; the user signs in once via magic-link, then enrolls a new passkey. Plan the cutover accordingly — see WebAuthn RPID for custom domains.
Changing or removing a custom domain
Custom domains aren't a permanent commitment, but moving them has user-visible consequences. The supported flow:
Add the new domain alongside the old one
Custom-domain rows are independent. You can run auth.acme.com and auth-v2.acme.com in parallel for as long as you need. New sign-ins arriving at the old domain still work until you remove its row.
Migrate magic-link URLs over time
Authio renders magic-link URLs against the project's default custom domain (the first active row). To flip the default, set is_default = true on the new row via the dashboard (Settings → Custom domains → Set as default). The old domain still resolves; new emails point at the new domain.
Plan a passkey re-enrollment window
This is the only non-trivial step. Tell affected users in advance that they'll be asked to re-enroll. The hosted- UI's “Set up sign-in” flow handles the actual enrollment; you just need to set expectations.
Decommission the old domain
Once the new domain has carried the bulk of sign-in traffic for a billing cycle and passkeys have re-enrolled, the operator can delete the old custom_domains row from the dashboard. Authio releases the Cloudflare-for-SaaS hostname, the cert is decommissioned, and a final custom_domain.removed audit event lands.
auth.authio.com. If the customer's app hard-codes the custom hostname (rather than reading from the Authio config endpoint), those sign-ins will fail until the customer redeploys. Always migrate to the new domain first; delete second.