# Custom domains

> Serve a SupaHost project from your own domain such as api.example.com, with automatic Let's Encrypt HTTPS. DNS records, Cloudflare settings, domain states and OAuth callbacks.

Source: https://supahost.app/docs/platform/custom-domains · Updated 2026-11-11

A custom domain lets your app call `https://api.example.com` instead of `https://app-one.acme.supahost.space`. It looks better in OAuth consent screens and email links, and it means you can move hosting later without changing URLs in shipped mobile apps. HTTPS certificates come from Let's Encrypt and are issued and renewed for you.

Builder includes 1 custom domain, Pro 3 and Business 10, shared across the instance's projects. Each extra domain is $2 a month.

## Add a domain

1. Open **Dashboard → your project → Domains** and add the hostname, for example `api.example.com`.
2. The page shows the DNS record to create. Add it at your DNS provider (see below).
3. Wait. SupaHost checks DNS about once a minute. When the record is right, it requests a certificate, and the domain becomes **active** once HTTPS answers.

A hostname can belong to only one SupaHost project. Hostnames under `supahost.space` and `supahost.app` are rejected.

## DNS records

You have two choices. A CNAME is the simpler one.

**Subdomain (recommended):** a CNAME from your subdomain to the project host.

| Type | Name | Target |
|---|---|---|
| CNAME | `api` | `app-one.acme.supahost.space` |

**A records:** point the name at your instance's IP address. You can look it up with `dig +short acme.supahost.space`. If there are several A records for the name, all of them must equal the instance IP.

**Root domain** (`example.com` with no subdomain): the record label is `@`. Plain DNS doesn't allow a CNAME at the root, so you need a provider that supports ALIAS, ANAME or CNAME flattening, or use an A record.

You can check what the world sees before waiting on SupaHost:

```bash
dig +short CNAME api.example.com
# app-one.acme.supahost.space.
```

### Cloudflare: use "DNS only"

On Cloudflare the record must be set to **DNS only** (grey cloud), not **Proxied** (orange cloud). When proxied, Cloudflare answers DNS with its own IPs and terminates HTTPS itself, so the DNS check fails and Let's Encrypt can't issue a certificate for your instance. See [Custom domain behind Cloudflare proxy](/kb/cloudflare-proxy-custom-domain).

## Domain states

| State | Meaning |
|---|---|
| **pending** | Waiting for the DNS record to be found |
| **verified** | DNS is correct; the certificate is being issued |
| **active** | HTTPS works on the domain and the project is using it |
| **error** | The domain stayed verified for 30 minutes without a certificate. The usual cause is a proxy in front of it, such as Cloudflare's orange cloud |

DNS is checked on every pass, roughly each minute, for 14 days after you add the domain. If it hasn't verified by then, checks stop; fix the record, then remove the domain and add it again. Stuck? See [Custom domain stuck verifying](/kb/custom-domain-not-verifying).

To test an active domain:

```bash
curl -H "apikey: $SUPABASE_ANON_KEY" https://api.example.com/auth/v1/health
```

## What changes when a domain goes active

When a project has an active custom domain, the oldest active one becomes the project's public address for Auth and functions:

- Auth uses it as its external URL, so links in Auth emails and the **OAuth callback URL** use your domain: `https://api.example.com/auth/v1/callback`.
- Studio uses it as the project URL.
- Edge Functions see it in `SUPABASE_URL`.

Some things stay on the SupaHost host:

- The `iss` claim in user JWTs stays `https://app-one.acme.supahost.space/auth/v1`.
- Public Storage URLs keep using the SupaHost host.

Your app can use either address. Point your client at the new one:

```ts
const supabase = createClient('https://api.example.com', process.env.SUPABASE_ANON_KEY!)
```

## Update OAuth providers

This is the step people miss. After the domain goes active, OAuth providers redirect to the new callback URL, so each provider's app settings must allow it. In Google Cloud Console, GitHub, Apple and the rest, add `https://api.example.com/auth/v1/callback` as an authorised redirect or callback URL. The Auth tab shows the current callback URL for each provider.

If sign-in breaks with a "redirect_uri mismatch" error right after a domain goes live, this is why. See [OAuth broke after adding a custom domain](/kb/oauth-after-custom-domain) and [Social login (OAuth)](/docs/auth/social-login).

Your own app's URLs (Site URL and Redirect URLs on the Auth tab) don't change; those are where users land after sign-in, not the Auth server's address. See [Site URL and redirect URLs](/docs/auth/redirect-urls).

## Related

- [Projects](/docs/platform/projects)
- [Plans and limits](/docs/platform/plans)
- [Security at SupaHost](/docs/security/overview)
