{"id":401,"date":"2026-03-09T13:21:46","date_gmt":"2026-03-09T13:21:46","guid":{"rendered":"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/cloudflare-workers-vs-aws-lambda-which-edge-runtim\/"},"modified":"2026-03-18T22:00:06","modified_gmt":"2026-03-18T22:00:06","slug":"cloudflare-workers-vs-aws-lambda-which-edge-runtim","status":"publish","type":"post","link":"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/cloudflare-workers-vs-aws-lambda-which-edge-runtim\/","title":{"rendered":"Cloudflare Workers vs AWS Lambda at the Edge: Six Months of Production Reality"},"content":{"rendered":"<p>My team spent the better part of last summer arguing about which edge runtime to standardize on. Four engineers, one product manager who kept forwarding Hacker News threads, and a deadline that kept moving. We ended up <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/docker-compose-vs-kubernetes-when-to-use-which-in\/\" title=\"Running Both\">running both<\/a> Cloudflare Workers and AWS Lambda (including Lambda@Edge) <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/langchain-vs-llamaindex-vs-haystack-building-produ\/\" title=\"in Production\">in production<\/a> at the same time \u2014 not because we planned to, but because two different features got built by two different people with two different opinions.<\/p>\n<p>Six months later, I have opinions.<\/p>\n<h2>The Use Case That Made This More Than a Benchmark Exercise<\/h2>\n<p>We were building an API layer for an AI-powered image annotation tool. User uploads an image, we run lightweight ML inference at the edge to return bounding box data before the full model on our backend finishes processing. Low latency was the entire point \u2014 if the edge layer added more than 50ms we&#8217;d have been better off routing straight to our EC2 instances.<\/p>\n<p>The edge inference shim went on Workers. The authenticated API routing \u2014 user sessions, JWT validation, rate limiting against our Redis cluster in us-east-1 \u2014 started as a Lambda Function URL setup and gradually moved toward Lambda@Edge as we got smarter (and then less smart, and then smarter again) about global routing.<\/p>\n<p>So this isn&#8217;t synthetic benchmarks I ran on a Saturday. <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/bun-vs-nodejs-in-production-2026-real-migration-st\/\" title=\"Real Traffic\">Real traffic<\/a>: roughly 2.3M Workers requests and about 800k Lambda invocations per month at peak. A team that had to live with these decisions.<\/p>\n<h2>Cold Start Numbers After <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/deno-20-in-production-2026-migration-from-nodejs-a\/\" title=\"Six Months of\">Six Months of<\/a> Actual Traffic<\/h2>\n<p>Workers cold starts are effectively zero. I know everyone says this, but I was still surprised by how consistently true it holds. Cloudflare&#8217;s dashboard showed p99 cold start overhead at 2\u20135ms. The V8 isolate model is not a marketing trick \u2014 you&#8217;re not spinning up a container, you&#8217;re instantiating a JS context inside an already-running process. It really does work that way.<\/p>\n<p>Lambda was messier. Our functions were Node.js 20, bundled with esbuild, around 3.2MB zipped. Cold starts in us-east-1 averaged 280ms, occasionally spiking to 500ms after a long quiet period. With Provisioned Concurrency on our three most latency-sensitive functions, p99 came down to ~18ms \u2014 but Provisioned Concurrency costs money (I&#8217;ll get to that).<\/p>\n<p>Lambda@Edge specifically \u2014 and this is where I think a lot of comparisons gloss over \u2014 cold starts at edge PoPs are often <em>worse<\/em> than in a primary region. We saw 400\u2013600ms cold starts at some locations, particularly in Southeast Asia and parts of South America where Cloudflare actually has strong infrastructure. The reason: Lambda@Edge functions run in a subset of around 13 AWS regions, not at every PoP. So &#8220;edge&#8221; in Lambda@Edge terms means &#8220;closer than a single origin region&#8221; but definitely not &#8220;at the network edge.&#8221;<\/p>\n<p>One thing I noticed: Workers cold start behavior is globally uniform because every Cloudflare PoP runs the same isolate model. Lambda@Edge is not uniform \u2014 it depends entirely on whether a warm instance exists <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/rag-deep-dive-chunking-strategies-vector-databases\/\" title=\"in the\">in the<\/a> nearest serving region. I thought this would matter less in practice. It mattered more.<\/p>\n<p>If cold start latency is what&#8217;s driving your architecture decision, Workers wins and it&#8217;s not close. You can reach acceptable Lambda performance with Provisioned Concurrency, but you&#8217;re paying to solve a problem Workers doesn&#8217;t have.<\/p>\n<h2>The Night Lambda@Edge&#8217;s 1MB Limit Wrecked Our Image Pipeline<\/h2>\n<p>I pushed an update to the annotation API on a Friday afternoon. I know. I know.<\/p>\n<p>Lambda@Edge has a 1MB response body limit for origin-facing responses (40KB for viewer-facing). Our API was returning base64-encoded thumbnail crops alongside bounding box data. Some responses were hitting 1.2\u20131.4MB.<\/p>\n<p>Lambda@Edge silently dropped the response body and returned a 502. Not a useful error. Just CloudFront returning a 502. I spent three hours digging through CloudWatch Logs before I found a note buried <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/rag-deep-dive-chunking-strategies-vector-databases\/\" title=\"in the\">in the<\/a> AWS docs about response size limits. It wasn&#8217;t surfaced in any error message or CloudFront distribution event \u2014 just: your request failed, good luck.<\/p>\n<p>We refactored to return pre-signed S3 URLs for the thumbnail data instead of the raw bytes. Honestly a better architecture \u2014 smaller responses, client fetches from S3 directly, the annotation metadata comes back fast. But discovering a hard infrastructure limit because prod broke at 11pm is a terrible way to learn this.<\/p>\n<p>Workers has limits too \u2014 128MB memory, 50ms CPU time on the free plan, 30 seconds on paid \u2014 but they feel less surprising in practice. Wrangler will catch some of these before deploy. The documentation is upfront in a way that Lambda@Edge&#8217;s documentation wasn&#8217;t (at least when we hit this issue in mid-2025).<\/p>\n<p>The other Lambda@Edge frustration: you can&#8217;t use environment variables the normal Lambda way. Configuration has to be baked into the function code or fetched from Parameter Store at runtime, which adds latency and a surprising amount of boilerplate. Workers handles this cleanly \u2014 <code>wrangler.toml<\/code> bindings, a typed <code>env<\/code> object passed into your handler. Much cleaner model.<\/p>\n<h2>Workers&#8217; Runtime Isn&#8217;t Node.js, and That Cost Me a Full Week<\/h2>\n<p>Here&#8217;s the thing: Cloudflare Workers runs V8 with a web-standard API surface. Not Node.js. Most of the time this doesn&#8217;t bite you. But when you need a library that uses <code>fs<\/code>, <code>path<\/code>, <code>child_process<\/code>, or the Node.js <code>crypto<\/code> module (as opposed to Web Crypto) \u2014 or anything with native bindings \u2014 you hit a wall.<\/p>\n<p>We tried <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/05\/claude-vs-gpt-4o-vs-gemini-20-which-ai-model-to-us\/\" title=\"to Use\">to use<\/a> <code>jsonwebtoken<\/code> for JWT validation in Workers. Works fine on Lambda. In Workers, it blew up because it calls into Node&#8217;s <code>crypto<\/code> internally. The fix was switching to <code>jose<\/code>, which uses the Web Crypto API and works great everywhere. The actual migration was maybe two hours. The debugging was four days, because I was certain the problem was something else \u2014 a bundling issue, a wrangler config problem. The error messages Workers gives you when you accidentally touch a Node built-in are not always intuitive.<\/p>\n<pre><code class=\"language-javascript\">\/\/ Worked on Lambda, broke on Workers without nodejs_compat:\nimport jwt from 'jsonwebtoken';\nconst decoded = jwt.verify(token, process.env.JWT_SECRET);\n\n\/\/ What we switched to \u2014 works everywhere, cleaner key rotation story:\nimport { jwtVerify } from 'jose';\nconst secret = new TextEncoder().encode(env.JWT_SECRET);\nconst { payload } = await jwtVerify(token, secret);\n<\/code><\/pre>\n<p>Cloudflare has been expanding their Node.js compatibility layer \u2014 <code>nodejs_compat<\/code> in <code>wrangler.toml<\/code> handles a lot more than it did a year ago. Buffer, EventEmitter, stream \u2014 much of this works now. But if your existing Lambda code uses the broader Node ecosystem heavily, budget a real migration effort. Do not assume it&#8217;s a lift-and-shift.<\/p>\n<p>Lambda just runs Node.js. That&#8217;s a genuine advantage if you&#8217;re inheriting code you didn&#8217;t write, or if your team&#8217;s muscle memory is Node. The breadth of npm packages that run without modification on Lambda vs. Workers is not comparable right now.<\/p>\n<h2>The Cost Math for a Team of Four, <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/deno-20-in-production-2026-migration-from-nodejs-a\/\" title=\"Six Months\">Six Months<\/a> In<\/h2>\n<p>Real numbers:<\/p>\n<p><strong>Workers (paid plan):<\/strong> ~$12\/month for 2.3M requests. Workers charges $0.30 per million beyond the 10M included <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/rag-deep-dive-chunking-strategies-vector-databases\/\" title=\"in the\">in the<\/a> $5\/month base. Our CPU time stayed well within limits \u2014 the inference shim is lightweight, heavy computation happens on backend EC2. Billing was predictable.<\/p>\n<p><strong>Lambda + Lambda@Edge:<\/strong> This is <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/webassembly-in-2026-where-it-actually-makes-sense\/\" title=\"Where It\">where it<\/a> got complicated. Lambda compute itself was cheap \u2014 maybe $4\/month <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/edge-computing-in-2026-why-developers-are-adopting\/\" title=\"for Our\">for our<\/a> invocation count and average duration. Then add Lambda@Edge replication charges (your function gets copied across AWS regions, and you pay for that storage), CloudWatch Logs ingestion, and Provisioned Concurrency on our three latency-sensitive functions. All in: about $38\/month.<\/p>\n<p>Thirty-eight dollars is not a lot. But it was three times <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/docker-compose-vs-kubernetes-when-to-use-which-in\/\" title=\"What I\">what I<\/a> estimated from the Lambda pricing calculator alone, because the ancillary costs aren&#8217;t surfaced clearly. Workers pricing is more honest about what you&#8217;ll actually pay. I&#8217;ve started budgeting for AWS by taking the calculator number, adding 50%, and hoping for the best \u2014 which is not a compliment.<\/p>\n<p>I&#8217;m not 100% sure this ratio holds at significantly higher scale. At 100M requests\/month, Lambda&#8217;s per-invocation costs drop with compute savings plans, and the math might shift. If you&#8217;re operating at that scale you should model it carefully rather than trusting my numbers. My numbers are from a small team running moderate traffic.<\/p>\n<p>Workers KV can add up fast if you&#8217;re doing heavy reads. We were caching inference results there and it added another $8\u201310\/month at our read volume. Not a dealbreaker, just worth knowing upfront.<\/p>\n<h2>Where I&#8217;d Actually Put New Work Today<\/h2>\n<p>Look, I&#8217;ll give you the direct answer instead of a balanced take: Workers is where I&#8217;d start new projects.<\/p>\n<p>The case for Workers: cold starts don&#8217;t exist at meaningful scale, the ecosystem (Workers, KV, R2, D1) is solid enough now that I don&#8217;t feel like I&#8217;m assembling something experimental, and the developer experience with Wrangler is genuinely good. I&#8217;d have said otherwise 18 months ago. Not anymore.<\/p>\n<p>The case for staying on Lambda: if you&#8217;re already deep in AWS \u2014 IAM roles, VPC access, RDS connections, SQS queues \u2014 re-doing that in Workers is genuinely non-trivial. Same goes if your team&#8217;s operational muscle memory lives in CloudWatch and the AWS console. Those aren&#8217;t small things, and I&#8217;m not going to pretend the switching cost is nothing.<\/p>\n<p>We&#8217;ve started migrating our Lambda@Edge functions specifically. The edge performance gap is real and the hard limits kept surprising us in bad ways. But I&#8217;m not telling teams deep in AWS to rip things out \u2014 the integration surface is wide.<\/p>\n<p>My actual take: Workers for new projects. Lambda stays <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/webassembly-in-2026-where-it-actually-makes-sense\/\" title=\"Where It\">where it<\/a>&#8217;s doing useful <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/05\/claude-vs-gpt-4o-vs-gemini-20-which-ai-model-to-us\/\" title=\"Work in\">work in<\/a> existing AWS infrastructure. The overlap \u2014 cases where either would be genuinely fine \u2014 is smaller than most comparison posts suggest.<\/p>\n<p><!-- Reviewed: 2026-03-09 | Status: ready_to_publish | Changes: removed \"Practical takeaway\" label, tightened cold-start conclusion, added cost-section personality line, restructured final section away from parallel lists, added \"Look\" opener per style rules --><\/p>\n","protected":false},"excerpt":{"rendered":"<p>My team spent the better part of last summer arguing about which edge runtime to standardize on.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[1],"tags":[],"class_list":["post-401","post","type-post","status-publish","format-standard","hentry","category-general"],"_links":{"self":[{"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/posts\/401","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/comments?post=401"}],"version-history":[{"count":7,"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/posts\/401\/revisions"}],"predecessor-version":[{"id":556,"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/posts\/401\/revisions\/556"}],"wp:attachment":[{"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/media?parent=401"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/categories?post=401"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/tags?post=401"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}