We moved a rewrite-heavy request path off Lambda@Edge to Workers and cut p95 from 340ms to 41ms. Here's when that swap pays off and when it doesn't.
Our A/B header-injection logic lived in a Lambda@Edge function triggered on viewer-request. It did maybe 3ms of actual work: read a cookie, pick a bucket, set a header. But p95 for that hop sat at 340ms on cold regions, and CloudWatch showed cold starts firing every few minutes in the smaller edge locations. Users in São Paulo and Mumbai felt it worst. That function was the whole reason we started looking at Cloudflare Workers.
Lambda@Edge runs a full Node process (or Python) in a micro-VM. You get the whole Node standard library, filesystem access to /tmp, and up to 128MB for viewer triggers. The price is cold starts. Even a trivial function pays init cost when a location hasn't run it recently, and Lambda@Edge replicates to regional edge caches, not to every PoP.
Workers run on V8 isolates. No container, no process boot. An isolate spins up in single-digit milliseconds and Cloudflare keeps the script warm across 300-plus PoPs. The tradeoff: you're not in Node. You get web-standard APIs (fetch, Request, crypto.subtle, URL), and until recently anything expecting fs or native addons just failed. nodejs_compat closed a lot of that gap but not all of it.
Here's the Worker that replaced our Lambda:
export default {
async fetch(request) {
const cookie = request.headers.get("cookie") || "";
const bucket = cookie.includes("exp_v2=1") ? "b" : "a";
const res = await fetch(request);
const out = new Response(res.body, res);
out.headers.set("x-exp-bucket", bucket);
return out;
},
};
Same logic, deployed with wrangler deploy. After a week, p95 for the hop was 41ms and cold-start-shaped spikes disappeared from the trace.
Don't read the above as "always pick Workers." Lambda@Edge earns its keep when you're already deep in AWS. If your function needs to sign requests to a private S3 bucket with SigV4, read from DynamoDB in the same account, or assume an IAM role, Lambda@Edge does that natively with an execution role. Doing the same from a Worker means managing static credentials or fronting things with an API, which is more moving parts.
Lambda@Edge also gives you four trigger points: viewer-request, origin-request, origin-response, viewer-response. The origin triggers only fire on cache misses, so you can do expensive rewriting without paying per viewer request. Workers run on every request unless you gate them with the Cache API yourself.
And payload limits differ in ways that bite. Lambda@Edge caps viewer-request bodies you can access and has a hard 5-second timeout on viewer triggers. Workers give you 50ms of CPU time on the free-ish plans and up to 30s of wall-clock on paid, but CPU time (not wall time) is what's metered, which trips people up.
Lambda@Edge bills per request plus GB-seconds, and the per-request rate is higher than regular Lambda. At around 200M requests/month our old bill for that one function was roughly $180, most of it request charges, not compute.
Workers Paid is $5/month for 10M requests, then $0.30 per additional million, with CPU time billed separately. The same 200M requests landed near $62. For high-volume, low-compute edge logic, Workers is cheaper by a wide margin. For low-volume functions that do heavy per-invocation work tied to AWS services, the gap narrows and can flip.
Two things cost us a day each. First, event.request.headers in Lambda@Edge is an object of arrays with lowercased keys, structurally nothing like the standard Headers object in Workers. Any shared code assuming one shape breaks on the other. Second, Lambda@Edge silently strips some headers (Host, Connection, several X-Amz-*) and rejects others. Workers let you set almost anything, so a header your origin depended on being stripped suddenly showed up and confused it.
We also lost CloudWatch's automatic per-trigger metrics. Workers give you wrangler tail and Workers Analytics, which are fine, but if your on-call runbooks assume CloudWatch dashboards, budget time to rebuild them.
If the work is request/response shaping, redirects, auth-at-edge, or header logic that isn't chained to AWS-native services, Workers win on cold starts, PoP coverage, and cost. Move it. If your edge function is really an extension of your AWS backend, needs IAM, or only runs on cache misses where per-request cost matters, keep it on Lambda@Edge. We migrated the header and redirect logic to Workers and left one SigV4-signing origin function on Lambda. Splitting by dependency, not by ideology, was the right move.
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
Least privilege fails when it's a one-time audit that locks things down until something breaks, then gets reverted. The iterative, log-driven approach that tightens permissions safely — and the policies we stopped writing by hand.
A bad deploy used to mean a pager at 2am and a manual rollback. Now Argo Rollouts watches the error rate and aborts the canary itself before anyone wakes up.
Explore more articles in this category
We ran secrets three different ways across AWS, GCP, and Vault. External Secrets Operator gave us one Kubernetes-native workflow. Here's the setup and the gotchas.
Moving our fleet from x86 to Graviton promised 20% savings. We got 31%, but only after fixing native dependencies, a broken base image, and one nasty perf regression.
A p99 that jumped to 3.4 seconds during traffic ramps turned out to be cold starts. Here's how we measured them properly and cut the tail, with real init timings.
Evergreen posts worth revisiting.