xhost
Sign in

Custom domains

Attach myapp.com to a channel. TLS is automatic.

The flow

Every channel ships at <channel>-<app>-<user>.xhostd.com out of the box. To put a channel on a domain you own, attach the hostname, create two DNS records at your registrar, and ask xhost to verify. HTTPS starts working on the first request — a certificate is minted on demand the first time a browser connects.

From inside Claude:

Inside Claude
> put my prod channel on myapp.com
# Claude calls:
#   add_custom_domain(app_name, "prod", "myapp.com")  # returns DNS instructions
#   verify_custom_domain(app_name, "prod", "myapp.com")  # once records are live

The first call returns a pending domain and a short instruction block telling you exactly which records to create. The second call validates DNS and flips the status to verified; from that point on, https://myapp.com serves your channel.

DNS records you create at your registrar

Two records, always: one TXT that proves you own the domain, and one routing record that points traffic at xhost.

Subdomain (app.mycompany.com)

DNS at your registrar
TXT    _xhost.app.mycompany.com   xhost-verify-<token from add_custom_domain>
CNAME  app.mycompany.com          <your channel's canonical hostname>

Apex / naked domain (mycompany.com)

Most DNS providers don't allow CNAME at the zone apex. Use an A record pointing at xhost's edge IP instead (the add_custom_domain response includes the address).

DNS at your registrar
TXT    _xhost.mycompany.com   xhost-verify-<token>
A      mycompany.com          <platform IP from add_custom_domain>

Verification

verify_custom_domain is idempotent and safe to retry. DNS propagation usually takes a few minutes after you save the records, so the first call after adding them often still returns pending with one of these reasons:

Once a domain is verified, only the four definitive failures (the first four above) ever flip it back to pending — transient resolver errors are recorded as last_error but never downgrade a working domain.

HTTPS

You don't issue or renew certificates. The first HTTPS request after verification triggers Caddy to mint a Let's Encrypt cert on demand; subsequent requests reuse the cached cert. Renewal happens automatically.

A consequence of on-demand TLS: the very first request after verification can take a few extra seconds while the certificate is issued. After that it's normal HTTPS — no warm-up needed.

OAuth works on custom domains

Google sign-in works on every verified custom domain with no extra config — /xhost-auth/* is served on every channel host. The identity cookie is scoped to the host you visited (its aud claim is that exact hostname), so signing in on myapp.com doesn't sign you in on the channel's *.xhostd.com hostname (or any other custom domain attached to the same channel). Verify the cookie against that domain's hostname as the expected aud.

Limits

Detaching

Inside Claude
> remove myapp.com from prod
# Claude calls: remove_custom_domain(app_name, "prod", "myapp.com")

The route is removed immediately and certificate renewals stop — the cached cert will expire on its own. No registrar action required (you can leave or remove the DNS records on your side; they have no effect once the domain is detached).

Privacy · Terms