Skip to content
downpipes docs

How the update channel stays safe: trust model, rollback, and gradual rollout

This page explains why the controlled update channel is safe to use and how its recovery controls work. It is for a self-hoster who wants to understand the model rather than just follow the steps: what is trusted and what is verified, how a bad version is undone, what the opt-in gradual rollout really does, and where the limits sit. The day-to-day flow is on applying updates, and turning the channel on is on activating updates.

An update channel can carry two components under one signature: the engine Worker, and, when a release ships one, the console’s static assets, each with its own apply and rollback path. Your archives are objects in your own bucket that no update rewrites, and your recovery runs offline from those bytes and your recovery kit, so neither is touched by a deploy. The worst a bad release can do is a brief, rollback-able availability blip on the live engine and, if a console component shipped, the console. The rest of this page explains how.

Pull, not push; verify before deploy

Nothing is ever pushed to your engine. The engine pulls a vendor-signed channel from inside your own account and checks it against a key you pinned during activation. Two guards then stand between a verified channel and a live deployment.

The signature and artefact guards

The signature guard is the pinned hybrid signature: an Ed25519 signature alongside an ML-DSA-87 signature, both halves required, verified against your pinned key and never a key the channel asserts. The artefact guard is a SHA-384 check: before anything is uploaded, the engine recomputes the downloaded bundle’s digest and requires an exact match against the hash the signed channel declared. A missing or mismatched hash is a refusal, and nothing is deployed.

The deploy credential is a one-shot token scoped to the “Edit Cloudflare Workers” template, collected at apply time and never stored. If your engine binds a Secrets Store secret as a backup source, add Secrets Store edit to the token: the template does not include Secrets Store, and an apply is refused at the upload-version step without it. A standalone rollback never uploads a version; it only deploys an already-uploaded version id, so the template token on its own is enough for a rollback. See applying updates for the full permission list.

One signature covers every component

The one signed document covers every component of a release, not just the engine. A components map describes each one: the engine as a worker module, and the console as a static-assets bundle, a single self-describing artefact carrying the console’s shell worker, every file it serves with a per-asset SHA-256, and its static config. The same pinned signer, the same detached hybrid signature and the same freshness guards cover each component’s hash and metadata, so nothing new is trusted: no second host, no second key, no new trust surface. The SHA-384 check runs per component, and the engine-only artefact list is published alongside the map as the engine’s fallback when the map has no engine entry; a console artefact is never placed in it, so the engine can never select console bytes for its own script.

The console artefact gets full discipline

The console artefact is code that runs in the operator’s browser, the same browser that generates break-glass keys. It gets the full engine-grade discipline: its whole bundle is hash-covered by the signed channel, including the shell worker that sets the console’s security headers, so a console update cannot silently weaken those headers without the hash saying so.

A guard against dropped bindings

There is a third, quieter guard that prevents the most insidious brick. When the engine uploads a new version, Cloudflare replaces the binding set rather than merging it, so a version that forgot a binding would come up without its archive, its Durable Objects or its signing secret. The driver therefore re-sends every binding the engine had: each non-secret binding verbatim, and the secret binding types listed so Cloudflare preserves the secrets and their values. If it encounters any binding type it cannot prove it can carry forward, it refuses the upload rather than risk dropping it. Binding preservation is enforced, not assumed. The console deploy holds the same line: the apply reads the console script’s live settings first and preserves its bindings, including the ENGINE service binding and its assets wiring, rather than trusting the artefact to know your install.

The console ownership proof

Deploying to a second script raises a question the engine refuses to leave implicit: is that script actually this deployment’s console? The engine learns the console’s script name from CONSOLE_WORKER_NAME (default downpipe-console), but a name is configuration, not proof. Before the apply touches the console script, it reads the script’s live bindings. The apply requires the ENGINE service binding to point back at this engine, the script named by WORKER_NAME. If that proof fails, the console component is refused and nothing is uploaded.

The proof also bounds where a deploy can land. A Cloudflare API token cannot be scoped to a script name, so the one-shot token you paste could technically edit any Worker in the account for as long as you hold it. What the engine enforces is the target: the apply writes only to the script named by WORKER_NAME and, for a console component, only to the script that both carries the configured console name and proves ownership through its ENGINE binding, never to any other script.

Sequenced with gates, with partial outcomes reported

When a release moves both components, the order is fixed: the engine applies first and must settle to a keep verdict before the console component applies. A live apply is refused outright on an engine with no destination configured, because there would be no canary flight to gate the keep with. A new console may call new engine API, and engine API changes are additive, so engine-new with console-old is the direction that is always safe; applying the console first could not make that guarantee.

The failure semantics never pretend two Workers are one atomic unit. If the engine refuses or rolls back, the console is not applied: the release was not applied, and the console is unchanged. If the engine settles and the console upload or promote then fails, the console auto-rolls back to its recorded target and the engine keeps the new version; that partial state is reported as a partial state, and the console component is retriable alone. True cross-worker atomicity is impossible on the platform, so the design is sequencing plus gates plus the additive-compatibility rule.

The settle itself is judged with patience and recorded with discipline. Only a dead canary, a bit strayed on the new code, rolls the engine back immediately; a verdict that is neither alive nor dead is retried on a short bounded schedule before anything is decided, because the settle runs in the window where the code swap has just reset the engine’s Durable Objects and a transient failure there is expected. A verdict still unresolved after those retries keeps the verified new version and marks the record as awaiting confirmation from the scheduled canary, rather than reverting a release the check could not fault. And the settled outcome is persisted before any rollback deploy is issued, then surfaced on the update status. That ordering matters because a rollback deploy swaps the running code out from under the very request reporting the outcome; persisting first means the console can always re-read the recorded outcome instead of guessing. Without that rule the operator’s only signal would be a generic error raised after the version had already been reverted, which is the one moment the rule removes.

The standalone rollback

Rollback is a first-class control, always available, and independent of any in-flight apply.

Triggering a rollback

It does not require that an update be mid-flight; the engine records a known-good version the first time you apply an update from the console and keeps it until you are back on a known-good version, so a target plausibly exists whenever an apply has happened.

When you trigger it, the engine resolves the recorded known-good version, reads the current live version so it knows what it is reverting away from, deploys the known-good version (atomically), and canary-verifies that it landed healthy. If the live deployment is already that version, it is a clean no-op. If there is no recorded target, it refuses rather than deploying anything blind.

No second-owner approval on revert

By design, a standalone rollback needs no second-owner approval. Rollback is the safe recovery direction, the same direction the automatic settle rollback takes, and gating it would impede incident response by leaving a lone owner unable to revert an unhealthy engine. So dual control is kept on the consequential direction (trusting new code) and never on the revert.

Per-component rollback

Rollback is per component too. A console rollback is a first-class control of its own: engine-driven with the same one-shot token, back to the console’s recorded target, offered one-click by the console when its post-apply self-check sees the wrong version serving, and available alone without touching the engine. And the anti-rollback floor is kept per component: the engine and the console each remember the newest version they have successfully settled, meaning canary-passed rather than merely attempted, so a correctly-signed channel that recommends an older version is refused for that component.

The downgrade-to-recover opt-in does not cross that floor, and reaching for it mid-incident wastes time. The floor check runs first and unconditionally, and refuses even with the opt-in set, because pushing an engine below a version it has already run would reintroduce an already-patched vulnerability. An older target that cannot be compared to the floor at all is refused too, failing closed. What the opt-in actually relaxes is the forward-only guard against the version currently running, and it may cross the compatibility floor for a same-or-forward re-pin. If you need to go back, the standalone Roll back control is the path: it targets a recorded known-good version by id and is deliberately exempt from the floor. System-controlled backward motion through that control stays allowed; channel-driven backward motion stays refused.

The console states a rollback as a controlled, safe outcome, never an alarm: The version the engine landed back on is named in the actual message, so the live line reads, for example, “Rolled back to <version>. The canary sings on it; your data and recovery were never affected.” where <version> is the recorded known-good Cloudflare version id, not a fixed phrase. Even a rollback whose canary could not confirm health reports that your data and recovery were never affected, because a rollback does not change your archives and restore is out of band. A rollback that failed outright also reports that nothing was half-applied.

The opt-in gradual ramp serves real traffic

A normal apply is atomic: promote to one hundred percent, canary-gate, keep or auto-rollback. The gradual ramp is a separate, explicit, per-release opt-in, off by default. It uploads the new version, ramps a fraction of live traffic to it (one to ninety-nine percent) with the same verify-before-deploy, migration and compatibility guards as the atomic path, then canary-gates the new code. If the canary sings, the ramp holds at that percentage awaiting an explicit promote-to-one-hundred; if it does not, the whole deployment is rolled back to one hundred percent on the prior version. The ramp is engine-only: a static-assets swap is atomic at promote and has no traffic-percentage concept, so a ramp request naming the console is refused.

This changes when you should use it. A ramp serves the new version to a fraction of your real production traffic. It is not an isolated preview or a sandbox. An isolated preview URL would need a workers.dev host, which downpipes does not use, so the ramp is the feasible substitute rather than a risk-free trial. The console states this as a standing caveat on the ramp control itself, before you ever run one. That caveat is not something the outcome message repeats back to you.

The practical consequence is that for a single, low-traffic engine, the atomic promote plus canary is the recommended default. A ramp earns its keep when you have enough live traffic that a percentage slice is a meaningful sample; on a quiet engine it mostly adds steps without adding signal, and the atomic path is already canary-gated and reversible.

Release metadata, alerts and dual control

What the channel carries

The channel carries more than a version number, and all of it is under the same signature, so it is tamper-evident without a new trust surface.

FieldWhat it isHow it is used
changelogStructured change lines grouped by typeShown in the “What’s in this update” panel
impactHuman impact notesShown alongside the changelog
requiredStepsExplicit operator actions, optionally blockingShown as a prominent “Required” badge; the console keeps Update now disabled until every blocking step is ticked, one checkbox per step, so an operator cannot sleepwalk past an action the release needs. The gate is the console’s: the engine deploys only what it verifies and does not read the acknowledgement. A non-blocking step is informational only and does not gate
minEngineVersionThe oldest engine a release may apply ontoThe compat guard refuses an apply onto too-old an engine
compatA human note paired with the floorShown when a release has a compatibility floor
riskClassroutine, migration, or breakingDrives the console tone and the dual-control rule

Alerts are proactive, never automatic

Alerts are proactive but never act. A once-per-version alert fires on the cron when a newer version is available, so you learn about an update without polling; it never auto-applies. Applying is always operator-driven.

Dual control is targeted, not blanket

Dual control is targeted, not blanket. When the dual-control gate is on, a second Owner’s approval is required for a migration or breaking release. That approval is bound to the specific target version and artefact hash, never to the deploy token. A routine release applies with a single Owner click even when the gate is on, which keeps the common patch case fast. An owner can also turn on the update-approval policy (POST /admin/config/update-approval-policy). With that policy and the dual-control gate both on, every live apply needs a second Owner’s approval, whatever the risk class.

An unlabelled or unrecognised risk class is treated as migration-class, so an unlabelled or garbled channel gets the second-owner approval rather than slipping through as routine. The settle step gates the same way, on the keep direction only; the rollback and the dry-run are never gated.

Components change none of that shape. The release-level risk class is the default and a component may carry its own override, so a console-only hotfix can ship as routine even in a series whose engine release was migration-class. Dual control gates a live apply as one action: one approval per apply, regardless of how many components the release carries, never a second approval queue.

Provenance: recorded at apply, published with the release

Provenance has two halves, and each half is owned by the party that can actually prove it.

The vendor half rides the signed channel. A release published through the attested ceremony carries a per-component provenance block inside the signed body: the source commit, the release tag, the CI run that built and attested the artefact, the Rekor transparency-log index of its keyless signature, and channel-relative paths to the attestation files (the SLSA statement, the cosign bundles, the checksums file and the release record). The offline release signature covers those pointers, and the ceremony refuses to sign bytes CI did not attest, so a present block means the offline signer checked the attestation at signing time. A release whose channel entry carries no block shows as “not published for this release” in the console, with nothing linked. Verify a release yourself is the recipe for checking all of it independently.

The customer half lives in your own records. At apply time the engine verifies the downloaded bytes against the signed digest and persists that digest on the pending and settled lifecycle records and in the hash-chained audit log, so which content hash went live is durably provable in your own account. Between upload and promotion the engine additionally reads the just-uploaded version back from Cloudflare’s own API and hashes it against the signed digest: the read-back verdict is recorded on the same records, and an unavailable read is recorded as exactly that rather than assumed verified. The gate runs in warn mode by default, recording evidence on every apply; enforce mode, which refuses promotion on anything but a verified read-back, is selected by setting UPDATE_READBACK_MODE to enforce, and a refusal there can never brick, because nothing has been promoted yet and the previous version keeps serving.

Two Security Centre checks cover the time between applies. update-apply-provenance confirms the last applied update left content-hash evidence. update-version-drift compares the running deployment’s version identity (the version_metadata binding) against the version the last verified apply left live, so a redeploy outside the update flow, whether a deliberate wrangler deploy or an unexpected actor, flips a visible finding instead of passing unremarked. Both ride into the signed evidence packs where a framework maps them: the drift check under ISO/IEC 27001 A.8.16, the provenance check under NIS2 Article 21(2)(d).

A running Worker cannot hash its own deployed bytes at runtime, so the engine’s self-stamped artefact SHA-384 is documentation rather than a verified assertion; the values above are the verified ones because they were computed and recorded at apply time. The hash that actually gates a deploy is always the signed channel’s SHA-384 over the downloaded artefact, unaffected by anything the engine self-reports.

The console records its own version identity the same way. The console build stamps a version into the bundle and emits it at a build-stamp path on the console’s own origin. The Provenance card shows the engine version and this console’s own version side by side. After a console apply, the page checks that its origin is actually serving the new version before it claims success, and offers the one-click console rollback when it is not. The stamp is documentation of what is deployed, not a verified assertion; the hash that gates a console deploy is always the signed channel’s, checked before upload.

A freshly checked-out engine reports no artefact hash. The stamp is applied at build, and the committed source ships only a placeholder sentinel, so a source checkout or any unstamped build reports no hash rather than the placeholder. Do not expect a raw git checkout to print its own digest.

What the model does not do

These are the limits of the model.

The engine reports a Cloudflare version id (cfVersionId) on its status, but no console screen surfaces it. Use the version names the apply, settle and rollback flows show you, not a Cloudflare version id in the console.

The provenance cross-check is on-demand, not a standing badge. The Provenance section can fetch the published release record and compare it against the digest your account verified, but it does so when you ask (and says so when the channel host refuses browser reads); the standing indicators are the two Security Centre checks, which grade evidence presence and version drift, not a live byte comparison. Workers offers no runtime attestation of the executing isolate.

Auto-rollback only happens inside the apply or the settle request. The scheduled canary can detect an unhealthy live version and raise a critical alert, but the engine holds no standing deploy credential, so it cannot revert itself out of band: reverting always takes an operator supplying a one-shot token. Once a settle has decided, use the recorded outcome on the update status as the result.

Where this fits

For the step-by-step apply, preview and one-click flow, see applying updates. To turn the channel on in the first place, see activating updates. For the operator-CLI upgrade path and the migration and schema-epoch rules that bound a rollback, see upgrades and rollback. For the canary that gates every keep decision, see the canary, and for the hybrid signature scheme, see cryptography.

Last updated .