# Custom domains

Every app gets an address on `<name>.nimbusapps.ca`. A custom domain lets the app answer on a hostname you own instead, such as `app.example.com` or `example.com`. Nimbus Land requests a Let's Encrypt certificate for it and renews it for you, and plain HTTP requests redirect to HTTPS.

An app has one custom domain. If it is a zone root or its `www` name, the other one can redirect to it (see [The root and www pair](#the-root-and-www-pair)).

## Adding a domain

In the console, open the app's **Access** tab, enter the hostname under **URLs and domains** and choose **Add domain**. You need permission to manage apps.

From the `nimbus` CLI:

```sh
nimbus domains add app.example.com      # claim the domain
nimbus domains                          # show it and the DNS records to create
nimbus domains check                    # check DNS now instead of waiting
nimbus domains remove                   # remove it
```

Each command takes `--app NAME` (otherwise the app linked to the current directory) and `--json`.

Adding a domain only *claims* it. Nothing is served on it and no certificate is requested until the claim is verified. The Access tab then shows a short checklist with the records to create, laid out with your DNS provider's own column names and a **Copy** button on each value.

## Step 1: point the domain at Nimbus Land

The Access tab shows exactly one record for the hostname you claimed. Point it at `ingress.nimbusland.ca` by name rather than at an IP address whenever your provider allows it, so your domain follows the platform automatically. A TTL of 300 seconds (5 minutes) is a good choice.

### A subdomain (`app.example.com`)

| Type | Name | Target |
|---|---|---|
| `CNAME` | `app` | `ingress.nimbusland.ca` |

Every provider supports this.

### A root domain (`example.com`)

DNS doesn't allow a plain CNAME at the root of a zone, so providers offer a record that behaves like one, under different names. The Access tab shows the record your provider offers:

| Provider | Record for `@` |
|---|---|
| Cloudflare | `CNAME` to `ingress.nimbusland.ca`, Proxy status **DNS only** (Cloudflare flattens it) |
| Namecheap | `ALIAS Record` to `ingress.nimbusland.ca` |
| Porkbun, DNSimple, DNS Made Easy, Gandi, Name.com, NS1, easyDNS | An ALIAS- or ANAME-style record to `ingress.nimbusland.ca` |
| Providers with no such record (GoDaddy, Route 53, Azure DNS, Squarespace and others) | An `A` record at the address shown on the Access tab |
| A provider Nimbus Land doesn't recognise | An `ALIAS` record, with an `A` record behind **My provider has no ALIAS, ANAME or root CNAME record type** |

Use the `A` record only when your provider has no alternative: it copies an address, so you would have to update it by hand if that address ever changed.

Nimbus Land recognises most common DNS providers from the domain's nameservers and tells you where in their dashboard the record goes. If it can't tell, it asks **Where is your DNS hosted?**. Your answer is kept, and you can change it at any time.

### Before you add the record

Most dashboards refuse to save a record for a name that already has an `A`, `AAAA`, `CNAME` or URL-forwarding record, and the error rarely says so. Registrars often add a parking redirect on `@` to new domains. Delete the existing record for that name first, then add the new one.

- Cloudflare says "An A, AAAA, or CNAME record with that host already exists".
- Namecheap only says the record could not be saved. Usually the culprit is the URL Redirect Record it adds to new domains.

## Step 2: prove the domain is yours

How you prove ownership depends on the hostname:

- **Nobody else has claimed it and it doesn't already point at Nimbus Land.** The record from step 1 is the proof. The claim is verified the first time Nimbus Land sees the hostname reach it. You can add a TXT record as a shortcut (**In a hurry? Verify with a TXT record instead**).
- **It already points at Nimbus Land, another account has claimed it, or its DNS couldn't be read.** A TXT record is required (**Prove you control the domain**).

The TXT record:

| Field | Value |
|---|---|
| Type | `TXT` |
| Name | `_nimbus-challenge.<your domain>`, for example `_nimbus-challenge.app.example.com` |
| Value | `nimbus-domain-verification=<token>`, exactly as shown on the Access tab |

Most dashboards want the name relative to the zone: `_nimbus-challenge` for `example.com`, `_nimbus-challenge.app` for `app.example.com`. The Access tab shows it in the form your provider expects. Nimbus Land asks your domain's own nameservers for it, so a record you've just saved is usually found within seconds. You can delete it once the domain is verified.

A TXT proof always wins: if another account claimed the hostname only by pointing DNS at it, your TXT record takes it over, and that account is told by email. A hostname someone has already proven with a TXT record cannot be claimed by another account.

### Waiting for verification

Nimbus Land checks every minute for the first hour after you add the domain and hourly after that, and the Access tab updates on its own. If you get to your DNS dashboard more than an hour later, choose **Check now** (or run `nimbus domains check`) rather than waiting for the hourly check.

The checklist always says what the last check saw and what to do next:

| What the check saw | What to do |
|---|---|
| Nothing yet | Publish the record; the step completes on its own |
| Another address, usually your previous host | Replace that record with the one shown |
| Cloudflare's proxy | Set the record's Proxy status to **DNS only** |
| Nimbus Land's address | Nothing; the step is done |

A claim that isn't verified within 7 days is released, so nobody can hold on to a hostname they don't control.

## Step 3: the certificate

Once the domain is verified and points at Nimbus Land, the certificate is requested from Let's Encrypt automatically and renewed automatically. The badge reads:

| Badge | Meaning |
|---|---|
| **Requested…** | Waiting to start. If it stays this way, DNS is not pointing here yet or is proxied |
| **Issuing…** | Let's Encrypt is checking the domain; the reason is shown next to it |
| **Certificate active** | The app is served on your domain over HTTPS |

The custom domain then becomes the app's public address, including the **Open** link.

## Cloudflare

New Cloudflare records default to **Proxied** (orange cloud). The Cloudflare proxy is not supported in front of a custom domain today: a proxied hostname is not verified and no certificate is issued for it.

1. Open the zone, then **DNS → Records → Add record**.
2. Delete any existing `A` or `AAAA` record for the same name.
3. **Type** `CNAME`, **Name** `@` (or `app` for a subdomain), **Target** `ingress.nimbusland.ca`.
4. Click the orange cloud so it turns grey: **DNS only**.
5. Leave **TTL** on Auto and save. For a root record Cloudflare notes that the CNAME will be flattened; that is expected.
6. If the Access tab asks for one, add the `_nimbus-challenge` TXT record.

## The root and www pair

If your custom domain is a zone root (`example.com`), its `www` name is paired with it automatically, and the other way round. Any other hostname has no pair. The pair needs no second proof of ownership.

Setting up the pair is optional. It appears as an extra, optional row in the records table. Once your custom domain is verified and the paired name points at Nimbus Land, that name gets its own certificate and permanently redirects, in one hop, to `https://<your custom domain>` with the same path and query string. If the paired name is never set up, or its certificate fails, your custom domain is not affected.

To make `www.example.com` the address visitors use, add `www.example.com` as the custom domain; `example.com` then redirects to it.

You can still attach the paired name to a different app on purpose, for example a marketing site on `www`. Once that claim is verified, the redirect stops and the name serves the other app. Browsers cache permanent redirects, so some visitors may keep following the old redirect until their cache expires.

## Phoenix and Laravel apps

Phoenix only accepts LiveView and other websocket connections from the host in `PHX_HOST`, and Laravel builds links, redirects and signed URLs from `APP_URL`. When you add a domain, Nimbus Land sets `PHX_HOST` (Phoenix) or `APP_URL` (Laravel) to it unless you untick **Also set PHX_HOST to this domain** (or `APP_URL`), or pass `--no-phx-host` to the CLI. If the variable names another address later, the Access tab offers **Use \<domain\> for PHX_HOST**. Removing the domain resets it to the app's `nimbusapps.ca` address.

Changing `PHX_HOST` means the `nimbusapps.ca` address stops accepting live connections unless you allow both hosts in Phoenix's `check_origin`. See [Elixir and Phoenix](elixir.md#hostnames-and-phx-host).

## Static sites

Custom domains work the same way for [static sites](static-sites.md): the same records, the same verification and the same certificates.

## Removing a domain

Choose **Remove** on the Access tab (or run `nimbus domains remove`). The app stops answering on the domain and its paired name; its `nimbusapps.ca` address keeps working. Then delete the DNS records at your provider: a hostname left pointing at Nimbus Land shows visitors a certificate error.

Adding the same domain again creates a new TXT value, so replace any TXT record left from the first attempt.

## Troubleshooting

Check what your DNS returns:

```sh
dig app.example.com A +short                    # subdomain: the CNAME, then an address
dig example.com A +short                        # root: an address
dig _nimbus-challenge.example.com TXT +short    # the TXT record
```

A flattened or ALIAS root answers with a plain address, so `dig example.com CNAME` returning nothing is expected.

| Symptom | Likely cause | Fix |
|---|---|---|
| "No verification record found" | The TXT record isn't published yet, or is under the wrong name | Check the name (`_nimbus-challenge`, relative to the zone), wait a minute, choose **Check now** |
| "A verification record exists but its value does not match" | A TXT value left from an earlier attempt | Replace it with the value shown now |
| The record "could not be saved" at your provider | Another record already exists for that name | Delete it (often a parking redirect or an old `A` record), then add the record again |
| Certificate stays on **Requested…** | The hostname doesn't resolve to Nimbus Land, or is proxied | Run the `dig` checks; on Cloudflare set **DNS only** |
| "Proxied through Cloudflare" | Cloudflare's proxy is on | Set the record's Proxy status to **DNS only** |
| The page loads but LiveView keeps reconnecting | `PHX_HOST` still names the `nimbusapps.ca` address | Choose **Use \<domain\> for PHX_HOST** on the Access tab |
| `www` (or the root) shows a certificate warning | The paired name isn't served yet: proxied, pointing elsewhere, or its certificate is still issuing | Check its row on the Access tab |
