Edge API Gateway versus Regional API Gateway
After working through this guide you will be able to decide, per gateway concern rather than per system, whether authentication, rate limiting, routing and payload transformation belong in edge isolates or in a regional managed gateway such as AWS API Gateway, an ALB with a Lambda authorizer, or a self-hosted Kong. You will also have a benchmark procedure that produces defensible numbers for your own traffic instead of a vendor’s.
The two architectures are not competitors so much as different placements of the same four jobs. A regional gateway sits directly in front of the services it protects: it shares their VPC, their database, their identity infrastructure, and their failure domain. An edge gateway runs the same policy code in a few hundred locations, each one milliseconds from a user and a continent away from the data. Everything below follows from that single difference — what a policy decision costs, where the state it needs can live, and how far a mistake travels.
Key decision criteria:
- Latency to first byte for a request that gets rejected, measured from where your users actually are.
- Where the state each policy needs lives, and whether it tolerates eventual consistency.
- Runtime constraints — CPU time, memory, library size, and the absence of raw TCP sockets at the edge.
- Blast radius, data residency, and how faithfully you can test the thing before shipping it.
Latency to first byte for a rejected request
Accepted traffic has to reach the service either way, so the honest comparison is the rejected request — the expired token, the over-quota key, the blocked country. In the edge shape that response is generated at a point of presence typically 5 to 20 milliseconds from the user. In the regional shape the request crosses the network to the gateway’s region, is rejected there, and the 401 crosses back.
That gap is pure geography and it does not shrink with better code. For a Sydney client hitting us-east-1, a rejected request costs roughly 200 milliseconds more than it needs to, and every retry the client makes costs it again. If your rejection rate is low and your users share a continent with your region, this dimension is close to a tie. If you serve a global audience or you are absorbing credential-stuffing traffic, it dominates everything else — which is the whole argument for doing JWT validation at the edge.
Where the state lives
A policy is only as portable as the data it reads. Static checks — signature verification against a cached JWKS, header shape, path allowlists, geography — need nothing but the request and a key set, so they run anywhere. Anything that reads a database row or a shared counter is a different problem, because the edge has no connection to your VPC.
At the edge you get environment variables and secrets replicated everywhere, key-value storage that is fast but eventually consistent, and single-instance coordination objects for anything that must be counted exactly — the pattern behind rate limiting API requests at the edge. A regional gateway simply opens a pooled connection to Redis or Postgres, which is why “look up the tenant’s plan and its feature flags” is trivial there and a design exercise at the edge.
The practical rule: a check whose inputs are already in the request belongs at the edge; a check that needs a consistent read of your own database belongs beside the database, unless you are willing to replicate a projection of that data to an edge store.
Cold starts, runtime limits, and cost
Edge isolates start in under a millisecond because the runtime is already resident and only your script’s global scope has to be evaluated; there is no per-request container to spin up. A Lambda authorizer has a genuine cold start — typically 100 to 600 milliseconds for a JVM or a fat Node bundle — which lands on exactly the traffic you most want to be fast: the first request after a quiet period. Provisioned concurrency removes the cold start and replaces it with a standing bill. A container-based Kong or an always-on ALB has no cold start at all, but you now own capacity planning.
The constraints run the other way. Edge isolates cap CPU per invocation (10 ms on free tiers, configurable up to 30 seconds on paid plans), cap memory around 128 MB, cap script size, and expose no raw TCP sockets — no native database driver, no gRPC client, no long-lived connection pool. A regional runtime hands you a full operating system: any driver, any library, gigabytes of memory, minutes of execution. Trying to port a heavyweight authorizer to an isolate unchanged is the most common failed migration.
Cost inverts too. Edge platforms bill per request plus CPU milliseconds and generally do not charge for bandwidth from the PoP; a rejected request costs a fraction of a CPU millisecond. A regional gateway bills per million requests plus the compute behind it, and — the line people forget — the NAT gateway and cross-AZ or cross-region data processing charges for traffic that was never going to be served. Rejecting at the edge removes both the compute and the egress for that traffic.
| Dimension | Edge isolate gateway | Regional managed gateway |
|---|---|---|
| Reject latency (distant client) | 5–20 ms, PoP-local | 150–250 ms, full round trip |
| Cold start | None; isolate is resident | 100–600 ms, or pay for provisioned concurrency |
| CPU per request | 10 ms free / up to 30 s paid | Seconds to minutes |
| Memory | ~128 MB | 128 MB – 10 GB |
| Raw TCP sockets | Not available; HTTP subrequests only | Full driver support |
| Consistent shared state | Single-instance coordination objects | Redis / Postgres in the VPC |
| Egress on rejected traffic | None billed | NAT and cross-AZ processing still billed |
| Blast radius of a bad deploy | Global within ~30 s | One region, or one stage |
| Data residency control | Coarse unless jurisdiction-pinned | Explicit by region |
| Local fidelity of tests | Same runtime locally | Container parity, network mocked |
Blast radius, residency, and testability
A deploy to an edge gateway reaches every location in well under a minute. That is the feature and the risk: there is no natural region-by-region rollout, so a bad policy is globally bad almost immediately. Mitigate it with versioned routes, a canary hostname carrying a fraction of traffic, and a rollback that is a redeploy of the previous version rather than a code fix. A regional gateway rolls out per region and per stage by construction, so the same mistake is contained — at the cost of a slower, more manual promotion pipeline.
Data residency pulls hard toward the region. If a regulator requires that request bodies for EU subjects never leave the EU, the default edge behavior of running your code wherever the user lands is a problem; you need jurisdiction-restricted placement, and you need to prove it. Regional gateways answer this question with a region name.
Testability is closer than it looks. Local emulators run the same isolate runtime that production runs, so behavior matches almost exactly, but you cannot easily emulate 300 locations or realistic inter-region latency. Regional gateways are the reverse: containers reproduce faithfully, while the managed gateway’s own authorizer caching, throttling and mapping-template behavior are hard to reproduce off the platform. Both shapes need a real staging environment; neither can be fully asserted from a laptop.
A benchmark that produces defensible numbers
Vendor latency claims are measured from somewhere convenient. Measure from where your users are, on your own token and quota logic, and compare a rejected request — the case where the two architectures genuinely differ.
You need curl 7.75+, hey (or vegeta), and a small VM in each region you care about. Deploy both gateway shapes in front of the same test endpoint so only placement varies.
curl --version | head -1 # 7.75.0 or later, for --write-out %{time_starttransfer}
hey -h 2>&1 | head -1 # load generator
- Deploy the same policy twice — once as an edge Worker, once behind the regional gateway — with identical
401bodies, so payload size is not a variable. - Mint one deliberately expired token. Every measured request must be rejected.
- From each region, warm the path with 20 requests and discard them, so TLS session resumption and any cold start are excluded from the steady-state numbers.
- Measure single-request time to first byte 200 times per region and per shape.
- Repeat under concurrency to expose queueing and authorizer-cache behavior.
for host in edge-gw.example.com regional-gw.example.com; do
for i in $(seq 1 200); do
curl -s -o /dev/null -H "Authorization: Bearer $EXPIRED_JWT" \
-w "$host %{time_starttransfer}\n" "https://$host/v1/ping"
done
done | awk '{a[$1]=a[$1]" "$2} END {for (h in a) print h, a[h]}' > ttfb.txt
hey -n 2000 -c 50 -H "Authorization: Bearer $EXPIRED_JWT" \
https://edge-gw.example.com/v1/ping
The shape of the result is predictable enough that a large deviation means your test is wrong, not that physics changed. These are p50 milliseconds for a rejected request against a gateway whose region is us-east-1:
Read three things out of a run like this. Edge numbers are flat with distance, because the rejection never leaves the metro. Regional numbers are essentially the network round trip plus a few milliseconds of gateway work. And in the gateway’s own region the two are within noise of each other — which is exactly why a single-region product with regional users gains almost nothing from moving policy to the edge.
Choosing per concern, not per system
| Concern | Put it at the edge when | Keep it regional when |
|---|---|---|
| Token validation | Tokens are signed asymmetrically and a JWKS is public | Sessions are opaque and need a store lookup |
| Rate limiting | Limits are per key and a small error is acceptable | Quotas are billed and must be exact to the request |
| Routing | Rules match on host, path, header or geography | Routing depends on service discovery inside the VPC |
| Transformation | Reshaping headers, URLs and JSON envelopes | Payloads are large, binary, or need a heavy schema library |
| Aggregation | Upstreams are HTTP and reachable from the edge | Upstreams speak a binary protocol or need a driver |
| Audit logging | Sampling and correlation IDs suffice | Every record must land in a specific jurisdiction |
The hybrid that most teams actually ship
The end state is rarely one or the other. Reject at the edge, enrich in the region: the edge verifies the signature, applies coarse per-key limits, blocks obvious abuse, attaches a correlation ID and a verified subject header, and forwards what remains. The regional gateway keeps the checks that need a consistent read — exact billing quotas, entitlement lookups, tenant-specific routing — and trusts the edge’s verified headers rather than re-parsing the token. The result cuts the volume reaching the region by whatever share of traffic is junk, usually a large number, while keeping the state-dependent logic beside the data.
export default {
async fetch(req: Request, env: Env): Promise<Response> {
const verified = await verifyToken(req, env); // edge-side, request-only checks
if (!verified) return new Response("unauthorized", { status: 401 });
const fwd = new Request(req);
// never trust a client-supplied copy of a header the edge is authoritative for
for (const h of ["x-auth-sub", "x-auth-scope", "x-edge-verified"]) fwd.headers.delete(h);
fwd.headers.set("x-auth-sub", verified.sub);
fwd.headers.set("x-auth-scope", verified.scope ?? "");
fwd.headers.set("x-edge-verified", "1");
fwd.headers.set("x-origin-secret", env.ORIGIN_SHARED_SECRET); // origin rejects without it
return fetch(fwd);
},
};
Two rules keep the hybrid honest. The origin must reject any request that did not arrive through the edge — mutual TLS, an allowlist of the provider’s addresses, or a shared secret header — otherwise the edge checks are advisory and an attacker simply addresses the origin directly. And the edge must strip any client-supplied copy of the headers it injects, so nobody can forge X-Auth-Sub from outside. When the edge tier also composes several upstreams, the same trust boundary applies to each subrequest, as described in aggregating microservice responses at the edge.
Migration pitfalls
The authorizer that quietly needs a database
A Lambda authorizer that opens a Postgres connection cannot be lifted into an isolate at all. Diagnose it before you plan the work: grep the authorizer for socket clients and driver imports. The fix is to split it — signature and expiry checks move to the edge, the entitlement lookup stays regional — or to project a read-only slice of that data into an edge key-value store on a schedule and accept its consistency window.
Rate limits that count differently after the move
Regional gateways count in one place, so a limit of 100 per minute means exactly 100. A distributed edge counter that samples or shards will let a burst through. Verify with a controlled loop of 120 requests and count the 429 responses; if the number is materially below 20, your counter is sharded more aggressively than you think. Either route the counter through a single coordination object per key or restate the limit as an approximate one in your API documentation.
CPU limit exceeded on a payload that used to be fine
An isolate terminates the request when CPU time runs out, usually on an unusually large body rather than under load. Reproduce with the biggest payload you accept, watch it in wrangler tail, and check whether you are parsing the whole body when you only need three fields. Cap the request size at the edge, stream instead of buffering, and push genuinely heavy work to the region.
A bad deploy that reaches every location in thirty seconds
The rollback story is the deploy story in reverse, and it needs to be rehearsed. Keep the routing manifest in version control, ship behind a canary hostname first, and confirm that redeploying the previous version is a single command. Instrument the gateway so a spike in 401 or 429 is visible within a minute — the practices in Edge Observability & Debugging exist for precisely this window. For a broader runtime-by-runtime comparison of the platforms themselves, see Cloudflare Workers vs AWS Lambda@Edge for Request Routing.
Frequently Asked Questions
Is an edge gateway always faster than a regional one? Only for requests it can answer itself. Accepted traffic still travels to the origin, so the end-to-end latency is similar; the saving is concentrated in rejected requests, cached responses, and anything the edge can compose or transform locally. If your users sit in the same region as your gateway, the difference on a rejected request is a few milliseconds.
Can I run exact billing quotas at the edge? Yes, but only by routing every request for a given key through a single coordination object, which reintroduces a serialization point and a dependency you must decide how to fail. If the quota is billed and disputes are expensive, keep the authoritative count regional and use the edge for a generous approximate limit that sheds abuse before it arrives.
What breaks first when porting a regional authorizer to an isolate? Anything that opens a socket — database drivers, gRPC clients, Redis clients — because edge runtimes expose HTTP subrequests rather than raw TCP. Bundle size and CPU time are the next two walls, usually hit by a large crypto or schema-validation library that was never a problem in a regional runtime.
How do I keep a global edge deploy from being a global outage?
Treat the routing manifest as the deployable artifact, put a canary hostname in front of every change, and make rollback a redeploy of the previous version rather than a hotfix. Alert on the rate of 4xx and 5xx responses per route within the first minute, since a policy bug shows up as a status-code shift long before users report it.
Does data residency rule out the edge entirely? Not entirely, but it constrains it. Signature verification and coarse limiting can run anywhere because they read no regulated data, while anything that inspects or stores a request body for a regulated subject needs jurisdiction-restricted placement or must stay in the region. Draw the line at the data, not at the tier.
Related
- Aggregating Microservice Responses at the Edge
- JWT Validation at the Edge with Cloudflare Workers
- Rate Limiting API Requests at the Edge
- Cloudflare Workers vs AWS Lambda@Edge for Request Routing
Back to API Gateway at the Edge