
# Set up a custom login domain

A custom domain such as `login.acme.com` serves every end-user surface of your environment: hosted login, the account portal, the Admin Portal, the OIDC and OAuth endpoints, SSO, SAML and SCIM, and the links in emails. Your own application's domain is not hosted by KeenAuth. The `development` environment has no custom domains.

## Before you start

- The name must have at least three labels (a subdomain), because a CNAME cannot be set at the root of a domain.
- It is converted to its IDNA2008 A-label and lowercased. It cannot be `keenauth.app`, `keenauth.com` or a subdomain of them, and it cannot be a public suffix.
- A name is unique across the platform. A released name is held in quarantine for 30 days before another account can add it.
- Free plan: one domain, and the account must have a verified payment card. More than one domain per environment is an Enterprise feature.
- Each account can add at most 10 domains per day (UTC). Rejected attempts count too.

## Step 1: add the domain

Call [Add a custom domain](/docs/api/management/custom-domains/create). The domain starts in `pending_dns` and the response lists the DNS records to publish.

## Step 2: publish two DNS records

| Type | Name | Value |
|---|---|---|
| TXT | `_akp-verify.<hostname>` | `idp-verify=<26-character base32 token>` |
| CNAME | `<hostname>` | `<environment-slug>.<region>.edge.keenauth.app` |

Keep the TXT record in DNS permanently, also after the domain is active. It is the proof of ownership: if the domain changes hands, the previous owner can no longer have the page served.

Both records must be visible. The check runs every 5 minutes for 72 hours, then hourly for 30 days. To restart a stopped check, call [Verify a custom domain](/docs/api/management/custom-domains/verify).

## Step 3: check CAA records

If the domain or any parent domain has a CAA record, it must allow both ACME certificate authorities that KeenAuth uses. Otherwise the domain returns to `pending_dns` with the failure reason `caa_blocked`, and the dashboard shows the exact CAA line to add. If no CAA record exists, nothing is needed.

## Certificate and lifecycle

KeenAuth obtains and renews the TLS certificate for you. A certificate is renewed when less than 30 days of its life remain. If renewal fails for 7 days you get the `custom_domain.certificate_renewal_failed` event and an email. Event delivery is not available yet; it comes with webhooks in Phase 6A.

| Status | Meaning | Next |
|---|---|---|
| `pending_dns` | Added; waiting for the TXT and CNAME records | Both seen: `issuing` |
| `issuing` | Certificate is being issued | Issued: `active`. CAA block: `pending_dns` with `caa_blocked`. Not issued within 72 hours: `pending_dns` with `certificate_failed` |
| `active` | In service | A failed daily check: `degraded` |
| `degraded` | The TXT or CNAME record has been missing for a day or more; the domain still serves | Fixed: `active`. Not fixed for 7 days: `disabled` |
| `disabled` | Out of service; the certificate is deleted and sessions on this host are invalidated | You restart with `:verify`: `pending_dns` |
| `released` | The domain or the environment was deleted | After 30 days of quarantine the name can be added again |

An active domain is checked once every 24 hours. Subscribe to the events `custom_domain.verified`, `custom_domain.activated`, `custom_domain.degraded` and `custom_domain.disabled` to be told when something changes. These events are not available yet; delivery comes with webhooks and the event API in Phase 6A.

## Primary domain and removal

One domain of the environment is primary; email links use it. Change it with [Make a custom domain primary](/docs/api/management/custom-domains/make-primary). When the environment's issuer follows the primary domain, changing the primary domain changes the issuer and is a planned operation scheduled at least 7 days ahead.

A primary domain, or the domain that is the issuer, cannot be deleted (`409 custom_domain_in_use`). Choose another primary domain first. A domain used as a passkey rpId cannot be deleted or disabled either (`409 domain_in_use_by_rp_id`).

## Several domains in one environment

On Enterprise an environment can have several verified domains. Each domain has its own session cookie, so there is no silent SSO between domains. Passkeys across domains depend on the rpId choice and on the related origins list, which holds at most 5 registered-domain labels and only `active` domains of the same environment. Each application can have its own branding on every domain; see [Brand your login pages](/docs/guides/branding).
