Highport orbital control

How your site reaches people

You publish, a pointer moves, and the next request gets the new thing. Nothing between Orbital Control and your reader keeps a copy, so there is no cache to clear.

Publish a change and it is live in a second or two, or up to about two minutes on a domain under continuous traffic. The rest of this page is why. Between a visitor's browser and your stored bytes there is a short, fixed path: our edge, then one of several origin replicas, then the file. Nothing on that path keeps a copy of your pages for later. There is no purge to issue and no invalidation step. What you may wait on is not somebody's cache holding your content. It is our own note of which version is current, expiring on its own.

The path a request takes

# Where What happens
1 DNS Your domain resolves to our edge, because of the traffic record you created when you set up the domain.
2 The edge TLS is terminated here, and a certificate is obtained during the first handshake that needs one.
3 The gate The edge decides whether to serve this request. There are two ways it says no: the path is one you have asked visitors to sign in for, or the bandwidth allowance covering the request is spent. Otherwise the answer is yes and nothing about the request changes.
4 The origin One of several replicas reads the Host header, finds the snapshot currently active for your domain, and looks the request path up in its manifest.
5 The bytes The stored file streams back. Nothing is rendered, no template runs, and no request-time code of yours executes, because there is none.

Plain http:// requests are answered with a permanent redirect to https://, so every visitor to a site here is on HTTPS.

The edge and the origin's rules are the mechanism in full, when you want it.

Your certificate

You do not obtain one, install one, renew one or pay for one. The first HTTPS connection to your domain obtains one during the handshake itself, it is stored and reused by every edge node from then on, and renewals happen on their own.

In practice that first connection is usually not a visitor's. Once your domain is verified we make an HTTPS request to it ourselves, so issuance has normally already happened by the time anyone arrives.

A certificate is only obtained for a domain whose registration here is verified or already serving. A domain that is pointed at us but is still pending, or has been suspended or released, gets a failed handshake instead. That is a browser TLS error, with no page and no status code to explain it, because a handshake that does not complete has nowhere to put one. The fix is to finish setting the domain up, not to retry.

How fast a publish lands

Publishing changes your site record. We validate it, fetch every file it names, build a new snapshot, and then move your domain's pointer from the old snapshot to the new one. That pointer move is the moment your site changes: before it, requests get the old snapshot; after it, the new one. There is no state in between where a visitor sees half of each. How the station notices covers how long that work takes, and every way it can end.

Nothing needs purging afterwards, because nothing between us and your visitor keeps a copy. We run our own edge and it does not cache. There is no CDN in front of it.

What the replicas do keep is the pointer itself, meaning which snapshot your domain currently points at, and it is kept in two places. There is one copy shared by every replica, which lasts 60 seconds, and each replica also holds its own copy for 60 seconds. A replica whose own copy has expired refills it from the shared one, which by then may be up to 60 seconds old itself, so the two can run end to end. A publish is usually live in a second or two, and up to about two minutes on a domain under continuous traffic.

The busy domain is the slow one, which is the counter-intuitive half. The shared copy is written only when a replica has to look the pointer up, and reading it does not extend it. So a domain nobody has requested lately has no copy left to serve from and changes over at once, while a domain under steady traffic always has one.

Every replica is also told the instant the pointer moves, and drops its own copy when it hears. That message is worth having, but it is not what bounds the wait: the next request refills that copy from the shared one, which the message does not clear. The two expiries above are the bound whether the message arrives or not.

Immediately after the move we also fetch / and /index.html from each replica ourselves, so the first visitor after a publish is not the one paying for the cold read.

A replica keeps the snapshot and the file bytes it has already read, and neither can go stale. A snapshot is named by the record version it was built from, and a file by the hash of its contents, so a publish asks for different objects rather than for newer copies of the same ones.

The only cache left between us and your reader is their own browser. Orbital Control has no jurisdiction there and never will.

Your visitor's browser

"No cache in the way" is a statement about shared caches: ours, a CDN's, a proxy's. A browser cache is a different thing, it is nobody's to clear but its owner's, and it is the reason you can publish a change and still be looking at the old page.

Every ordinary response carries Cache-Control: public, max-age=60. A browser that fetched a file less than a minute ago may reuse its own copy without asking us anything. After that minute it asks, and it asks cheaply: a response carrying a file also carries an ETag that is the content hash of the exact bytes, so a file that has not changed comes back as 304 Not Modified with no body at all.

So a first-time visitor sees your change immediately, and a returning visitor sees it within about a minute. A page already open in a tab fetches nothing further until somebody reloads it. And if you publish and your own browser still shows the old version, that is your browser holding its copy for its minute. A reload that bypasses the cache shows what everyone else is getting.

Response Cache-Control
any file from your manifest public, max-age=60
your notFound document public, max-age=60
a redirect public, max-age=60
our plain not-found page public, max-age=60
the "nothing published here yet" page public, max-age=30
/.well-known/rasl/{cid}, where a file is requested by its own content hash public, max-age=31536000, immutable
a refusal: 400, 403, 405, 414, 431, 503 none sent

We never send Last-Modified and never read If-Modified-Since. Freshness here is a content hash, which is exact, rather than a timestamp, which is not.

Compression

Files are compressed on the way out, and you can take that job over by storing compressed bytes yourself.

We compress a response as it is sent when five things hold: the visitor accepts Brotli or gzip, the status is 200, the file is 4 MiB or smaller, its content type is one we compress, and the result is actually smaller than the original. The types we compress are everything under text/, anything ending +json or +xml, and a short list of others including application/json, application/javascript, application/wasm and image/svg+xml. Images, video and woff2 are compressed already and are served as stored. A compressed result is kept, so a busy page pays for compression once rather than once per visitor. The origin's rules has the exact list.

The alternative is to store already-compressed bytes and say so. Set contentEncoding on the manifest entry to gzip, br or zstd, and those bytes are served exactly as stored, with the matching Content-Encoding header, never recompressed and never decompressed. It is the cheapest option at serve time and the only one that works for a file over 4 MiB. The site record is where that field lives.

Two things follow from declaring an encoding, and both are yours to get right.

The declaration is taken at its word. We do not check that the bytes are actually in the encoding you named. A file declared br that is not Brotli is sent with Content-Encoding: br, and the browser fails to decode it. Nothing errors on our side and nothing warns you. That one entry is simply broken until you correct the record.

A visitor whose Accept-Encoding refuses your coding gets 406 Not Acceptable with an empty body. We do not decompress on their behalf, because you stored one representation of that file and it is the one they get. Declare a coding you are confident the clients you care about accept.

Large files and ranges

A response carrying a file also carries Accept-Ranges: bytes, so a video seeks and an interrupted download resumes.

Request Answer
one range, satisfiable 206 with Content-Range
one range, past the end of the file 416 with Content-Range: bytes */{size}
several ranges in one request 200 with the whole file. We never send a multipart body
a range on anything that is not a 200 ignored; the whole document is sent with its own status

A range is always a range of the stored bytes. If the entry is one you pre-compressed, the range is over the compressed bytes, because that is what its hash covers. Compression on the way out only ever applies to a whole 200 response, never to a range.

Files are streamed rather than read into memory, so a large file costs a replica nothing extra to serve. The 4 MiB ceiling is on compression alone, not on what you can serve.

What the response says

A page of yours, fetched with curl -sD- https://example.com/about.html, comes back roughly like this. The ETag, the length and the truncated security policy are illustrative; every header name is exact.

HTTP/1.1 200 OK
      Content-Type: text/html; charset=utf-8
      Content-Encoding: br
      Content-Length: 1837
      ETag: "bafkreiabcd..."
      Cache-Control: public, max-age=60
      Vary: Accept-Encoding
      Accept-Ranges: bytes
      Content-Security-Policy: default-src 'self'; ...
      Permissions-Policy: interest-cohort=()
      Referrer-Policy: strict-origin-when-cross-origin
      X-Content-Type-Options: nosniff
      X-Bard-Rev: 3lauf5jkxyz2b
      X-Bard-Did: did:plc:ewvi7nxzyoun6zhxrhs64oiz
      X-Bard-Cid: bafyreifk7o4rokf6cgkseo2zzxhwzxldb3jhovwnr7z4yq5uqz6tjcsova
      X-Bard-Outcome: hit
      

Content-Type is the only header in that response whose value your record chooses. It comes from the contentType you set on the entry, or from the file's own recorded type when you set none. The Content-Encoding line above it is the declaration from the previous section, echoed back rather than decided here. Everything else is ours. The four security headers are identical on every site we serve and no field in any record can move them. What cannot happen is where that guarantee is set out in full.

The four X-Bard- headers are diagnostics, and the useful one after a publish is X-Bard-Cid. It names the site record version currently being served. If it matches the record you just wrote, your change is what visitors are getting.

curl -sI https://example.com/ | grep -i '^x-bard'
      

X-Bard-Outcome says which kind of answer this was: hit for a file from your manifest, notfound, redirect, reserved for one of the paths we answer ourselves, or error.

When it goes wrong

A 503 means we resolved your domain and then could not read something we needed. It is ours, not yours, and nothing in your record will change it. Most are brief, and a retry a minute later fixes them. One that persists is a fault on our side to repair.

Our plain not-found page, headed This page is not part of this site, means the domain is serving and the path is not in your manifest. Publish a notFound document if you would rather answer that yourself. See redirects and missing pages.

"Nothing is published here yet" means the domain reached us but no site is active for it. The registration was never completed, it was released, it was suspended, or nothing has been published to it yet. The page says the same thing in all four cases, because the origin cannot tell them apart and should not be able to. Check the domain in the Hub.

An outage is visible immediately. Nothing here serves stale content when the origin cannot answer, because there is no shared cache holding an old copy to fall back on. Having no shared cache is why a publish needs no purge and lands inside the window above. It is also what puts an outage on the next request. They are the same fact.

A proxying CDN in front of us is a cache we do not know about, and it will not follow the headers above. Cloudflare's proxy, for example, caches by file extension rather than by Cache-Control, so a publish stops landing on the schedule above and starts landing on theirs. It has also been observed injecting its own script into HTML responses, which means the bytes a visitor receives are no longer the bytes your record published. If you want the guarantees on this page, point DNS at us without a proxy in the path.

Site owners ยท 12 of 12