# Custom domains

Every web service you expose gets a default HTTPS URL from its first deployment. You can also point your own domain at it — delivered over HTTPS with a certificate Koo issues and renews for you.

## Your app's default URL

When you expose a web service, Koo's hosting layer assigns it a public URL automatically. There is nothing to configure: HTTPS is included, with the certificate managed for you.

Each app exposes one primary web service at its default URL. Other services in the app — including [Postgres](/docs/data/postgres) and [Redis](/docs/data/redis) — stay internal and are never publicly routable. See [Apps and services](/docs/apps/apps-and-services) for how services fit together.

You can find the URL on the service in the console, or read it from the `liveUrl` field on the [Service object](/api/objects/service). Public [app pages](/docs/showcase/app-pages) link to it as your app's live destination.

## Add a custom domain

A custom domain is a hostname you attach to an exposed web service. The service keeps its default URL — a custom domain adds a hostname, it doesn't replace one.

Domains are declarative on the service, and the hostname is its own identity: you manage the full set of hostnames, and there are no domain IDs to track. The service must already be deployed and exposed to route a custom domain to it.

**In the console.** Open the service's drawer, go to **Networking → Public domains**, and add the hostname — for example `app.example.com`. If the service isn't exposed yet, expose it first under **Settings → Public networking** and deploy.

**Over the API.** Write the full desired set of hostnames to the service through a [service update](/api/objects/service), as a `domains` array of strings:

```json
{
  "domains": ["app.example.com", "www.example.com"]
}
```

The array is the complete desired state: a hostname you leave out is removed. Read each hostname back on the service's read-only `domains` array, where every entry is enriched with the DNS records to set and its live status.

## Set the DNS records

As soon as you add a hostname, Koo returns the exact DNS records to create — an ownership record and a routing record. The console shows each record's name and value with copy buttons; over the API they come back on the service's `domains[].dnsRecords`. Don't guess the values — set exactly what Koo returns, at your DNS provider.

Routing is always a `CNAME`, for both subdomains and apex domains. Because the DNS standard doesn't allow a `CNAME` at the zone root, point an apex domain (like `example.com`) at Koo's target using your DNS provider's CNAME flattening — often labelled ALIAS or ANAME. You never configure NS delegation, and Koo never asks you to.

## Verification, routing, and TLS

Once the records are set, Koo takes it from there — you don't trigger anything. A domain walks a simple lifecycle, readable from its `status` on the service:

- 
- 
- 
-

While a domain is `pending` or `error`, Koo re-checks it automatically about once a minute, so you just set the records and wait — there is no verify button to press. A domain is never marked `active` until DNS and the certificate are both real, so the status you see is the truth.

## Remove a domain

Remove a hostname in the console, or leave it out of the service's `domains` array on the next update. Traffic on that hostname stops routing right away; the service's default URL and any other custom domains keep working. To restore it, add it again and re-set the DNS records.

## Plan requirements

Custom domains are a paid-plan feature. The [Free plan](/docs/get-started/free-tier) doesn't include them, and each paid plan includes a set number — see the plan comparison in [Free tier & pricing](/docs/get-started/free-tier).

> **Note:**
>
> When you reach your plan's custom-domain limit, the console blocks adding another and points you to the plan that raises it, and the API returns a quota error naming the tier to upgrade to. Plan changes are handled in [Billing](/docs/account/billing).
