Custom Domains & TLS
Every Web Application gets a generated address on the platform the moment it is created. When you are ready to serve traffic on your own domain, you attach one — and prove that it is yours. Once ownership is proved, the platform routes the domain to your app and issues a TLS certificate for it automatically.
Custom domains apply to Web Applications only. Background Workers and Databases are not reachable over HTTP and have no domains to attach.
The default app URL
Each Web Application is assigned a generated hostname — a random subdomain under the platform’s app domain — with HTTPS enabled from the start. You’ll find it on the app’s page in the dashboard and on its Domains tab, under Platform Endpoint.
This address always works, TLS included, and it keeps working after you add custom domains: custom domains are aliases for it, not replacements. That makes the platform endpoint a reliable fallback for health checks and for verifying a deployment while a new domain is still being set up.
The flow, end to end
Attaching a domain takes four steps:
- Add the domain to your app. This creates a claim on the name. Nothing is routed and no certificate is requested yet.
- Create the ownership record — a TXT record at your DNS provider that carries a token unique to your organization and that domain.
- Verify. The platform looks the record up and, when it matches, marks the domain verified.
- Point the traffic record (CNAME, or ALIAS for a root domain) at your app’s platform endpoint. The certificate is issued automatically, and the domain goes live.
Steps 2 and 4 are two separate DNS records, and both are shown to you when you add the domain. You can create them at the same time — the order does not matter.
Add the domain
In the dashboard:
- Open your Web Application (for example the
webapp in theproductionenvironment of theshopproject) and go to the Domains tab. - Under Custom Domains, enter the hostname you want — for example
app.example.com— and click Add Domain. - The domain appears as Not verified, with both DNS records to create listed next to it.
With the CLI:
paas domains add app.example.com -o acme -p shop -e production -a web
paas domains add prints both records:
Added app.example.com to web. It is not served yet — the platform needs proof that you own it.
Create these DNS records at your DNS provider:
TYPE NAME VALUE
TXT _d2o-verify.app.example.com d2o-verify=0123456789abcdef0123456789abcdef
CNAME app.example.com k3j2h1.cx.d2o.io
Adding a domain requires the manage_web_apps permission on the project (organization Owners and Admins have it automatically). See Access Control.
The ownership record
The TXT record is the proof:
| Type | TXT |
| Name | _d2o-verify.<your domain> — for app.example.com, that is _d2o-verify.app.example.com |
| Value | d2o-verify=<token>, exactly as shown |
The token is unique to your organization and that one domain. Never share it: it is what proves the name is yours.
Some DNS providers want the name relative to the zone rather than fully qualified — in the example.com zone, enter _d2o-verify.app instead of _d2o-verify.app.example.com. If in doubt, create it and check with dig TXT _d2o-verify.app.example.com before verifying.
The traffic record
Point the domain itself at your app’s Platform Endpoint hostname, shown on the app’s Domains tab and printed by paas domains add:
- CNAME for a subdomain such as
app.example.com. - ALIAS for a root (apex) domain such as
example.com, where a CNAME is not allowed. Some DNS providers call this an ANAME record. If yours supports neither, serve the app from a subdomain such aswww.example.cominstead.
DNS changes can take anywhere from minutes to hours to propagate, depending on your provider and record TTLs.
Verify
Once the TXT record is live, verify the domain:
paas domains verify app.example.com -o acme -p shop -e production -a web
In the dashboard, click Check now next to the domain on the Domains tab.
If the record has not propagated yet, verification fails and says so — nothing is lost, just try again in a few minutes. The platform also re-checks unverified domains on its own in the background, so a domain you added and then forgot about will verify itself once its record shows up.
Unverified claims are not kept forever: a domain left unverified for 7 days is dropped, and you’ll have to add it again. That only affects unverified claims — a verified domain stays until you remove it. An organization can have at most 25 unverified domains at a time, and 50 custom domains in all; verify or remove one before adding more.
Statuses
Each domain is in one of four states. The Domains tab shows it as a badge, paas domains list prints it in words, and --json returns the raw value:
| Status | Badge | CLI | Meaning |
|---|---|---|---|
unverified | Not verified | not verified | No proof yet, and the platform has not looked for the record. The domain is not routed and has no certificate. |
verifying | Verified, setting up | verified, setting up | Ownership is proved and the certificate is being set up. It is issued automatically once the traffic record resolves. |
active | Active | active | The certificate is ready and the domain is serving traffic. |
error | Needs attention | error | The last ownership check failed (record missing, unreadable, or the name is already in use). The reason is shown next to the domain. |
A claim you have added but not yet proved usually reads error, not unverified: the platform re-checks unverified domains in the background, and once it has looked for a record that is not there yet, it records “We have not seen the verification record for <domain> yet.” against the claim. That is expected — create the TXT record and verify.
paas domains list -o acme -p shop -e production -a web
DOMAIN STATUS ADDED
app.example.com verified, setting up 2026-09-20
shop.example.com error 2026-09-22
shop.example.com: We have not seen the verification record for shop.example.com yet.
A domain marked "not verified" is not served: create its ownership record, then
run `paas domains verify <domain>`. "Verified, setting up" means the certificate
is still on its way.
Every hostname is verified on its own
Proving example.com does not cover www.example.com, api.example.com, or any other subdomain. Each hostname you want to serve is added and verified separately, with its own TXT record. There is no wildcard support.
When a domain is already in use
A domain can be attached to only one app in your organization; adding it to a second app fails with “domain is already added to one of your apps” — remove it from the other app first.
Adding a domain that another organization has already verified fails:
That domain is already in use on the platform.
In the dashboard the same rejection offers a Release it button, which runs the release below for you. The platform will not say who holds it, and it will never hand a verified domain over automatically — not even to an organization that later proves ownership. This is deliberate: it is what stops someone from taking a live domain away from the app that is serving it.
Several organizations can each hold an unverified claim on the same name at once. Only the first to verify it gets it; the others stop being checked, show error with the same “already in use” message, and expire.
Releasing a domain you own
If a domain of yours is being served by an organization that should no longer have it — an old team account, a contractor’s organization, a project that moved — you can free it by proving you own the name:
paas domains release example.com -o acme
The platform asks for the same kind of TXT record, using your organization’s own token for that name. Create it, run the command again, and on success:
- the domain is detached from the app that was serving it, and stops routing there straight away;
- both organizations get an entry in their audit log;
- the owners of the organization that held it are emailed.
Releasing a domain does not attach it to your app. Once it is free, add it and verify it the normal way:
paas domains add example.com -o acme -p shop -e production -a web
paas domains verify example.com -o acme -p shop -e production -a web
If nobody is serving the domain, there is nothing to release — just add it.
In the dashboard you do not need the CLI for this: whenever a domain is refused because another organization holds it — when you add it, and when you choose Check now on a claim that lost the race — the Domains tab offers a Release it button that runs the same release and tells you which record to add if it is missing. Once it succeeds the name is yours to verify: released from the add form, the domain is added for you; released from a claim you already hold, choose Check now again.
Releasing needs organization Owner or Admin — see Access Control. Adding and verifying the freed domain afterwards needs manage_web_apps on the project, as usual.
Automatic certificate issuance
Once a domain is verified and its traffic record resolves to the platform, a TLS certificate is requested and issued automatically. There is nothing to upload and no renewal to manage.
Both halves are needed: the certificate authority checks the domain over the public internet, so it cannot be issued until the CNAME or ALIAS record is in place.
Remove a domain
On the Domains tab, click Remove next to the domain and confirm — or, with the CLI:
paas domains remove app.example.com -o acme -p shop -e production -a web
This detaches the domain from the app and removes its certificate and routing — requests to that hostname will no longer reach your app, and the name becomes available for anyone to claim again. The app’s platform endpoint is unaffected.
Removing a domain does not touch your DNS records; clean those up at your DNS provider separately.
Domains added before verification existed
Domains that were already attached when verification was introduced are treated as verified and keep serving traffic exactly as before — they read verifying / Verified, setting up until their certificate is ready, like any other proved domain. Their ownership record is still shown in the dashboard and the CLI, so you can create it at your leisure — worth doing, because it is what lets you re-verify the name later if it is ever released.
Troubleshooting
Verification keeps failing. Check the record with dig TXT _d2o-verify.<your domain> (or your provider’s DNS lookup tool). The answer must contain d2o-verify=<token> exactly as shown for that domain. The most common causes are a record created in the wrong zone, a provider that appended the zone name twice (_d2o-verify.app.example.com.example.com), and a TTL that has not expired yet.
Verified, but the domain does not load. The traffic record is the other half. Confirm dig app.example.com returns the app’s platform endpoint, then give issuance a few minutes. Until the certificate is ready the domain reads Verified, setting up (verifying in --json).
The status says error. The reason is shown next to the domain in the dashboard and under the table in paas domains list. Fix what it names and verify again.
Reserved names. Some names cannot be attached — platform-managed domains and a handful of names the operator has set aside. Adding one is rejected with a message saying so; use a domain you control.