Custom domains
Serve a site on your own domain — add it on the site's edit page or via the API, prove with one DNS record that the domain is yours, and the certificate is issued automatically.
403.Add a domain and prove you control it
Add the domain on the site's edit page (or with the API calls below). Until its DNS proves it belongs to this site, the claim is pending: recorded, and shown with the record to create, but not served and not issued a certificate. Either record suffices:
| Proof | Record |
|---|---|
| TXT | _sitebin-challenge.<domain> = sitebin-verify=<token>. The token is minted per site and per domain, and shown on the edit page when you add the domain (pending_domains[].txt_name / txt_value in the API). |
| CNAME | <domain> → the site's own address — <id>.sitebin.app on the hosted service (pending_domains[].cname_target). That is also how the domain routes, so this route needs nothing extra. |
A CNAME to the base domain (app.sitebin.io) is
not proof: it cannot tell sites apart. Only the site's own
address names the site.
Pending claims
The check runs when you add the domain, again when you press
Check now on the edit page (a repeat POST
of the same domain re-checks with the same token), and on every cleanup sweep — every
10 minutes on the hosted service — which attaches a claim on its own
once the record appears, so you never have to come back. A claim that
never verifies is dropped after 7 days; add the domain
again to start over.
Each check that does not attach the domain says what it found, under
the record on the edit page and as check_result in the API:
no TXT record yet, and which of your nameservers said so; a TXT record
with a different value (a missing sitebin-verify= prefix is
the usual one); a CNAME that points somewhere else; or a lookup that
failed. Check now can be pressed three times in a row, then
once a minute per domain.
A pending claim reserves nothing. The site whose DNS carries
its token gets the domain, whoever asked first — which is the
point: without the proof, a stranger could claim
docs.customer.com, wait for the customer to point DNS here,
and be served on it with a valid certificate while the customer was told
the domain was taken.
Route the domain
| Record | Name | Value |
|---|---|---|
| A | your domain | the server's IP address |
| CNAME | your domain | the site's own address, e.g. k3x9q2.sitebin.app |
With the CNAME, routing and proof are the same record.
With an A record, keep the TXT record
permanently: verified domains are re-checked daily, and a
domain whose proof has been definitively absent for
three days is detached — no longer served — and goes
back to pending with the same token, so restoring the record attaches
it again. A lookup error (a timeout, a failing resolver) never detaches
anything; only a definite "no such record" starts the three-day window,
and the proof being seen again inside it closes the window.
How the record is checked
Sitebin asks your domain's own nameservers, not a caching resolver, so Check now sees a record as soon as your DNS provider publishes it — usually within a minute. It finds those nameservers through your domain's registry, so a domain registered minutes ago works too, even before resolvers have caught up with it.
Certificates
The certificate is issued automatically on the first HTTPS request —
there is nothing to configure per domain. The internal
tls-check endpoint issues certificates only for
verified domains, which keeps strangers from triggering
issuance for arbitrary hostnames (issuance-DoS).
Redirects and the generated address
Attach every domain you want to answer — example.at,
example.de, www.example.at — and pick, for each,
whether it serves the site or redirects
to another of the site's domains. The redirect keeps the path and query
(https://example.de/prices?x=1 →
https://example.at/prices?x=1) and is answered by Sitebin
itself, before anything is served, on the domain's own certificate — no
redirect service, no extra DNS. Choose the status code:
| Code | Use it for |
|---|---|
301 | The default. Permanent: search engines move the ranking to the target. |
302 | Temporary, for a move you may undo. |
307 | Temporary, and the browser repeats a POST as a POST. |
308 | Permanent, and the browser repeats a POST as a POST. |
A permanent redirect is sent with Cache-Control: public,
max-age=3600: browsers would otherwise remember it for ever, and a
redirect you change later would keep sending your visitors to the old
target. A temporary one is not cached.
The generated address — <id>.sitebin.app
— keeps serving the site next to your domains until you change it. Once
one of your domains serves the site, switch it off
(visitors get “Site not found”) or have it
redirect to your domain the same way. Off is the right
choice when the site should not be found under sitebin.app at all;
redirect keeps links that were already shared working.
The rules that keep this safe:
- Only your own domains. A redirect points at another verified domain of the same site — never at a URL, never at someone else's site.
- One hop. The target serves the site itself; a domain something redirects to cannot redirect in turn.
- Never offline. Off needs a domain that serves the site. If that domain is ever detached — its DNS proof gone for three days — the generated address serves again on its own, and a redirect to a detached domain becomes a domain that serves, until the proof is back.
- Removing a domain removes its rules. Domains that redirected to it serve the site again.
In the site's settings, each domain in the custom-domain card has two dropdowns: what it does, and the status code.
Container sites have the same rules per HTTP port: every port has a generated address of its own, each domain serves the port you point it at, and a port's generated address can be switched off once a domain serves that same port. See Containers.
Limits
The tier's custom_domains value is a per-site
cap: it limits how many domains one site may carry, not how many your
account may have in total. It is stamped onto each site, and changing
tier restamps the sites you already own. On the hosted service the number
comes from your plan. Pending claims count
against this cap too, so claims cannot be sprayed. Names in your own
zone do not count.
Self-hosted Enterprise instances have a second, separate ceiling: the license entitlement caps verified custom domains instance-wide. See Enterprise. Both are checked only when a domain is added — a domain already serving is never taken away by a cap.
API
Adding a domain answers 202 while the claim is pending and
200 once it is attached. Both return the site's settings
document: attached domains under custom_domains, pending
ones under pending_domains with the records to create.
POST the same domain again to re-check; too many re-checks in
a row answer 429.
# add a custom domain (Enterprise edition only; 403 in community)
# 202 = claimed, pending DNS proof; 200 = attached. POST again to re-check.
curl -X POST -H "X-Edit-Password: $PW" -H "Content-Type: application/json" \
-d '{"domain":"docs.client.com"}' \
https://app.sitebin.io/api/sites/$EDIT_ID/domains
# remove it (pending or attached)
curl -X DELETE -H "X-Edit-Password: $PW" \
https://app.sitebin.io/api/sites/$EDIT_ID/domains/docs.client.com
# redirect an attached domain to another attached domain of the site
# (code 301 by default; 302, 307, 308). DELETE makes it serve again.
curl -X PUT -H "X-Edit-Password: $PW" -H "Content-Type: application/json" \
-d '{"to":"client.com","code":301}' \
https://app.sitebin.io/api/sites/$EDIT_ID/domains/www.client.com/redirect
# the generated address: {"mode":"serve"}, {"mode":"off"},
# or {"mode":"redirect","to":"client.com","code":301};
# on a container site, "endpoint":"api:8080" picks which port's address
curl -X PUT -H "X-Edit-Password: $PW" -H "Content-Type: application/json" \
-d '{"mode":"off"}' \
https://app.sitebin.io/api/sites/$EDIT_ID/view-address
The 202 body, abridged:
{
"id": "k3x9q2",
"view_url": "https://k3x9q2.sitebin.app",
"custom_domains": [],
"pending_domains": [
{
"domain": "docs.client.com",
"txt_name": "_sitebin-challenge.docs.client.com",
"txt_value": "sitebin-verify=<token>",
"cname_target": "k3x9q2.sitebin.app",
"requested_at": "2026-09-07T12:00:00Z",
"checked_at": "2026-09-07T12:00:00Z",
"check_result": "no TXT record at _sitebin-challenge.docs.client.com (answered by ns1.your-dns.example)"
}
],
"domain_recheck_minutes": 10,
…
}
The same fields come back from GET /api/sites/$EDIT_ID,
and the MCP add_domain tool returns
pending_domains in its result instead of failing, with what
the check found in warnings.
The settings document also carries the rules:
domain_redirects ({"www.client.com": {"to":
"client.com", "code": 301, "in_effect": true}}),
view_address ({"mode": "off", "in_effect": true})
and primary_url, where the site is served first.
in_effect is false while a rule cannot be
followed, as described above. A container
site's document adds addresses (each port's generated
address and rule) and domain_targets (the port each domain
serves). Over MCP, set_domain_redirect,
set_view_address and set_domain_target do the
same.
Zones: a whole domain, proven once
If your plan includes zones (Studio on the hosted service), you can add a domain you own as a zone on your account page and use every name under it without a DNS record per name. Create two records:
*.clients.example.com CNAME app.sitebin.io
_sitebin-zone.clients.example.com TXT "sitebin-zone=<token from your account page>"
Press Check now once the TXT record exists. From then on any
name under the zone — acme.clients.example.com, and the zone
itself — attaches to any of your sites the moment you add it.
Names in your own zone do not count against your plan's custom-domain
cap. Certificates are still issued per name, on the first visit, and
only for names you have attached.
- Only you. Every other account is refused a name in your zone, whatever DNS it can show.
- No overlaps. A zone cannot overlap another
account's zone, or one of your own —
example.comandclients.example.comcannot both be zones. - First proof wins. An unproven zone reserves nothing; if two accounts try, the one whose TXT record appears first holds it. Unproven zones are dropped after seven days.
- Nothing is taken away. If another account already has a verified domain inside your zone, the zone stays pending and your account page names that domain; it verifies once the domain is gone.
- Keep the record. The TXT record is re-checked daily. If it stays gone for three days the zone is released, and names that relied on it need their own record (above) within another three days. Removing a zone on the account page works the same way: nothing is detached on the spot.
- A new plan without zones keeps what you have but adds no new zones or names.
- New names are limited to 50 per hour per account, to protect the shared certificate budget; the total is not limited.
Self-hosting: operator zones
If you own a zone and point all of it at your instance with a
wildcard record (*.app.example.com), list it in
SITEBIN_OPERATOR_DOMAINS. Its subdomains then attach to
sites of the instance's operator — accounts with the admin tier that
are also listed in SITEBIN_ADMIN_ACCOUNTS — at once and
without a DNS record, and are refused to every other account. It is the
same as a zone above, configured at startup instead of claimed, and no
account can claim a zone that overlaps it. Without either, a wildcard
pointed at a shared instance leaves every name under it waiting for
whoever claims it first.
Self-hosting: SITEBIN_DOMAIN_VERIFICATION
dns (the default) attaches a domain only once the TXT
record with the site's token or a CNAME at the site's own address is
visible. off attaches on the owner's word alone, with a
warning at startup — only for an instance whose every account holder
you trust, such as a team's own install. On a path-only instance
(SITEBIN_VIEW_ACCESS=path) there is no per-site view host,
so cname_target is absent and only the TXT route exists.
Domains attached before verification existed have no token to check
against and are left alone.