Edge Runtime Limits — What You Can't Do at the Edge
Edge runtimes look like Node but aren't. Here's what actually breaks — CPU caps, no filesystem, no TCP sockets — and how we route around it.
Key takeaways
- Edge runtimes look like Node but aren't.
- Here's what actually breaks — CPU caps, no filesystem, no TCP sockets — and how we route around it.
On this page
Edge Runtime Limits — What You Can't Do at the Edge
We moved an auth-heavy API route to Cloudflare Workers expecting a free latency win. Deploy went green. Then a chunk of requests started returning 1102 "Worker exceeded CPU time" under load. The code ran fine locally on Node. That gap — runs on Node, dies at the edge — is the whole story, and it catches people every release.
Edge runtimes are not Node. Workers, Vercel Edge, and Deno Deploy are V8 isolates. You get JavaScript, the standard web platform (fetch, Request, Response, URL, streams, WebCrypto), and almost nothing from Node's standard library. No fs. No net. No child_process. No process-level threads. The isolate boots in single-digit milliseconds because there's no Node process behind it, and that speed is exactly why the APIs you lean on aren't there.
The four ceilings you'll hit#
CPU time, not wall-clock. This is the one that bit us. Workers meter CPU time — actual compute — separately from wall-clock. On the paid plan you get up to 30 seconds of CPU, but the default limit is 50ms, and the free plan sits near 10ms. Awaiting a slow subrequest costs wall-clock but almost no CPU, so I/O-bound code is fine. A tight loop hashing a password is pure CPU and blows the budget:
// Runs locally, times out at the edge under load
export default {
async fetch(req) {
const body = await req.text();
// bcrypt-style work factor = lots of CPU, zero I/O
const hash = await slowHash(body, { rounds: 12 });
return new Response(hash);
},
};
The fix wasn't a faster hash. It was recognizing that per-request key stretching doesn't belong in an isolate at all.
Memory, ~128MB. Per isolate. Load a 40MB model or buffer a large upload fully into memory and you'll OOM. Streaming is the escape hatch — pass the body through instead of collecting it.
Bundle size. Workers cap the compressed script around 1MB on the free plan, 10MB on paid. Vercel Edge Functions sit near 1–4MB depending on plan. That kills "just npm install it" for anything dragging in a Node polyfill tree. sharp, bcrypt, aws-sdk v2 — all too big or too Node-dependent to bundle.
Subrequests. Each Worker invocation caps outbound fetch calls (50 on free, 1000 on paid). A fan-out that hits twenty microservices per request quietly hits the wall.
You can raise the CPU ceiling and lower it to fail fast in wrangler.toml:
name = "auth-api"
main = "src/index.ts"
compatibility_date = "2024-09-23"
[limits]
cpu_ms = 50 # fail fast in staging; raise to 30000 only where the work is justified
No sockets means no normal DB driver#
Here's the sharp edge nobody warns you about: there are no long-lived TCP connections. Postgres, MySQL, Redis, Mongo — their native drivers open a raw socket and keep it warm through a pool. Isolates can't open sockets and don't persist between requests, so those drivers either fail to import or hang on connect.
// pg — opens a TCP socket. Doesn't work in a V8 isolate.
import { Client } from "pg";
const client = new Client({ connectionString: process.env.DATABASE_URL });
await client.connect(); // no socket API → dead on arrival
The pattern that works is an HTTP/fetch-based client talking to a proxy that holds the real pool: Neon's serverless driver, PlanetScale's, Supabase's PostgREST, Upstash for Redis. Same query, different transport.
// Neon over HTTP fetch — no socket, connection pooling lives upstream
import { neon } from "@neondatabase/serverless";
const sql = neon(process.env.DATABASE_URL);
const rows = await sql`select id from users where email = ${email}`;
Even here, watch the subrequest cap: every query is an HTTP call.
What breaks, and where it goes instead#
Heavy crypto (bcrypt, scrypt, large RSA keygen), image processing (sharp, ImageMagick bindings), PDF generation, anything importing fs or assuming __dirname, and fat SDKs written for Node — these don't belong in an isolate. This is the same tradeoff we walk through in our edge computing playbook: the edge is for cheap, I/O-shaped, latency-sensitive work, and the origin is for everything heavy.
Detection is mostly mechanical. Set your dev runtime to match production — wrangler dev or Vercel's edge target, not plain next dev — so a missing fs fails on your machine, not in prod. Then:
- Swap Node's
cryptoforcrypto.subtle(WebCrypto). SHA-256, HMAC, AES-GCM, ECDSA are all there and run native. - Move CPU-bound or filesystem work to an origin function (regular Lambda, a container) and call it over
fetch. - Stream request and response bodies instead of buffering.
- Buy back CPU headroom by caching upstream responses at the edge rather than recomputing.
WebCrypto covered our HMAC signing cleanly:
const key = await crypto.subtle.importKey(
"raw", new TextEncoder().encode(secret),
{ name: "HMAC", hash: "SHA-256" }, false, ["sign"]
);
const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(payload));
The call we'd make#
Treat the edge as a fast, dumb front door, not a place to port your server. Put routing, auth-token verification, redirects, header rewrites, cache logic, and HTTP-based DB reads there — work that's I/O-shaped and finishes in a few milliseconds of CPU. Keep anything that hashes, resizes, buffers big blobs, or wants a socket back at the origin. Run wrangler dev locally and pin a low cpu_ms in staging so the timeout shows up on your laptop instead of in a customer's 1102. Do that and the edge earns its latency win. Fight the isolate model and you'll spend the savings debugging CPU timeouts at 2am.
Stay Updated
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
AWS Graviton Migration: What Broke and What We Saved
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.
GitHub Actions Best Practices in 2026: Workflows You Can Trust
A production-focused GitHub Actions guide: reusable workflows, least-privilege permissions, keyless OIDC to the cloud, SHA-pinned actions, environments with approvals, concurrency-safe deploys, and CodeQL/Dependabot gates — with copy-paste examples.
More from Cloud
Explore more articles in this category
Best Serverless Databases in 2026 (Compared)
A practitioner comparison of the leading serverless databases by use case, cold-start behavior, branching, pricing model, and lock-in.
Cloudflare D1: The Edge SQLite Database Guide (2026)
A practitioner's look at Cloudflare D1, the serverless SQLite database built for Workers, covering setup, read replication, limits, and fit.
Neon vs PlanetScale: Serverless SQL Compared (2026)
A practitioner comparison of Neon's serverless Postgres against PlanetScale's Vitess-backed MySQL to help you pick the right database.
You might have missed
Evergreen posts worth revisiting.