Highport orbital control

Redirects, missing pages, and being your own handle

What Orbital Control answers when somebody asks for an address you never published. Three fields decide it, and one of them also decides whether your domain can be your name on the network.

Three optional fields on your site record decide what happens at an address you did not publish a file for. redirects sends an old address to a new one. notFound says what to serve when nothing matches. wellKnown controls the files we answer on your behalf, and one of them is why the last section of this page exists. If the domain you are publishing at is also your handle on the network, that file has to say the right thing.

None of it is required. A record that mentions none of these serves your files, answers a plain 404 at everything else, and does the identity paperwork on its own.

Everything below assumes you already have a manifest, the list saying which of your files answers which address. That is the site record.

Redirects

redirects is a list of rules, and we check it before we look at your files. Each rule has a from, a to, and optionally a status.

"redirects": [
        { "from": "/old", "to": "/new", "status": 301 },
        { "from": "/gone?ref=x", "to": "/" }
      ]
      
Field Required What it is
from yes The address being redirected. An absolute path, optionally with a query string. At most 2048 bytes for the whole thing, path and query together
to yes Where it goes. An absolute path that is a literal key of the manifest this site serves from, either yours or a bound tile's
status no One of four values, below. Leave it out and we apply 302
status What the Hub calls it
301 301 permanent
302 302 temporary
307 307 temporary, method preserved
308 308 permanent, method preserved

A rule whose status you leave alone in the Hub carries no status field at all. The record stays smaller, and 302 is written into the site when we build it.

Matching is exact

The address you are redirecting from has to match exactly, including anything after a ?.

Because redirects are checked before your files, a rule at a path you also publish wins over the file. That is how you retire a page without deleting it.

Redirects are the site record's, always. A site bound to a tile still carries its own list, and those rules apply over the tile's addresses. A tile has no redirects of its own, and none of this is ever attributed to one.

The rule that catches people

to has to be a literal key that exists in the manifest. Not a pattern. Not an external URL. Not a path that looks about right.

/blog/ is not a key when the manifest holds /blog/index.html, and the refusal says so: the target "must be a literal key, not a path that merely resolves". Serving /blog/ works, because the index-document rule runs when somebody asks. A redirect target is checked against the list of keys as written, and that list has no /blog/ in it.

In the Hub the To field is a dropdown of your manifest's keys, not a text box, so there is nothing to mistype. A redirect pointing somewhere that is not there makes the whole record invalid, and validation tells you before you write it. On a site bound to a tile it is a plain text box instead, because we do not hold the tile's file list in your browser, and the check catches a bad destination when you run it.

The site editor's extra settings, showing the redirect rows with their destination dropdown, the not-found page upload, and the two well-known checkboxes

What a redirect cannot do

When a redirect is refused

Validation reports these before anything is written. If you write the record yourself and skip that step, the same rules run when we index it, nothing is activated, and whatever was serving before keeps serving.

Error What happened
RedirectTargetNotFound to is not a literal key of the manifest. That includes a path that resolves but is not a key, one that does not begin with /, and a protocol-relative //host/path
ReservedPathCollision from or to names a path we answer ourselves
DuplicateRedirect Two rules match the same request line. The message names both, and says which one already matched
InvalidManifest More than 256 rules; a from over 2048 bytes; a from that is not a usable path; a query containing a NUL byte, a control character or #; a status that is not one of the four

Two spellings of one address are a duplicate, not two rules. /old?a=%7E and /old?a=~ are the same request line, and only the first would ever fire. Going over 256 stops the redirect pass, so the rest of the list is not checked that time. Fix the count and validate again.

When a page is not found

notFound names one file to serve when a request matches no redirect and no entry. It is a file in its own right, carried directly in the record, not a path into your manifest. It stays yours even when the rest of the site comes from a tile, and its bytes count against your site's total size like any other file.

"notFound": {
        "src": { "$type": "blob", "ref": { "$link": "bafkrei…" }, "mimeType": "text/html", "size": 2840 },
        "status": 200
      }
      

In the Hub it is one file picker labelled Your not-found page. Upload something and a pair of radio buttons appears for the status. Because it is not a manifest key, the page has no address of its own. If you also want it reachable at, say, /404.html, add it to the manifest as well.

Set nothing and we serve a plain page with a 404, headed This page is not part of this site. It carries no Highport name on it, deliberately: it appears inside your site at your domain, and it is not ours to sign.

Choosing the status

status Meaning
absent, or 404 A real not-found response
200 One page handles every address, the single-page-application fallback
410 Gone, permanently

404 is the ordinary case. A real not-found page, honestly reported.

200 turns it into a single-page-application fallback. Every unmatched path serves that document with a success status so a client-side router can take over. Use it if your site is a single-page application, and do not use it otherwise, because it means a genuinely missing page stops reporting as missing to anything that checks: your own analytics, a link checker, a search engine, and you.

410 is accepted, and it says every unmatched address on this site is permanently gone, not merely absent. That is a strong claim to make about a whole site. The Hub has no button for it, so setting it means writing the record yourself.

What the not-found page never covers

There is no Accept condition on any of this. A request for a missing image gets the not-found document too, with whatever status it carries. So on a site using the 200 fallback, a broken image tag fetches your HTML shell and gets a 200 for it.

Under /.well-known/ and /_bard/, a miss is a bare 404 and your not-found page is never reached. Those requests come from programs looking for one artifact in one format, written to read a 404 as "this host does not implement that protocol". The rule exists for the case at the end of this page. Without it, a site with a 200 fallback would answer a handle resolver with a page of HTML and a success status.

The files we serve for you

Three addresses, all answered by us. The first two are on unless you turn them off, and the third is off until you name a publication. None of them are pages anybody visits.

Field Address What it answers
atproto /.well-known/atproto-did The DID of the account that published this site, as text/plain; charset=utf-8
rasl /.well-known/rasl/{cid} One of the files this site serves, addressed by the hash of its bytes
standardSitePublication /.well-known/site.standard.publication The at:// address of a standard.site publication you name, as text/plain; charset=utf-8. Nothing, unless you set it

The two flags exist only to turn things off. A record that never mentions them gets both, and so does {"rasl": false} for atproto. Write only the flags you are setting to false. {"atproto": true, "rasl": true} means exactly the same thing and changes your record's hash for nothing. standardSitePublication is the other way round: we answer nothing there until you name a publication.

The rasl addresses

/.well-known/rasl/{cid} hands out one of your files by the hash of its contents, so other software can fetch it directly and check that what arrived is what you published.

The scope is exactly this site: the entries it serves, which are a bound tile's if it is bound to one, your not-found page, and any file your parameters carry. A hash your site does not reference is a 404, always. It is not a way to read anything else we hold.

Responses go out as application/octet-stream, uncompressed, and are cacheable indefinitely, because the address names the bytes. Turn it off and every address under /.well-known/rasl/ returns 404 without a lookup. Leaving it on is harmless.

Proving a standard.site publication

standard.site has a domain prove a publication by answering /.well-known/site.standard.publication with the publication's at:// address. Name the publication and we answer it for you:

"wellKnown": {
        "standardSitePublication": "at://did:plc:…/site.standard.publication/3mtneerja2d6u"
      }
      

In the Hub it is the last box under Files Highport serves for you, on the site's content page.

We check it when we index your site record, never when somebody asks, and we refuse the record if it does not hold:

The answer is the address and nothing else, text/plain; charset=utf-8 with no trailing newline, and it is readable from any origin, so a verifier running in a browser on another site can check it. We read the publication as it stands when the site record is indexed and do not pin a version of it, so renaming the publication or changing its theme needs nothing from you here. Change its url and publish the site record again, and the check runs again.

While standardSitePublication is set the path is ours. A file of your own at /.well-known/site.standard.publication, or a redirect from or to it, is refused as ReservedPathCollision: you would be giving two answers, and we refuse rather than pick one.

A file of your own still works. Without the field, /.well-known/site.standard.publication is an ordinary path, and a manifest entry holding the address serves like any other file. What it lacks is what the field adds. It is not readable from other origins, so a verifier running in somebody's browser cannot read it, and nothing checks that the address is in your account or that the publication says it lives here. Use the field for anything a verifier will read.

Either way, a sign-in gate over / covers this path too, so a signed-out verifier gets the sign-in page. Leave the path outside the gate.

When your domain is also your handle

Your account has two names on the network. One is a DID, usually a did:plc:… string, and it never changes. The other is a handle, and a handle is a domain name pointing at that DID, which can be a domain you own instead of the one your account came with.

There are two ways for the domain to point back: a DNS TXT record at _atproto.<domain>, and a file served over HTTPS at /.well-known/atproto-did. Either is enough on its own. A resolver that consults both, as ours does whenever somebody signs in with a handle, treats a disagreement as an error and not as a preference.

_atproto.<domain> is not the _bard.<domain> record that proved this domain to us. Two different records at the same domain, and we never accept one in place of the other. Bringing your domain alongside covers that one.

That HTTPS address is the one the atproto flag controls. With the flag on and no entry of your own there, we answer it with the DID of the account that published the site record. If that is the same account the handle belongs to, the answer is already right and there is nothing here for you to do.

It goes wrong in one situation. If example.com is the handle of account A, and the site at example.com is published from account B, then we answer with B, an account that is not the one the handle names. Anything resolving your handle over HTTPS gets the wrong account, and anything that checks both ways gets a conflict and treats the handle as unresolvable.

What to check. Two commands, with your own domain in place of the example:

dig +short TXT _atproto.example.com
      curl -s https://example.com/.well-known/atproto-did
      

The first prints "did=did:plc:…" if you use the DNS method, and nothing if you do not. The second prints a DID. Wherever either prints something, it must be the DID of the account the handle belongs to. If the second names a different account, handle resolution is broken for anything that asks over HTTPS, and there are three ways out:

  1. Publish the site from the account the handle belongs to. Nothing else changes, and the answer is correct from the next publish onward.
  2. Serve the path yourself. A manifest entry at /.well-known/atproto-did overrides ours. It is the one documented exception to the paths we reserve, and it must hold the correct DID and nothing else. The exception only holds while atproto is on: a record that both defines that entry and sets the flag false is contradictory and is refused as a ReservedPathCollision. The Hub refuses that path when you add files, with This path is reserved by Highport and cannot be a manifest key, so this route needs a record you write yourself.
  3. Turn atproto off. Only when _atproto.<domain> already exists and holds the right DID, or when the domain is not used as a handle at all.

Off means 404, permanently and unconditionally. It does not mean we step aside so something else can answer. With the flag false, a manifest entry at that path is refused, your not-found page never applies there, and the address returns 404 to everyone forever. The DNS record is then the only thing left answering for your handle, which is why it has to be there first.

A redirect can never point at that path or away from it, in either state of the flag. A resolver that follows a 302 away from /.well-known/atproto-did has been pointed at somebody else's account, so we refuse the rule instead of serving it.

One thing that is not a problem: if your handle is me.example.com and your site is at example.com, those are two different hostnames and none of this touches either of them.

Site owners · 5 of 12