Edge deployment guide

Cloudflare Workers Deployment, Done Safely

Release a bounded edge handler through isolated environments, a measurable canary, and a known-good version that can be restored without rebuilding under pressure.

Published
Updated
Reading time
21 min read
On this page

A Cloudflare Workers deployment that only has to work once is easy; one that has to work the same way every week, across every edge handler a business depends on, is an operational discipline. That is the same bar a managed edge-orchestration layer holds itself to: staged rollout, measurable canary, and a tested reversal path, applied consistently whether the runtime behind a given route happens to be Cloudflare's or another partner's.

Overview

Outcome and Acme Shop scenario

Outcome

Acme Shop will deploy a typed, public catalogue Worker first to staging, upload the production candidate without immediately serving it, then create a small version split. The release record identifies the stable version, candidate, stop conditions, and wrangler rollback command.

Running scenario

The Worker serves GET /edge/catalog by fetching Acme Shop's public catalogue API. It does not handle checkout, account, inventory writes, or credentials. The upstream URL is non-secret configuration; any upstream credential would be an independently managed Worker secret and is never printed or committed.

Acme Shop Worker release flow
  1. Stage

    Deploy to staging and run synthetic checks.

  2. Upload

    Upload the production candidate without sending it traffic.

  3. Canary

    Create a small version split, observe, and promote only when gates pass.

  4. Recover

    Restore the known-good version when a stop condition is met.

The old and new version can both serve requests during a gradual deployment, so their contracts must be compatible.

Cloudflare Workers are a strong fit for request shaping close to users: authentication checks, redirects, cache policy, header normalization, lightweight API composition, and routing between services. They are not a default replacement for every backend. Start by deciding whether the workload is stateless and bounded enough for an edge runtime, then deploy it through explicit environments with a tested reversal path.

This guide is for DevOps and platform engineers operating Workers as production infrastructure. It assumes that the application team owns the handler logic while the platform team owns deployment controls, secrets, traffic exposure, and operational visibility.

Cloudflare Workers Deployment Prerequisites

Before changing traffic, have the following in place for this Cloudflare Workers deployment:

  • A Cloudflare account with permission to create and deploy Workers, manage the target route or custom domain, and read Worker analytics.
  • A local installation of Wrangler authenticated to the intended account. Cloudflare documents supported installation and login methods in Get started with Wrangler.
  • A Git-backed Worker project with a reviewed wrangler.jsonc, wrangler.json, or wrangler.toml configuration.
  • Separate non-production and production deployment environments, with distinct secrets and bindings where required.
  • A rollback owner, an alert destination, and a known-good version already deployed before enabling a new route or version.
  • A decision on Workers observability defaults for each environment: whether Logs are enabled, and at what sampling rate, before the first candidate ships rather than after an incident.

Stage 1: Confirm That the Workload Fits the Edge

Define the Worker’s contract before choosing bindings or writing a route. A good edge contract has a small, deterministic request path and delegates durable transactions, long-running work, or large data processing to appropriate backend services.

Check the current platform constraints against the proposed behavior:

  • Review Workers limits for CPU time, memory, request, subrequest, and script-size limits. Limits vary by plan and can change, so treat the documentation as the source of truth for the account you will deploy.
  • Review runtime APIs for compatibility rather than assuming Node.js server APIs are available.
  • Ensure any outbound dependency is reachable from the Worker and has explicit timeouts and failure handling in the application code.
  • Keep personally identifiable data, credentials, and tenant-specific configuration out of source code and plain-text vars when they are secrets.

CPU time and wall-clock time are not the same budget, and conflating them is a common edge-fit mistake. Cloudflare's currently documented Free plan limits CPU time to 10 ms per HTTP request and does not allow that ceiling to be raised; the paid plan defaults to 30 seconds of CPU time per request, configurable up to a 5-minute ceiling. Time spent waiting on fetch(), a KV read, a D1 query, or any other network call does not count toward CPU time — only time the CPU spends executing your code does. Acme Shop's AbortSignal.timeout(1_500) in the handler below bounds how long the Worker waits on the upstream catalogue API; that is a wall-clock control, not a CPU-time one. A Worker that does heavy synchronous JSON transformation, templating, or cryptography on every response can exhaust its CPU budget before any network timeout fires, so review CPU time against the target plan whenever the handler does non-trivial work on the request or response body, not just when it calls a slow upstream.

Stateful business logic caveat

Workers can coordinate state through products such as Durable Objects, KV, D1, R2, Queues, and external services, but an individual Worker invocation is not a durable transactional application server. Do not place inventory reservation, payment settlement, account mutation, or other exactly-once business workflows solely in request-local Worker code. Design an authoritative backend, idempotency keys, retry behavior, and a durable audit trail. If using Durable Objects, understand their location and concurrency model from Durable Objects.

Stage 2: Define Configuration, Secrets, and Bindings

Keep non-secret deployment configuration in the Wrangler file and provision secrets through Wrangler or Cloudflare’s dashboard. Cloudflare explicitly notes that vars are not encrypted; use Worker secrets for credentials and tokens. See Secrets.

An environment-scoped configuration might look like this:

name = "edge-gateway"
main = "src/index.ts"
compatibility_date = "2026-07-14"

[env.staging]
name = "edge-gateway-staging"
vars = { API_ORIGIN = "https://api.staging.example.com" }

[[env.staging.kv_namespaces]]
binding = "CONFIG"
id = "staging-namespace-id"

[env.production]
name = "edge-gateway-production"
vars = { API_ORIGIN = "https://api.example.com" }

[[env.production.kv_namespaces]]
binding = "CONFIG"
id = "production-namespace-id"

The binding declarations must match the handler’s env interface and the target resources must exist in the target account. Use the documentation for the relevant resource type when adding bindings; the Wrangler configuration reference lists binding configuration and environment inheritance rules.

Set each environment's secret independently. An immediate secret update creates and deploys a Worker version, so use it only for a non-gradual environment such as staging:

wrangler secret put UPSTREAM_TOKEN --env staging

For a production gradual deployment, use the version-aware command in the same reviewed release workflow as the candidate, then deploy that version through the approved split:

wrangler versions secret put UPSTREAM_TOKEN --env production
wrangler versions deploy --env production

Cloudflare documents that older Wrangler versions may require --x-versions. Confirm the installed version supports wrangler versions secret put before the change window; if it does not, upgrade Wrangler or use Cloudflare's reviewed version-aware workflow rather than falling back to an immediate production wrangler secret put. Do not put a production secret in a preview, staging, or repository configuration. Treat secret rotation as an operational change: deploy code that accepts both old and new credentials if the upstream requires a staged rotation, then remove the old value after verification.

Turn on Workers observability before you need it

Workers Logs are not on for a new Worker by default. Add an observability block to the Wrangler file and redeploy before the change window, not during an incident:

[observability]
enabled = true
head_sampling_rate = 1

[env.production.observability]
enabled = true
head_sampling_rate = 0.1

head_sampling_rate accepts a value from 0 to 1 and defaults to 1 (every invocation logged) when omitted. Acme Shop keeps staging at full sampling, where volume is low and every request matters for the acceptance gate, and lowers production sampling once the catalogue route is stable and high-volume, to control log cost. A lower sampling rate does not have to mean losing visibility into failures: pair head-based sampling with a Tail Worker or a Logpush filter that always forwards non-2xx responses and exceptions regardless of the sampling decision, so a rare error is never sampled away from the dashboard that a canary gate depends on.

Acme Shop typed handler under review

This handler accepts only the one public read route and does not forward cookies, authorization headers, or arbitrary client headers to the upstream. It uses a bounded timeout and returns safe, generic errors. The explicit no-store avoids accidentally creating a shared cache policy in the Worker; cache policy can be reviewed separately.

export interface Env {
  API_ORIGIN: string
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url)

    if (url.pathname !== "/edge/catalog") return new Response("Not found", { status: 404 })
    if (request.method !== "GET") return new Response("Method not allowed", { status: 405 })

    const upstream = new URL("/catalog", env.API_ORIGIN)
    upstream.search = url.search

    try {
      const response = await fetch(upstream, {
        headers: { Accept: "application/json" },
        signal: AbortSignal.timeout(1_500),
      })

      if (!response.ok) return new Response("Catalogue temporarily unavailable", { status: 502 })

      return new Response(response.body, {
        headers: {
          "cache-control": "no-store",
          "content-type": response.headers.get("content-type") ?? "application/json",
        },
      })
    } catch {
      return new Response("Catalogue temporarily unavailable", { status: 503 })
    }
  },
} satisfies ExportedHandler<Env>

Use an Env interface that matches every declared binding. Keep the staging origin distinct from production and have TypeScript validate the handler before deploying. If a future route needs an authenticated upstream, use a Worker secret and a narrowly scoped server-to-server contract; do not pass client credentials through the Worker.

Stage 3: Deploy to an Isolated Environment

Run static checks and application tests first. Then validate the resolved Wrangler configuration and deploy the staging environment:

wrangler deploy --env staging

Wrangler environments let one project declare separate deployment targets. Confirm the deployed worker name, account, routes, bindings, compatibility date, and any environment-specific settings in the command output and Cloudflare dashboard. The environments guide explains what is and is not inherited by named environments; do not assume a production binding or variable is automatically present in staging.

If the Worker is attached to a route, verify route precedence and that the route pattern is intentionally narrow. A broad pattern can intercept traffic that was previously served by another Worker or by the origin. For custom domains and routes, use the documented routing configuration.

Stage 4: Test Safely Before Production

Use a non-production hostname or a deliberately narrow staging route. Exercise the request path end-to-end, not only a local handler test.

Test at least the following:

  • Expected success responses, methods, headers, cache behavior, and CORS behavior.
  • Missing or invalid credentials, malformed inputs, and authorization boundaries.
  • Upstream timeouts, DNS failures, 5xx responses, and rate-limit responses.
  • Every binding path, including behavior when optional configuration is absent.
  • Route matching with representative production-like URLs, including paths that must bypass the Worker.
  • The response and logging behavior when a secret or required binding is deliberately unavailable in staging.

Use the following command for fast local feedback:

wrangler dev

Do not treat local development as equivalent to production. Cloudflare distinguishes local and remote development modes in Wrangler development; remote dependencies, routes, bindings, and account configuration still require an environment deployment test.

Stage 5: Workers Observability in Staging

Instrument the Worker with structured, non-sensitive events that identify the route, outcome class, upstream dependency, and deployment version. Avoid logging authorization headers, cookies, request bodies, access tokens, or customer identifiers.

During the test window, inspect:

  • Worker error rate and exception samples in the dashboard.

  • Request volume and response status distribution compared with the known-good path.

  • CPU time and other resource signals that may approach documented limits.

  • Upstream latency and error metrics from the authoritative service, since a successful Worker response can still conceal degraded origin behavior.

  • Inspect logs when live request-level diagnosis is needed:

    wrangler tail --env staging
    

Cloudflare documents available telemetry and tailing workflows in Observability. Retain metrics and logs in your central observability platform if the incident process requires longer retention or correlation with origin signals.

Choosing where the telemetry lives

Workers observability spans four distinct mechanisms, and picking the wrong one for the question you are asking wastes the observation window:

  • Workers Logs in the Cloudflare dashboard, once enabled per the previous stage, is the fastest path to a filtered, queryable view of a single Worker's recent invocations without any external system.
  • wrangler tail streams events live during a deploy or an incident; it is a diagnostic session, not a retention mechanism, and stops the moment the terminal closes.
  • Logpush exports Workers Trace Event Logs continuously to R2, S3, or a third-party log destination, which is the mechanism to reach for when the acceptance criteria require retention beyond the dashboard's window or joins against origin or WAF logs stored elsewhere.
  • OpenTelemetry export sends Workers trace data over OTLP to an external stack such as Grafana, Honeycomb, or Axiom, which matters once the catalogue Worker is one hop in a request that also touches other services and the useful unit of analysis is the whole trace, not one Worker's logs in isolation.

Pick the mechanism that matches the question in front of you: a live tail answers "what is happening right now," Workers Logs answers "what happened to this Worker recently," and Logpush or OTel export answer "what happened across this system over the retention period the incident process requires."

Stage 6: Run an Edge Canary Rollout

Deploy the production environment only after staging has met its acceptance criteria:

wrangler deploy --env production

For a versioned canary, do not use the immediate wrangler deploy path above for the candidate. Cloudflare's current gradual-deployment workflow uploads a version without deploying it, then creates a split deployment interactively. Confirm the installed Wrangler version supports this workflow before the change window.

wrangler versions upload --env production
wrangler versions deploy --env production

Choose the known-good and candidate version IDs in the interactive deployment, start with the smallest exposure justified by traffic volume, and record the chosen percentages. A split is per request by default, so consecutive requests can reach different versions. Keep API and Durable Object changes backward and forward compatible; a Durable Object migration cannot be safely handled as an ordinary gradual deployment rollback.

Representative Wrangler output; version IDs shortened
Uploaded edge-gateway-production version: 3a0f...c91e
No deployment changed.

Deployment created
95%  version 81d2...0a44  (known good)
 5%  version 3a0f...c91e  (candidate)

Prefer a limited exposure mechanism before a full route cutover. Depending on the architecture, that can be a dedicated canary hostname, a narrowly scoped route, a controlled cohort chosen by the application, or Cloudflare Worker versions and deployments when available for the account and deployment model. Do not claim a fixed latency improvement or assume traffic splitting removes the need for functional monitoring; cache location, origin behavior, code paths, and client geography determine observed results.

A canary is only as trustworthy as the telemetry watching it

Attribute every metric, log line, and alert to a specific Worker version before trusting a percentage split — a dashboard that blends versions together will hide a regression inside an average. This is the same discipline a managed observability layer such as MYO applies when an edge canary rollout runs behind more than one provider: normalize version and deployment identifiers into one view so a 5% candidate on Cloudflare is held to the same gates as a 5% candidate anywhere else in the estate, instead of being judged from a provider-specific dashboard that only shows part of the picture.

Automate the split for a repeatable canary

An interactive prompt is fine for a one-off release, but a repeatable process should script the split so the same version IDs and percentages are reviewed in the change record rather than chosen ad hoc at a terminal. Once both version IDs are known, Wrangler accepts a non-interactive form:

wrangler versions deploy \
  81d2f0a1-3b7c-4e21-9a5d-000000000a44@95% \
  3a0fbb21-7c4d-4a6e-8b12-0000000c91e0@5% \
  --message "Acme Shop catalogue canary, step 1" \
  -y

Keep the --message field meaningful; it becomes part of the deployment history the dashboard and API expose, and it is the fastest way for an on-call responder to see what changed without re-deriving it from version IDs. Template this command from the release record and drive it from whatever change-management step already gates a production Cloudflare Workers deployment, rather than leaving the exposure percentage for someone to type correctly by hand.

Version skew, affinity, and stateful edge cases

A percentage split is evaluated per request by default, so the same browser tab can hit the known-good version on one request and the candidate on the next. For Acme Shop's stateless catalogue read this is harmless; it becomes a real defect class once a route touches sessions, multi-step forms, or content-hashed static assets that must match the HTML page that referenced them. Cloudflare's version affinity mechanism pins a caller to one version by hashing a value carried in the Cloudflare-Workers-Version-Key header — typically set from a session cookie, an authenticated user identifier, or the client IP for anonymous traffic — so consecutive requests from the same caller stay on one version until they cross over as the candidate's percentage grows, and callers already on the newer version do not flip back without an explicit rollback. Affinity is normally set through a zone Transform Rule for traffic on a controlled route; it is not available on a bare *.workers.dev hostname unless the header is set by the client or an upstream Worker.

Version affinity keeps a caller on one Worker version

Per-request splits suit stateless routes. Use version affinity when a caller must not alternate between compatible but different Worker versions.

Download:PNGSVG

Durable Objects introduce a second, unrelated form of skew. Because each Durable Object instance is a single globally unique actor, code changes roll out in an eventually consistent way: a request can reach the new Worker version while the Durable Object it calls is still running the previous version for a short window, typically seconds to minutes. A new version deploy also restarts every Durable Object it touches, which disconnects any WebSocket currently open on it. Treat a canary on a Durable-Object-backed Worker the same way as a Durable Object migration: confirm the RPC or storage contract is compatible in both directions before exposure, and expect connected clients to reconnect during the release rather than assuming a canary is transparent to long-lived connections.

Define explicit canary gates before exposure:

  • No material increase in Worker exceptions or 5xx responses compared with the baseline.
  • No breach of upstream service error or saturation thresholds.
  • No regression in critical synthetic checks or authenticated user journeys.
  • Resource use remains below the operational safety margin chosen from Cloudflare’s current limits.

Increase exposure only after a sustained observation period appropriate to request volume. Low-volume services need longer observation or synthetic traffic to generate meaningful evidence.

Validation matrix

CheckExpected resultStop and investigate when
Positive: public catalogue GET200 JSON response, expected schema, Cache-Control: no-storeContent type, schema, or latency budget regresses
Negative: POST /edge/catalog405; upstream is not calledThe Worker forwards or mutates an unsupported request
Negative: /checkout404 from this Worker route; checkout remains on its intended pathA broad route intercepts checkout or account traffic
Failure: staging catalogue API timeout503 with no credentials or upstream details exposedUnbounded wait, exception spike, or sensitive diagnostic response
Canary: version splitCandidate metrics stay within agreed error and latency gatesCandidate errors, origin saturation, or synthetic failures rise

Rollback

Prepare the reversal command and decision threshold before the production deploy. For a route-based release, remove or narrow the new route only if doing so restores the prior known-good handler or origin path. For a versioned Worker deployment, roll traffic back to the known-good version using the Cloudflare-supported deployment workflow. Refer to rollbacks for Worker versions for the current command and dashboard behavior.

wrangler rollback [<version-id>] is the versioned path: it immediately creates a new deployment that routes 100% of traffic to the version you name, or to the previous version if omitted, replacing whatever split was active rather than adjusting it. Rollback does not touch bound resources — KV namespaces, R2 buckets, D1 databases, queues, and secrets stay exactly as configured — so it is only safe against data drift if the shape the older code expects has not changed underneath it. Cloudflare blocks the rollback outright if a Durable Object migration happened between the active deployment and the target version, or if the target binds to a KV namespace, R2 bucket, or queue that no longer exists; treat either case as a forward fix, not a rollback. Only the 100 most recently published versions remain eligible targets, so periodically confirm a relied-upon fallback version has not aged out of that window on a Worker that deploys frequently. For a scripted runbook, pass an explicit message to skip the interactive confirmation:

wrangler rollback 81d2f0a1-3b7c-4e21-9a5d-000000000a44 \
  --message "Acme Shop catalogue rollback: canary error-rate gate breached"

After rollback:

  1. Confirm the known-good path is receiving requests.
  2. Re-run synthetic and representative endpoint checks.
  3. Keep observing both Worker and origin errors until they return to baseline.
  4. Preserve the failed deployment identifier, configuration diff, logs, and timeline for incident review.

Do not use a rollback as a substitute for data repair. If the release performed stateful effects, reconcile those effects in the system of record using the business workflow’s documented recovery procedure.

Troubleshooting

SymptomLikely causeSafe diagnosticRecovery
Candidate serves 404 unexpectedlyRoute pattern or environment route was not deployedInspect the resolved production route and test the exact canary URLCorrect the narrow route, validate in staging, then upload a new candidate
Valid request becomes 502Upstream returned a non-success responseCompare non-sensitive Worker status metrics with upstream status metricsRestore the stable deployment; fix the upstream contract before retrying
Requests time out at the edgeUpstream is slow or timeout budget is too smallReview Worker and origin latency without logging request bodiesRoll back the candidate or adjust the reviewed bounded timeout after staging tests
Canary metrics mix versionsNo version attribution or per-request version skewUse deployment IDs and version-aware telemetryAdd safe version metadata to observability and keep versions contract-compatible
Rollback fails or is unsafeBinding/resource changed, or Durable Object migration occurredCompare candidate and stable bindings/migrations before actionUse the documented forward fix or resource recovery; do not force an incompatible code rollback
WebSocket clients drop mid-rolloutA version deploy restarted the Durable Object backing the connectionConfirm the timing against the deployment log, not client-side retry noiseDesign for reconnect on any Durable-Object-backed release; do not treat it as a Worker defect
Canary looks clean but incidents still surfaceLog sampling is hiding the failures the dashboard would otherwise showCheck the effective head_sampling_rate for the environment and any Tail Worker error-forwarding filterRaise sampling for the observation window or add a filter that always forwards non-2xx responses and exceptions

Technical Caveats

Worker behavior depends on the Cloudflare plan, compatibility date, enabled products, account configuration, and the specific region and dependency path serving a request. Review Cloudflare’s current limits and product documentation at each material design or platform upgrade. Edge execution can reduce work at the origin for suitable requests, but it does not guarantee lower end-to-end latency, availability, or consistency for every request.

Primary Sources

Make every Cloudflare Workers deployment safer to operate

Talk to Optimi about edge canary rollout gates, Workers observability, and delivery visibility across Performance, Security, and Visibility for critical application paths.

Discuss edge delivery