Highport orbital control

Handing out names under your domain

Prove your domain once, bring one wildcard record alongside, and the station answers for every name you hand out beneath it.

You own website.town. You want Robin to be robin.website.town and Sam to be sam.website.town: their handles, and their sites if they want them, without either of them touching your DNS or proving anything to us. This page sets that up.

You prove the domain once. You add one wildcard record. Then you reserve each name for the account it belongs to, and from then on that name answers for that account. While you are handing out names, nobody else can register one under your domain.

Orbital Control proves your wildcard once and keeps an eye on it after that. Who each name is for, it takes on your word.

There is no screen for this in the Hub yet. A domain's page there shows what you have set up, under Names under this domain, but turning delegation on and reserving names are calls your own tool makes with service authentication, as described on XRPC, and building your own tools. That is why each step below names its method.

The steps

  1. Register the domain and get it verified. This is the ordinary process on Bringing your domain alongside: space.highport.manage.register, a _bard.<domain> TXT record holding did=<your DID>, and space.highport.manage.verify until the status is verified. The domain does not have to serve a site of its own, and its apex never has to point at us. The traffic record register hands back only matters if you also want a site at website.town itself. Until a site is published there, we do not try to get the domain a certificate of its own, and getDomain shows its certificate as none, not as failing. The domain counts as one of your registrations, like any other.
  2. Turn delegation on with space.highport.manage.enableDelegation. It answers with a test address, probeHostname, which looks like hp-3mvbd7kgdlswa.website.town, and one record to create: *.<domain> as a CNAME to the domain's own traffic address, the same value its registration was given, like t-7kq2n8vhtxrwc.highport-dns.net. A domain registered before those addresses existed is given go.highport.space, which works the same way. Delegation is now probing.
  3. Create the wildcard record. About once a minute we request https://<probeHostname>/_bard/health. The first time that request reaches us, delegation moves to enabled, and space.highport.manage.getDomain reports it as delegation.state. You never create a record for the test address itself, because the wildcard covers it.
  4. Reserve each name with space.highport.manage.reserveDomain, giving the hostname and the subject, which is the DID of the account the name is for. There is no DNS step per name, with one exception, covered under the record that breaks a name. Reserving a name again for the account that already holds it is safe to retry: it answers the reservation as it stands, changes nothing, and does not count against maxNames a second time.
  5. Check on them with space.highport.manage.listReservations. Each entry gives the account a name is for, whether the name still reaches us, and, once the account has actually adopted the name as its handle, when that happened (takenUpAt).

A worked example

website.town and both DIDs below are made up. The record types, the target, the ttl and the field names are exact.

Registering website.town hands back its ownership record, and the domain verifies the usual way. Turning delegation on is a request body of {"domain": "website.town"}, and it answers:

{
        "domain": "website.town",
        "probeHostname": "hp-3mvbd7kgdlswa.website.town",
        "records": [
          {
            "type": "CNAME",
            "name": "*.website.town",
            "value": "t-7kq2n8vhtxrwc.highport-dns.net",
            "ttl": 300,
            "purpose": "traffic",
            "required": true
          }
        ],
        "status": "probing"
      }
      

Once getDomain reports enabled, Sam's name is one call:

{ "hostname": "sam.website.town", "subject": "did:plc:q4hjt6kxgm2vzo5lrw7ycsne" }
      

It answers with the same hostname and subject and "status": "verified". The name is Sam's from that moment.

Robin already uses robin.website.town as their handle, verified by DNS, so the zone holds a TXT record at _atproto.robin.website.town. That record takes robin out of the wildcard's reach (the next section explains why), so their name needs a record of its own. All told, the zone holds three records for this:

Name Type Value Why it is there
_bard.website.town TXT did=did:plc:7x2c4k5wqvhbnrzj3dd6mafe Proves website.town is yours. The value is your DID, and register hands it back
*.website.town CNAME t-7kq2n8vhtxrwc.highport-dns.net Sends every name one label under website.town to us, the test address included. From enableDelegation
robin.website.town CNAME t-7kq2n8vhtxrwc.highport-dns.net Only because _atproto.robin.website.town exists. Sam's name needs nothing

The value is website.town's own traffic address, and every name under it points at the same one. That is what lets us move the base and every name it has handed out together, with no change in your zone.

A fourth record appears only if you ask for one certificate for every name instead of one per name: _acme-challenge.website.town as a CNAME into our zone. Read that section before adding it — it grants more than the three records above do.

Names are entered the way Bringing your domain alongside describes. In a relative Name field the wildcard is * and Robin's record is robin.

The record that breaks a name

A DNS wildcard only answers for a name that does not exist in the zone, and the catch is what counts as existing. A name exists if there is any record at it or anywhere beneath it. _atproto.robin.website.town is beneath robin.website.town, so robin.website.town exists, holds nothing of its own, and *.website.town stops answering for it. A lookup for it comes back with no address at all.

The usual cause is an _atproto TXT record left over from verifying a handle by DNS. Anything else somebody once created under that label does the same: a mail record, a _dmarc record, an old A record. An explicit record at the name itself also beats the wildcard, and sends the name wherever that record points.

There are two fixes. Give the name its own CNAME to the same value as the wildcard, as in the example. A CNAME cannot share its name with any other record, so if something sits at the name itself, not beneath it, remove that first. Or, if the only thing beneath the name is the _atproto record, remove that instead: a reserved name already answers the handle check over HTTPS for the account it is reserved for, so the handle does not need the TXT record.

We do not refuse a reservation over any of this, because nothing looks the name up when you reserve it. We keep checking afterwards instead. Every reserved name is requested through our edge in the same rolling sweep as every live site, and a name that does not reach us shows it in its edge entry in listReservations and in the account's own view of the domain. Nothing is suspended over it, and the next check that gets through clears it.

What the account gets

Its DID, at the name, straight away. https://<name>/.well-known/atproto-did answers with the account's DID as soon as the name reaches us and has its certificate. The account does not have to have published anything, or even to exist yet, which is what lets somebody create an account with the handle already in place. The answer may be cached for up to a minute.

A site there, whenever it publishes one. The account writes a site record keyed by the name, exactly as on Your first site. The name is already verified, so it creates no DNS records and no _bard record of its own.

The bill for its own traffic. What the name serves is drawn from the account's bandwidth allowance, not from yours — unless your default design is what a visitor is getting, in which case the traffic is yours. See below.

A default design, if you set one. Until the account publishes, the name shows nothing. You can put one design on every unclaimed name under your domain instead.

Three things it does not get:

The account can give the name up itself with space.highport.manage.releaseDomain, the same method it would use for a domain it registered.

A default design for names nobody has claimed

A name you hand out resolves as its holder's handle straight away and shows nothing until they publish. You can set one design — a published tile — that every unclaimed name under your domain serves instead: a landing card, a "this name is reserved for you" page, whatever you want strangers to find there.

Set it in the Hub, on your domain's settings, or with space.highport.manage.setDelegationDefaultTile. Three things about it are worth knowing before you do.

The holder replaces it, and does not have to ask. The moment they publish a site record for the name, theirs serves and yours stops. You are not told. That is not a limitation to work around — the name is theirs, and this is the arrangement you agreed to when you reserved it for them.

You pay for its traffic, not them. While your design is what a visitor gets, the bytes are billed to you. Once they publish, the bill moves to them with the content. This is the one exception to "the account pays for its own traffic" above.

It can never touch handle resolution. https://<name>/.well-known/atproto-did keeps answering the holder's DID whatever your design contains — that is why a design whose own content defines that path is refused as a default. Somebody using the name as their handle is unaffected by anything you put there.

It is a one-page design. A request for any path the design does not define gets our standard not-found page, not the design's own. A design meant to be a single-page app — one that expects every path to load the same page and route in the browser — will not work as a default: its deep links will be not-found. A landing card, a "reserved for you" page, a single page of anything: fine. This is a limit of default designs only; a design bound by a site record can say what its not-found page is, because the record carries that field and there is no record here.

Two further limits on which designs qualify. It must be a public design, not one belonging to a space. And it must ask for no required details: your design goes on every name at once, so there is nobody to fill them in. A design whose parameters are all optional is fine — its own code supplies the defaults.

Removing the default puts every unclaimed name back to showing nothing. Names whose holder has published are untouched.

Whether a name was taken up

takenUpAt appears on an entry in listReservations the first time we see the account holding the name as its handle: its DID document lists at://<name>, and the name resolves back to the account. It is recorded once. If the account later moves to another handle, the time stays. Releasing the name clears it, so a name reserved again starts without one. No takenUpAt means we have not seen the name taken up yet, which is not the same as having seen that it was not.

Who a name is for is your call

reserveDomain checks the subject's shape and nothing else. It has to be a well-formed did:plc or did:web, and a did:web at the very name being reserved is refused, because nothing could serve its document there. The DID is not looked up, and the account does not have to exist.

That puts the weight on you, so it is worth knowing what it does and does not allow. Software that follows the protocol accepts sam.website.town as a handle only when the account's own DID document lists at://sam.website.town, so reserving a name for somebody's DID cannot make it their handle. A tool that stops at the HTTPS answer is different: it will show whichever DID you named. Reserve a well-known person's DID under a name, and some tools will show that person at your address.

If you hold the account's genesis operation, send it. A service that creates accounts in bulk, such as a PDS, has each new did:plc account's signed genesis operation in hand. Pass it as genesisOp and we verify it offline, without asking any directory. It has to be the account's first operation, hash to the subject, carry a valid signature from one of its own rotation keys, and list at://<name> in alsoKnownAs. If any of that fails, the call is refused with InvalidSubject and a message saying which check failed. Without a genesisOp, the shape check is the only one.

Certificates, and why a new name can take a minute

This section describes what happens by default, which we call per-name mode: a certificate for each name. There is a second way, a single wildcard certificate covering every name at once, and it is a deliberate choice with a real trade-off — see One certificate for every name.

Each reserved name gets a certificate of its own, and every name under one registered domain draws from one weekly budget. The certificate authority limits new certificates per registered domain: Let's Encrypt issues at most fifty in seven days. We hold ourselves to forty new certificates per registered domain in any seven days, across the whole deployment, and leave the other ten for certificates we cannot count, including any you obtain elsewhere.

The forty cover each reserved name's first certificate, the test address's one certificate, and every other name under the same registered domain that is served here, your domain's own included.

A reserved name waits for its turn. When the week has room, that is usually a minute or so. When it does not, the name waits until older certificates leave the seven-day window. While a name waits, and until its certificate arrives, listReservations shows its certificate as pending and its reachability as unknown: we do not check whether a name reaches us before its certificate is on its way. Neither reads as failing.

Releasing a name does not give its turn back. The count is of certificates obtained, not of names held, so releasing some names and reserving others does not get you more certificates in a week.

One certificate for every name

If you are handing out many names, one wildcard certificate covers all of them and the weekly budget above stops being the thing that paces you. Getting one asks something of you that per-name mode does not, and it is worth reading the whole of this section before you decide.

space.highport.manage.setCertificateMode with {"domain": "website.town", "mode": "wildcard", "acknowledgeCertificateGrant": true} answers with one record to create:

Name Type Value
_acme-challenge.website.town CNAME _acme-challenge.ac-<label>.delegated.highport.space

The ac- label is yours alone and is minted for this grant. Copy the value exactly as the call returns it; nothing derives it from anything else, and a value copied from another domain's instruction is refused.

The record has to be visible as a CNAME, because the certificate authority follows it. If getDomain's certificate check says no CNAME is visible and you have added one, the likely causes are that it has not been published or has not propagated yet, that it is proxied rather than DNS-only (an orange-cloud record on Cloudflare), or that CNAME flattening is turned on for the whole zone (a setting on paid Cloudflare plans).

What that record grants

While that CNAME exists, we can obtain a publicly-trusted certificate for website.town itself and for every name in the zone — mail.website.town, vpn.website.town, names you have never told us about and names you create later. That is what delegating the challenge label means. It is not narrowed by anything in our policy, and we would rather you read it here than discover it from a certificate log.

Three things bound it, and none of them is our discretion:

You can check what we did without trusting us. Every certificate any public certificate authority issues is published to Certificate Transparency. Watching CT for your domain — crt.sh is the usual free way — shows exactly what we ordered, when, and for which names.

Holding a certificate for a name is not the same as receiving its traffic. Traffic goes where your DNS sends it, and an explicit record always beats the wildcard, so mail.website.town keeps going to your mail host. Our edge also refuses any name we have no reservation for.

Pinning issuance to us

Recommended, not required. A CAA record on your domain restricts wildcard issuance to our ACME account and nobody else's:

website.town.  CAA 0 issuewild "letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/3730318876; validationmethods=dns-01"
      

If someone later took over the challenge target, our zone, or the CNAME itself, they still could not get a wildcard for your zone. It does not reduce what we can do with the delegation you have already given us.

Put it on issuewild, never on issue. issue governs the certificate every individual name uses, and those validate over HTTP — a record allowing dns-01 alone there stops delegation being turned on at all, refuses per-name mode, and leaves no fallback. issuewild governs wildcards only, which is exactly the thing being delegated.

CAA is zone-wide and additive. A CAA record set applies to every name beneath it that has none of its own, so publishing only our record tells every certificate authority to refuse everyone else's — your mail server's, your other tools'. Keep the records you have and add ours beside them.

If we ever move to a new ACME account, we will tell pinned owners before it happens and list both account URIs while the change is in progress, so there is no window where your pin blocks renewal.

A narrower way to do the same thing

If granting the whole zone is more than you want to give, register sites.website.town as the base instead of website.town, delegate _acme-challenge.sites.website.town, and hand out robin.sites.website.town. We can then obtain *.sites.website.town and nothing else anywhere in your zone. Everything else on this page works the same way. It is exactly what sites.highport.space is.

Ending a grant, and what the certificate log will show

A grant ends when you switch to per-name mode, when you release the domain, or when the CNAME stops resolving for longer than the grace period. When it does, we revoke the wildcard and delete it.

The certificate authority records that revocation with the reason keyCompromise, and no key was compromised. We revoke using the certificate's own private key, which is the method that needs no account credentials — and Let's Encrypt records every key-signed revocation that way regardless of what is asked for. If you are watching CT or your own certificate monitoring, that is what you will see, and it means the grant ended, nothing more.

A certificate that a renewal already replaced may stay valid until it expires; revocation reaches the one we hold. Granting again after an ending mints a new challenge target, so you will need to update your CNAME — the old value stops working.

Rules you will meet

Rule Refused with
A name is exactly one label under your domain: sam.website.town, never a.sam.website.town. A wildcard covers one label DelegatedNameTooDeep (400)
Your domain holds at most maxNames reserved names. Leave maxNames out when you turn delegation on and the deployment's ceiling applies, which is twenty by default. Asking for more than the ceiling is refused DelegationQuotaExceeded (429) when reserving, MaxNamesAboveLimit (400) when turning delegation on
Only the account the domain is registered to may do any of this, and only while the domain is verified or active NotBaseOwner (403)
A domain registered to a space cannot hand out names. Neither can a name we provide, a name that was itself handed out, or a domain above or beneath one that already hands out names: foo.website.town cannot while website.town does, and the other way round DelegationUnsupported (501)
Names can be reserved only once the wildcard has reached us, and not while it has lapsed. The message names the test address and the record DelegationNotVerified (409)
A name somebody has already proved the ordinary way, or that is already reserved for a different account or suspended, has to be released first. An unproven claim on it is simply replaced DomainTaken (409)
The subject is not a well-formed DID, or a genesisOp does not prove it InvalidSubject (400)
The name or the account is on the operator's denylist DomainDenied (403)

There is no list of held-back labels. You choose the names, www and mail included.

While your domain hands out names, register refuses every other account a name under it, with DomainDelegated. You can still register one yourself the ordinary way, with its own _bard record, but it cannot hand out names of its own: delegating domains do not nest.

maxNames is set when you turn delegation on. Calling enableDelegation again with the same settings just answers the current state, and naming a different maxNames is refused with InvalidRequest. Changing it means turning delegation off and on again, which requires releasing every name first.

Taking names back

space.highport.manage.releaseReservation, given the hostname, releases one name. The name stops answering for the account and its site stops serving at once. You can reserve the name again straight away, for the same account or a different one. Other people's software may go on showing the old account for a while after a name changes hands.

space.highport.manage.disableDelegation turns delegation off. While any name is still reserved it is refused with DelegationHasReservations (409), because releasing somebody else's name should be a deliberate act, one name at a time.

Releasing your domain itself with releaseDomain releases every name under it at the same moment.

If the wildcard stops reaching us

Once delegation is enabled, we keep requesting the test address every fifteen minutes. If those requests keep failing for seven days, delegation moves to routing-lapsed: every name under your domain stops serving, DID answers included, and no new names can be reserved. Your domain's own status does not change, and anything served at website.town itself carries on. The first request that gets through moves delegation back to enabled, and the names serve again, unless something else is holding one of them, such as an operator's hold.

The test address proves the wildcard reaches a server running this software, not that it reaches this deployment. A name with nothing published on it is answered the same way by any deployment running this software, and that answer carries nothing saying which deployment sent it. So if your wildcard points at a different deployment, the test still passes, and listReservations reports your names as reaching us when they do not. If names read as reachable but do not serve, check that the wildcard points at the value enableDelegation returned.

Seven days is this deployment's verification grace, the same week a missing _bard record gets. That clock applies here too: if your domain's ownership record goes missing for seven days, the domain stops serving and every name under it stops with it. Bringing your domain alongside has that clock in full.

CAA records

Most zones have none, and then there is nothing to do here. A CAA record says which certificate authorities may issue certificates for a domain. If your zone has any, enableDelegation checks them before it turns anything on, looking where a certificate authority would: at your domain and, if nothing is there, at each parent in turn.

The issue records it finds have to allow all three of the following, or turning delegation on is refused with CaaForbidsIssuance (409) and a message naming the record in the way:

Part of the record What it has to allow
The authority it names This deployment's certificate authority. On highport.space that is Let's Encrypt, written letsencrypt.org
accounturi, if present One of this deployment's own ACME accounts. There is normally one. While we move to a new account we accept both, and the message suggests a record for each, so keep both until we say the old one is retired
validationmethods, if present http-01 or tls-alpn-01. A record allowing dns-01 alone rules out the certificate every reserved name uses

A wildcard grant is checked against a different record, with nearly the opposite rule. issue governs individual names, which validate over HTTP; issuewild governs wildcards, which can only be validated over DNS. So if you ask for one certificate for every name, we look at issuewild — or at issue, if your zone has no issuewild — and it has to allow letsencrypt.org over dns-01, pinned to one of our accounts if it names accounturi at all. A zone whose issue records demand http-01 and whose issuewild demands dns-01 is perfectly consistent, and is what a zone doing both looks like.

Neither check is a way for us to do more than you allowed: both refuse, and neither widens anything.

A record marked critical, with a tag no certificate authority understands, is refused as well. So is a set of records that could not be read at all, and the message says so, which tells a passing DNS fault apart from a record that needs changing.

Sharing a domain with people you do not know

This part is not a setting, and we have not solved it.

A browser treats every name under website.town as one site. Each name is still its own origin, and what follows the origin stays separate: one name's pages cannot read another's, and browser storage such as localStorage stays with the name that wrote it. What follows the site does not separate your tenants from each other.

The real fix is the Public Suffix List, which browsers use to decide where one site ends and the next begins. When a domain is on it, every name beneath it is a site of its own. Only the domain's owner can apply, by publishing a DNS record and asking the list's maintainers, and it is slow: review takes weeks or months, and browsers pick up the change on their own release schedules. Being on the list also changes how other software treats the domain, this service included, because it will not register a name that is itself a public suffix. Talk to the operator before you apply.

Until then, hand out names to people who can live with sharing a site, and keep member-gated sites, and anything else that depends on cookies, on a domain of their own.

Site owners · 8 of 12