> ## Documentation Index
> Fetch the complete documentation index at: https://docs.boxd.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom domains

> Bring your own domain — point it at one machine, or delegate a wildcard to your whole organization.

Every machine already gets `name.boxd.sh` automatically — see [HTTPS](/reference/https). Custom domains let you put your own domain in front of it instead, two ways:

* **Per-machine**: point one domain you own at a single machine (`app.example.com` → `myapp`).
* **Org-wide** ([organizations](/organizations/overview) only): delegate a wildcard subdomain to your whole org, so every machine gets its own name under it (`myapp.preview.mysaas.com`, `other-vm.preview.mysaas.com`, …) with no per-machine setup.

Both are self-serve. boxd verifies your DNS and issues TLS certificates automatically — no manual cert management, no waiting on us.

## Per-machine

<Steps>
  <Step title="Start adding the domain">
    ```bash theme={"theme":"github-dark"}
    boxd machine domain add app.example.com --vm myapp
    ```

    This doesn't touch anything on the server yet — it just prints the two DNS records to add:

    ```
    A     app.example.com        <shown to you>
    CNAME *.app.example.com      myapp.boxd.sh
    ```

    The A record's target is the same shared address `myapp.boxd.sh` already resolves to — `boxd machine get myapp` shows it too. The wildcard CNAME is required even if you don't plan to use subdomains yet; it's how the certificate gets issued (see below).
  </Step>

  <Step title="Add those records at your registrar">
    Exactly as printed — don't add anything else, and don't delegate NS records for this one (that's the org-wide flow, below).
  </Step>

  <Step title="Confirm">
    Same terminal, same command — it's paused waiting for you. Once the records are live, press Enter. This is the point where boxd actually starts checking; nothing is created server-side before this.

    Scripting it instead of running it interactively? Pass `-y`/`--confirm` up front and it skips the wait — only do this once the records are actually live:

    ```bash theme={"theme":"github-dark"}
    boxd machine domain add app.example.com --vm myapp --confirm
    ```
  </Step>
</Steps>

The domain starts `pending` and a background check verifies DNS and issues the certificate, typically within a minute or two of the records actually resolving. Once both the apex and wildcard certs exist, it flips to `active` and starts routing.

```bash theme={"theme":"github-dark"}
boxd machine domain list                    # domain, machine, status, error (alias: ls)
boxd machine domain remove app.example.com  # unbind (alias: rm)
```

Same three verbs from the [console](https://boxd.sh/app)'s **Domains** page, and from the [Python](/reference/python-sdk#domains) and [TypeScript](/reference/typescript-sdk#domains) SDKs.

<Note>
  Not available on a Tailscale-only org's machines — the A record lives in your own DNS pointing at boxd's public proxy, which can't be repointed at a tailnet address. Org-wide vanity domains (below) don't have this restriction.
</Note>

<Note color="#E05A6D">
  **Don't test the domain before adding the DNS records.** Public DNS resolvers cache a "this doesn't exist" answer once they've seen it — sometimes for a while, independent of what the record's own TTL says once it does exist. If you (or a browser, or a monitoring tool) look up the domain before the records are live, that negative answer can stick around and make a perfectly correct setup look broken for longer than it should. If a domain you just set up won't resolve for you but boxd's own check succeeds (console shows `active`), it's almost always this — try a different network, or a resolver you haven't queried it from yet (e.g. explicitly set your browser to use `1.1.1.1` or `8.8.8.8` under "secure DNS" settings).
</Note>

## Org-wide (vanity wildcard)

Requires being an [org admin](/organizations/overview#roles). One domain per org — the wildcard covers every machine, current and future, with no per-machine step.

<Steps>
  <Step title="Start setting the domain">
    ```bash theme={"theme":"github-dark"}
    boxd manage domain set preview.mysaas.com
    ```

    Prints the NS records to delegate:

    ```
    preview.mysaas.com.  NS  ns1.boxd.sh.
    preview.mysaas.com.  NS  ns2.boxd.sh.
    ```
  </Step>

  <Step title="Delegate at your registrar">
    This is **NS delegation**, not an A/CNAME record — you're handing off DNS authority for `preview.mysaas.com` itself to boxd, which is what lets it mint a wildcard cert and answer for any subdomain under it automatically. Don't delegate your whole domain's apex, just this one subdomain.
  </Step>

  <Step title="Confirm">
    Same terminal, same command — press Enter once delegation is live. For scripts, pass `-y`/`--confirm` up front instead:

    ```bash theme={"theme":"github-dark"}
    boxd manage domain set preview.mysaas.com --confirm
    ```
  </Step>
</Steps>

Once active, every machine in the org is reachable at `<machine-name>.preview.mysaas.com` in addition to `<machine-name>.boxd.sh` — the default domain never stops working. The **bare** vanity domain itself (`preview.mysaas.com` with no machine name) isn't routed anywhere; there's no "default machine" for it.

```bash theme={"theme":"github-dark"}
boxd manage domain get     # any org member can read it
boxd manage domain clear   # admin only
```

Same from the [console](https://boxd.sh/app)'s **Settings** page (General tab), and from the [Python](/reference/python-sdk#vanity-domain) and [TypeScript](/reference/typescript-sdk#vanity-domain) SDKs (`boxd.orgs.set_domain`/`get_domain`/`clear_domain`). The console's **Domains** page links there too, for anyone who lands on the per-machine flow looking for this instead.

<Note>
  Unlike per-machine custom domains, this works on a Tailscale-only org too — boxd-dns is authoritative for the delegated apex (that's what NS delegation buys you), so it can serve the tailnet address instead of the public proxy IP.
</Note>
