# Serve a bucket on your own domain

> Link a domain you control, such as cdn.example.com, to a bucket, prove it with DNS records, and serve the bucket's objects there over HTTPS.

Source: https://www.coritan.com/docs/object-storage/custom-domains/

In the dashboard:

- /dashboard/storage/…/buckets/…/public: https://www.coritan.com/dashboard/storage

A custom domain serves a bucket's objects at an address of your own, such as `https://cdn.example.com/images/logo.png`. Visitors read the bucket there as they do on its [public URL](/docs/object-storage/public-access/), with the same cache, and we issue the domain's certificate. A bucket can have up to 10 custom domains, and each one serves even while the bucket's public URL is off.

## Before you begin

- Everything in [Serve a bucket publicly](/docs/object-storage/public-access/#before-you-begin): an `active` service, the Admin or Technical role, and a payment method, credit or a paid invoice on the account.
- A domain you control, and access to its DNS records. Link a subdomain such as `cdn.example.com` when you can: the top of a zone (`example.com`) cannot hold a `CNAME` record.
- Nothing else on Coritan may use the hostname. A website, proxy or deployment that answers at it has to let go of it first.

## Link a domain

1. Open the service, select the **Buckets** tab, then select the bucket.
2. Select the **Public access** tab.
3. On the **Custom domains** card, select **Link domain…**.
4. In **Domain**, type the hostname, such as `cdn.example.com`, without `https://` or a path.
5. Select **Link domain**.

What happens next depends on where the domain's DNS is hosted.

### When the domain's DNS is on Coritan in this account

If the hostname is in a DNS zone that this account holds on Coritan ([How DNS hosting works](/docs/websites/dns/)), we add its `CNAME` record to the zone and the domain is verified at once. The message says `cdn.example.com linked and verified.` Nothing else is needed.

If the hostname already has records in that zone, or it is the top of the zone, we leave the zone as it is. The domain is still verified, and its row shows **Point the domain at the bucket** with what to change, such as `cdn.example.com already has DNS records. Remove them, or point it at 6f1c2a9e8b3d4f70a5e1c9d2b7f30a48.pub.example.net yourself.` Change the records in [the zone](/docs/websites/dns/manage-dns-records/) and the domain starts to serve.

A zone that belongs to an organization, or to another account, does not prove the domain. Its domains follow the steps below.

### When the domain's DNS is hosted elsewhere

The domain is linked with **Not verified** and **Waiting for its DNS records**, and its row lists two records to publish where its DNS is hosted:

| Type | Name | Value |
| --- | --- | --- |
| `TXT` | `_coritan-storage.cdn.example.com` | The domain's token, such as `5b0e8f6c2a9d4e17b3c6a8d0f1e2b4c7` |
| `CNAME` | `cdn.example.com` | The bucket's public host, such as `6f1c2a9e8b3d4f70a5e1c9d2b7f30a48.pub.example.net` |

The `TXT` record proves the domain is yours, and the `CNAME` record sends its visitors to us. Copy each name and value from the table: every bucket has its own host and every domain its own token.

1. Publish both records with your DNS host.
2. Back on the **Custom domains** card, select **Check domain** on the domain's row.

DNS changes can take a few minutes to appear. When a record is not there yet, the domain's status stays **Not verified** and its row says what is missing, such as `No TXT record at _coritan-storage.cdn.example.com holds the token yet.` Fix the record and select **Check domain** again.

At the top of a zone, where a `CNAME` is not allowed, use your DNS host's `ALIAS` or `ANAME` record, or its CNAME flattening, with the same target. We accept address records that match the target's.

## Result

The domain's status becomes **Verified** and the message says `cdn.example.com verified.` We then request its certificate. The line under the domain says **Certificate on its way** until it is issued, then **Certificate issued**. Requests over `http://` are redirected to `https://`.

Visitors can then read any object at `https://cdn.example.com/<key>`. Select the domain's name to open it in a new tab. [What visitors can do](/docs/object-storage/public-access/#what-visitors-can-do) applies, with two differences: a custom domain has no per-visitor request limit, and search engines may index it.

Leave both records in place. The `CNAME` keeps sending visitors to the bucket.

## Unlink a domain

1. On the **Custom domains** card, open the domain's menu and select **Unlink domain…**.
2. Select **Unlink domain**.

The domain stops serving the bucket at once, and its cached copies are purged. A `CNAME` record we added to your Coritan zone for it is removed. Records you published with another DNS host stay there until you remove them.

## Troubleshooting

`No TXT record at _coritan-storage.cdn.example.com holds the token yet.`
: Publish the `TXT` record with the exact name and value from the domain's row, wait a few minutes, then select **Check domain** again. Some DNS hosts add the zone's name to what you type, so enter only `_coritan-storage.cdn` there.

`cdn.example.com does not point at 6f1c2a9e8b3d4f70a5e1c9d2b7f30a48.pub.example.net yet. Add a CNAME record for it.`
: The proof is in place and the hostname does not reach us yet. Add the `CNAME` record, or at the top of a zone an `ALIAS` to the same target, and check again.

`That hostname is in use on the platform`
: Another bucket, or another service on Coritan, serves that hostname. Remove it there first. A hostname that another bucket linked and never verified is released after 72 hours.

`That hostname belongs to the platform. Use a domain of your own.`
: Coritan's own domains cannot be linked. Use a domain you control.

`A bucket can have 10 custom domains. Remove one first.`
: Unlink a domain you no longer use. **Link domain…** is greyed out while the bucket has 10.

`Enter the domain alone, without https:// or a path.`, `Enter one hostname. A wildcard cannot be linked.` or `cdn_example.com is not a hostname.`
: Type one hostname, such as `cdn.example.com`. To serve several subdomains, link each one.

The browser warns that the connection is not private
: The certificate is not issued yet. Wait until the domain's row says **Certificate issued**.

## Related

- [Serve a bucket publicly](/docs/object-storage/public-access/)
- [Host a static website in a bucket](/docs/object-storage/static-websites/)
- [Add, edit and delete DNS records](/docs/websites/dns/manage-dns-records/)
- [DNS record types](/docs/websites/dns/record-types/)

## With the API

Each request takes the service ID and the bucket ID, as in [Serve a bucket publicly](/docs/object-storage/public-access/#with-the-api), and each answers with the whole public access view that [`GET .../public`](/docs/api/reference/client/object-storage/object-storage-buckets/#op-get-api-v1-client-object-storage-service-id-buckets-bucket-id-public) returns. Its `domains` list holds each domain:

```json
{
  "hostname": "cdn.example.com",
  "status": "pending",
  "url": null,
  "error": null,
  "records": [
    {"type": "TXT", "name": "_coritan-storage.cdn.example.com", "value": "5b0e8f6c2a9d4e17b3c6a8d0f1e2b4c7", "status": "pending"},
    {"type": "CNAME", "name": "cdn.example.com", "value": "6f1c2a9e8b3d4f70a5e1c9d2b7f30a48.pub.example.net", "status": "pending"}
  ],
  "created_at": "2026-10-08T09:12:00Z",
  "verified_at": null
}
```

`status` is `pending` until the domain is checked, `failed` when a check found a record missing, `verified` once it serves, and `active` once its certificate is issued. `url` is set while the domain serves. `error` says what is missing, or how to point a domain we verified in your own zone.

### Link a domain {#api-link}

[`POST /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}/domains`](/docs/api/reference/client/object-storage/object-storage-buckets/#op-post-api-v1-client-object-storage-service-id-buckets-bucket-id-domains) takes the `hostname` and answers `201`:

```bash
curl -X POST https://api.coritan.com/api/v1/client/object-storage/1207/buckets/31/domains \
  -H "Authorization: Bearer $CORITAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"hostname": "cdn.example.com"}'
```

### Check a domain

[`POST /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}/domains/{hostname}/verify`](/docs/api/reference/client/object-storage/object-storage-buckets/#op-post-api-v1-client-object-storage-service-id-buckets-bucket-id-domains-hostname) looks up the records now:

```bash
curl -X POST https://api.coritan.com/api/v1/client/object-storage/1207/buckets/31/domains/cdn.example.com/verify \
  -H "Authorization: Bearer $CORITAN_TOKEN"
```

A domain that is already verified is answered as it is.

### Unlink a domain {#api-unlink}

[`DELETE /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}/domains/{hostname}`](/docs/api/reference/client/object-storage/object-storage-buckets/#op-delete-api-v1-client-object-storage-service-id-buckets-bucket-id-domains-hostnam):

```bash
curl -X DELETE https://api.coritan.com/api/v1/client/object-storage/1207/buckets/31/domains/cdn.example.com \
  -H "Authorization: Bearer $CORITAN_TOKEN"
```

### Errors

| Status | `detail` | Cause |
| --- | --- | --- |
| `403` | `To serve a bucket publicly, your account needs a saved payment method, credit, or a paid invoice. Add one in Billing, then try again.` | Linking a domain needs an account in good standing. |
| `403` | `Our staff turned off public access for this bucket. Contact support to have it turned back on.` | [Our staff turned public access off](/docs/object-storage/public-access/#turned-off). |
| `404` | `Custom domain not found` | The hostname is not linked to this bucket. |
| `409` | `That hostname is in use on the platform` | Another bucket or service serves the hostname. |
| `409` | `cdn.example.com is already linked to this bucket.` | The domain is on this bucket already. |
| `409` | `A bucket can have 10 custom domains. Remove one first.` | The bucket has 10 domains. |
| `422` | `cdn_example.com is not a hostname.`, or another message about the name | The hostname is not valid, or belongs to the platform. |
| `429` | An object with `"error": "rate_limited"` and `retry_after_seconds` | Your account made 60 changes, or 120 checks, in the last hour. |

## API

- `POST /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}/domains`: Link a custom domain to the bucket (https://www.coritan.com/docs/api/reference/client/object-storage/object-storage-buckets/#op-post-api-v1-client-object-storage-service-id-buckets-bucket-id-domains)
- `POST /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}/domains/{hostname}/verify`: Check a pending domain's DNS records now (https://www.coritan.com/docs/api/reference/client/object-storage/object-storage-buckets/#op-post-api-v1-client-object-storage-service-id-buckets-bucket-id-domains-hostname)
- `DELETE /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}/domains/{hostname}`: Unlink a custom domain (https://www.coritan.com/docs/api/reference/client/object-storage/object-storage-buckets/#op-delete-api-v1-client-object-storage-service-id-buckets-bucket-id-domains-hostnam)
