Activating the controlled update channel
The vendor serves a signed channel at the singular update.downpipes.io, and the engine’s pinned release-signer public key verifies it. On the shipped base config the signer is pinned and the channel URL is set. The live confirmation is GET /admin/updates reporting verified: true: it has somewhere to look and the host serves a channel that verifies under the pinned key.
Activation is one-time Owner work outside the product. You pin the release-signer key the vendor prints, or confirm the pinned one. You point the engine at the vendor’s signed channel, and you redeploy. You run no channel host. The engine fetches the channel only from https://update.downpipes.io, and any other host is a configuration fault (fetchAndVerifyChannel, engine/src/admin/updates.ts).
This page is for the owner who runs that ceremony, and it says what to do if the vendor replaces the release-signer key. It is the prerequisite for applying updates, which is where the day-to-day flow lives. None of this gates a backup or a recovery: an update is never required for either.
Activation switches on the verify-and-review side and deploys nothing. Applying a live update and rolling one back are separate, explicit actions, each needing a one-shot Cloudflare deploy token you supply at the moment you act, so neither can happen as a consequence of turning the channel on.
What you are turning on
The engine updates itself by pulling a vendor-signed channel document from inside your own account and verifying it against a release-signer public key you have pinned. A release can also carry the console as a component, applied by the same engine under the same one signature. The vendor never pushes code, and never holds a token that can rewrite your Workers. This is the deliberate alternative to a vendor-held deploy credential, which would be a worse custody risk than the data itself.
The engine ships the verify side. It is presence-gated on two settings and is otherwise a pure pull-and-verify, so activation is a handful of operator actions plus one offline vendor action.
The activation steps
Sign the channel offline (vendor action)
The vendor signs the channel document with the release-signer private key, offline, on a machine with no network. That signing tool lives outside the engine and console packages. It emits the channel JSON, its detached signature, and the public key for you to pin. The signature covers exactly the emitted bytes (the engine verifies over the bytes it fetches and then parses those same bytes, with no canonicalisation), so the document must be published verbatim. Re-serialising or reformatting it after signing breaks the signature. The release-signer private key never leaves that offline step. The vendor process is documented on publishing a signed release.
Publish the channel (vendor action)
The vendor publishes two objects at
https://update.downpipes.io. One is the channel body,stable.json. The other is the same URL with a.sigsuffix, holding the base64url signature on a single line. The engine trusts the pinned signature and not the host, so this is static signed content with no server logic. The host also carries the artefact bytes each release points at: the engine bundle and, for a release that carries a console component, the self-describing console artefact (console-<version>.json), both hash-pinned by the one signed document. The engine requests the channel only over https fromupdate.downpipes.io. A channel URL on any other host reports the faulturl-config, and the engine fetches nothing (fetchAndVerifyChannel,engine/src/admin/updates.ts).Pin the signer and point the engine at the channel
Set two values on the engine Worker. The engine reads each one as a plain string from its environment.
UPDATE_SIGNER_PUBLICis awrangler.tomlvar or a Worker secret. It is the printed release-signer public key (an Ed25519 32-byte half concatenated with the ML-DSA-87 public 2592-byte half, 2624 bytes, base64url). This pin is the whole security model.UPDATE_CHANNEL_URLishttps://update.downpipes.io/stable.json, set as a Worker secret or as that one line inwrangler.toml. The engine pins the channel host at run time. It requests nothing from a URL that is not https onupdate.downpipes.io(fetchAndVerifyChannel,engine/src/admin/updates.ts).A third setting exists for component-aware releases and is optional:
CONSOLE_WORKER_NAMEnames the console script a release’s console component may deploy to, and it defaults todownpipe-console, so set it only if your console Worker uses a different script name. The name alone is never trusted: before the engine touches that script it requires the script’s live bindings to include theENGINEservice binding pointing back at this engine, so a wrong name can never redeploy an unrelated Worker.Redeploy the engine
Redeploy so the engine picks up the two settings. Presence of both makes the status report
configured: true.GET /admin/updatesreportsverified: trueonce the channel athttps://update.downpipes.ioverifies under the pinned key.Confirm configured and verified
Read the engine’s update status. The status flag reports the channel as configured (presence only). The dedicated updates route does the real work: it fetches the signed document and its signature, verifies them under your pinned key, and returns whether the channel verified. Confirm that it reports verified true, and that the recommended version it returns is the one you signed.
Configured is not verified
The distinction is deliberate and it is the thing operators most often conflate. They are two different checks, answered by two different routes.
| Reading | Where it comes from | What it actually means |
|---|---|---|
configured | The status report flag updateChannelConfigured | Presence only: both UPDATE_CHANNEL_URL and UPDATE_SIGNER_PUBLIC are set. It says nothing about validity |
verified | The updates route, which fetches and checks the signature | The real result: the channel was fetched over HTTPS and its detached signature verified under the pinned signer |
The status flag is a pure truthiness check on purpose: it cannot leak a parse-derived detail of a secret, and it is cheap enough for the console to poll during setup. So a malformed pinned key still reads as configured at the status level. The verified result comes from the updates route, which never throws and never falls back to an unverified document. A wrong signer, a tampered body or an unfetchable URL all surface there as not verified, with a reason. If both settings are absent, the channel simply reports not configured, which is not an error.
The console reflects these states directly. The update tile reads “Channel not set” when the channel is unconfigured, and the signing tile reads “Configured, not verified” when the settings are present but the signature has not verified. Only when the channel is both configured and verified does the signing tile read “Signer verified”.
The trust property
The engine pulls and verifies; it never accepts a push, and nothing inbound is ever accepted. The document is fetched outbound over TLS with redirects disabled, so a redirected signed fetch cannot be steered to another host. The signature is the hybrid scheme used everywhere else in the product, an Ed25519 signature alongside an ML-DSA-87 signature, both halves required, so the channel is post-quantum hybrid signed. The engine verifies against the key you pinned, never a key the channel asserts about itself.
The custody story is the point of the whole design. The vendor never holds a credential that can rewrite your Workers, and the release-signer private key lives offline and is never deployed. A compromised channel host therefore cannot substitute an unsigned or wrong-signer document, because the engine checks the pinned signature rather than trusting the host. For the deploy step of an actual apply, the only credential involved is a one-shot token scoped to the “Edit Cloudflare Workers” template (Workers Scripts: Edit), collected in the console at apply time and used once; it is distinct from your full account deploy credential and is never stored.
A release-signer fingerprint is pinned via the UPDATE_SIGNER_PUBLIC setting and that pin alone. There is no compile-time bake of the fingerprint into the engine binary, so do not rely on the engine printing a baked-in fingerprint or on a recovery sheet carrying one; the pin is the environment value you set in step three.
GET /admin/status reports updateSignerFingerprint, the edmldsa1: fingerprint of the key the engine verifies updates with. The console shows it as “Update-signer fingerprint” in the Provenance section of the Licence and updates screen. If you set RELEASE_SIGNER_PIN, set it to that fingerprint. The engine compares the two and shows a warning that names the fix when they differ.
If the release-signer key changes
Your engine verifies the channel under one pinned key, UPDATE_SIGNER_PUBLIC. A release cannot change that pin. An update you apply from the console sends every plain setting back unchanged, so the pin stays as it was.
If the vendor replaces the release-signer key, for example after a compromise, you must re-pin each engine by hand. The vendor publishes the new key’s fingerprint and a security advisory. Check the fingerprint, then set UPDATE_SIGNER_PUBLIC to the new key in wrangler.toml and redeploy with npm run deploy.
Until you re-pin, your engine trusts only the old key. A channel signed with the new key reads as not verified, so the engine offers no update. After a compromise, a channel signed with the old key can still verify, so apply no update until you have re-pinned.
The generated deploy config cannot drift the channel settings
The shipped base config points at the singular update.downpipes.io host and pins the key. A bare wrangler deploy is destructive to bindings: it replaces the worker’s whole binding set with exactly what wrangler.toml lists, so it would drop any source attached from the console. Deploys instead run through scripts/sync-bindings.mjs, which reads the live worker’s current bindings and writes wrangler.deploy.toml: a generated, gitignored file that is the committed wrangler.toml plus those live source-binding stanzas appended. Deploying that generated file (npm run deploy, or wrangler deploy -c wrangler.deploy.toml) carries the console-attached sources forward instead of dropping them.
Nothing in that generation step touches the update settings, so wrangler.deploy.toml carries the same update lines as wrangler.toml. A value held as a Worker secret stays on the Worker. There is no separate deploy-only copy of these settings to reconcile.
What activation does not do
Activation makes the channel verifiable and reviewable. It deploys nothing, and it changes no running Worker. Two further actions are yours to take, and each needs a one-shot deploy token.
Applying an update is the first. The apply reads the target script’s live settings, preserves every binding and secret, and refuses the upload rather than promote a version it cannot prove will keep them; an uploaded version is inert until it is promoted, and a promote that fails leaves the prior version serving, because Cloudflare deploys are atomic. A rollback is the second, and running one deliberately proves the revert direction end to end on your own deployment. The engine records a rollback target the first time you apply an update from the console, and the standalone Roll back control deploys that recorded version and flies a canary flight on it; with no recorded target it refuses rather than deploy anything blind. Plan both rather than meeting them for the first time during an incident; the mechanics are on applying updates and the recovery controls on update trust and rollback.
Where this fits
Once the channel reports verified, move to applying updates for the review, preview and one-click apply flow. For the brick-safety and trust model in depth, including the standalone rollback and the opt-in gradual ramp, read update trust and rollback. For the vendor side of signing and publishing a channel, see publishing a signed release. For the scopes a deploy token needs, see Cloudflare token scopes.
Last updated .