# Serving an app at your own domain

> A custom domain serves one of an app's environments at a name you own, like app.example.com. `osyrin app domain add` points the name at an environment and prints the two DNS records to publish — a TXT that proves the name is yours and a CNAME that routes it. It serves nothing until a check finds both; then the platform issues its certificate.

<!-- id: zones-custom-domains · area: zones · stability: preview · html: https://osysharp.com/reference/zones/custom-domains/ -->

## Summary        {#summary}

`osyrin app domain add app.example.com --env prod --app billing-acme` answers the two records to publish at your DNS
provider. Once they are there, `osyrin app domain check app.example.com --app billing-acme` reads DNS and says what it
found; the platform also checks on its own and issues the certificate.

## Signature      {#signature}

```console
osyrin app domain list --app <slug> [--server <url>] [--json]
osyrin app domain add <hostname> [--env <slug>] --app <slug> [--server <url>] [--json]
osyrin app domain check <hostname> --app <slug> [--server <url>] [--json]
osyrin app domain primary <hostname> --app <slug> [--server <url>] [--json]
osyrin app domain remove <hostname> [--yes] --app <slug> [--server <url>] [--json]
```

## Description    {#description}

**Two records, then a certificate.** `add` creates the domain unverified and prints:

| record | name | value | what it is for |
|---|---|---|---|
| TXT | `txtName` | `txtValue` | proves the name is yours |
| CNAME | `routeName` | `routeTarget` | routes the name to the platform |

A name that cannot take a CNAME (the apex of a zone, `example.com`) takes an A record to the addresses `list` shows once a
check has read them. Once both records are right the platform asks for a certificate for the name, proved by the name
pointing here — usually a few minutes, at most an hour. No third record is needed for it.

**A check says what it found.** `check` reads DNS now and answers whether the TXT and the route were found, with the
values it read beside the ones it wants. The platform also checks every domain on its own until it verifies, and gives
up after a while; `list` says so.

**It serves one environment.** `--env` names the environment the domain serves; left out, the app's only environment is
used, and with several the command names them and asks. `primary` makes a verified domain the app's main name.

### What changes once its certificate is issued        {#redirect}

**The platform address sends visitors to your domain.** Once the domain the environment is known by — its primary
domain, or its one verified domain — has its certificate, a browser opening the platform address
(`billing-acme.osyrin.app/orders/7`) is redirected to the same page on your domain (`app.example.com/orders/7`), so the
name people bookmark and search engines index is yours. Old links keep working: they arrive at the platform address and
are sent on. Until the certificate is issued both names serve the page, because a redirect to a name with no
certificate would be a security warning.

Only page visits move. A webhook your app receives, an API call from a script or another service, and anything that is
not a browser opening a page are served at the platform address exactly as before, so nothing you registered elsewhere
needs changing. The redirect is temporary (307), so removing the domain later sends no visitor's browser to a name that
no longer serves.

**Its mail is no longer held to a daily number.** An app whose production is served on its own domain sends mail that
links to that domain, not the platform's, so the plan's daily mail limit stops refusing its sends. The platform still
counts them, and its operators are told when an app sends more than 50,000 in a day. An app whose mail was withheld
stays withheld.

**Removing one stops it at once.** `remove` asks first; `--yes` answers for you, and with `--json` (or no terminal) it is
refused without it.

**Deleting the app releases its domains.** Each stops being served, its certificate is let go, and the name is free for
anyone to add. If the app is later restored, its domains are added back unverified, with the same DNS records as before. A
domain whose records are still published serves again within a minute, and you do not need to change anything. A name
that someone else added in the meantime stays with them; the app's Settings page lists each released domain and whether
it came back.

**The platform's own names are not yours to claim**, and a plan may limit how many custom domains an app has — the
refusal says so.

Who may manage an app's domains: an Owner or Admin of its organization, or a member of the app who is not only a
developer.

- `--json` — `list` and `add` print each domain as `{id, hostname, environment, verified, primary, stateLabel, txtName,
  txtValue, routeName, routeTarget, routeAddresses, certificateLabel, checkDetail, lastCheckedAt}` (plus
  `redirectNote`, the sentence saying that the platform address sends visitors here, once it does); `check` prints
  `{txtOk, routeOk, detail}`.

## Examples       {#examples}

```console
osyrin app domain add app.example.com --env prod --app billing-acme --server https://osyrin.app
osyrin app domain check app.example.com --app billing-acme --server https://osyrin.app
osyrin app domain primary app.example.com --app billing-acme --server https://osyrin.app
osyrin app domain list --app billing-acme --server https://osyrin.app --json
```

## See also       {#see-also}

[An organization's environments](https://osysharp.com/reference/zones/environments-of-an-organization/) — the environments a domain can serve.

[Deploying your app to an environment](https://osysharp.com/reference/local/deploying-your-app/) — an app must be deployed to an environment before a domain can point at it.

[What your plan allows, and how much of it is used](https://osysharp.com/reference/zones/plans-and-limits/) — how many custom domains a plan includes.
