API security

How do you secure serverless and edge apps?

Serverless and edge apps expose many independently invocable functions, so a perimeter WAF is not enough. Enforce identity-aware limits and bot policy inside each handler, and pair the platform edge with Arcjet on Lambda, Vercel, and Cloudflare Workers.

7 min read
In short: Serverless and edge apps expose many independently invocable functions, so a perimeter WAF is not enough. Enforce identity-aware limits and bot policy inside each handler, and pair the platform edge with Arcjet on Lambda, Vercel, and Cloudflare Workers.

What is serverless security?

Serverless security protects applications that run on event-driven platforms where functions start on demand, such as AWS Lambda, Vercel Functions, and other function-as-a-service hosts. You do not harden a long-lived fleet. You protect each function, each request flow, and each service call, because every function is its own entry point.

Workloads scale automatically, often across regions, and they are composed of many small handlers. The interesting questions move from "is this host patched?" to "can this identity call this expensive function right now?" For more information about enforcing those decisions in the request path, see what is runtime application security.

Why do serverless, edge, and microservices increase risk?

In a monolith, traffic usually enters through one gateway that can rate-limit, filter, and authenticate. Distributed architectures break that assumption.

Each serverless function or edge route can accept requests directly. A login handler, webhook, AI inference route, and background trigger may all be independently invocable. An attacker does not need to take the whole application down; they only need to aim at one expensive function.

Edge runtimes execute close to users for latency. If every request returns to a centralized inspection tier, you give back the performance you bought. Security has to run where the code runs.

Microservices talk to each other over HTTP or RPC. Retry storms, a bad deploy, or a stolen service credential can generate traffic that looks fine at the network layer and still overwhelms a dependency. A perimeter device cannot see identity or intent inside those calls. The security boundary exists at every execution environment, not only at the edge of the network.

Why isn't perimeter security enough?

Firewalls, WAFs, and API gateways still matter. They filter at the network, integrate routing and authentication, and give you a single place to watch volumetric attack. They generally lack user identity, per-route business meaning, and visibility into internal service intent.

Relying only on the perimeter creates uneven coverage: some endpoints sit behind a well-tuned gateway rule, others rely on implicit trust. Abuse finds the gap. For more information about the API controls that belong next to application logic, see API security best practices.

The following table compares perimeter controls with application-layer enforcement.

DimensionPerimeter (WAF, gateway, edge ACL)Application layer (in the function)
What it seesIP, headers, TLS, raw bytes, route prefixAuthenticated user, plan, tenant, parsed body, target object
Decisions it can makeVolumetric limits, IP reputation, signatures, geo rules

Per-user quotas, object authorization, cost-weighted token buckets

Internal service callsOften invisible or implicitly trusted

Enforced on the same protect() path as public traffic

LatencyOne hop before the origin; can become a cross-region bottleneckRuns in the isolate or function that already handles the request
Coverage of jobs and toolsNone; there is no inbound HTTP requestThe same library in the job or tool function
Policy driftConsole rules that some services never inheritRules in the repo next to the handler

Use both. Keep the edge for DDoS and coarse filtering. Put identity-aware limits and route-specific bot policy in the function.

What is application-layer security in distributed systems?

Application-layer security evaluates the request inside the function: user identity, payload, route, behavior, and service-specific rules. Identity-aware rate limiting can combine IP, email, API key, or account ID. That is more precise than IP throttling at a firewall, and it scales horizontally with the function instead of forcing every region through one chokepoint.

What serverless security practices should you follow?

Enforce rate limits in the function, not only at the gateway. Login, signup, password reset, and AI inference should check identity-aware capacity before they do expensive work.

Protect authentication and AI routes more aggressively than a public catalog. Those paths consume backends and often trigger downstream calls. Credential stuffing and cost amplification succeed when the handler runs first and the limiter sits somewhere else.

Apply limits and validation to internal APIs. A misconfigured worker should not be able to retry a dependency into the ground. Distributed systems fail gradually; internal enforcement shrinks blast radius.

Do not add a distant inspection hop just to satisfy a security checkbox on an edge route. Run the control in the isolate that already has the Request.

Keep policies consistent across services. Shared rules in a library beat a mix of strict functions and unprotected ones. Consistency matters more than a clever one-off.

How do you add Arcjet on Lambda, Vercel, and Cloudflare Workers?

Create one client at module scope (or from the Worker env binding), then call protect() before business logic. The following examples use Shield, bot detection, and a sliding window or token bucket. Start new rules in DRY_RUN on a busy route, then switch to LIVE.

AWS Lambda with @arcjet/node. This adapter does not take a Fetch Request. It takes an ArcjetNodeRequest, the http.IncomingMessage shape, so map the event onto headers, method, and url yourself. Pass ipSrc from the source IP the platform already verified, because there is no socket to read it from:

import type {
APIGatewayProxyEventV2,
APIGatewayProxyResultV2,
} from "aws-lambda";
import arcjet, {
detectBot,
shield,
slidingWindow,
type ArcjetNodeRequest,
} from "@arcjet/node";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
rules: [
shield({ mode: "LIVE" }),
detectBot({ mode: "LIVE", allow: [] }),
slidingWindow({ mode: "LIVE", interval: 60, max: 100 }),
],
});
export async function handler(
event: APIGatewayProxyEventV2,
): Promise<APIGatewayProxyResultV2> {
const req: ArcjetNodeRequest = {
headers: event.headers,
method: event.requestContext.http.method,
// Keep the query string: Shield reads `query` from this URL.
url: event.rawQueryString
? `${event.rawPath}?${event.rawQueryString}`
: event.rawPath,
};
const decision = await aj.protect(req, {
ipSrc: event.requestContext.http.sourceIp,
});
if (decision.isDenied()) {
const status = decision.reason.isRateLimit() ? 429 : 403;
return {
statusCode: status,
body: status === 429 ? "Too Many Requests" : "Forbidden",
};
}
return { statusCode: 200, body: "ok" };
}

Vercel with @arcjet/next. The App Router handler already receives a Fetch Request:

import arcjet, { detectBot, shield, tokenBucket } from "@arcjet/next";
import { NextResponse } from "next/server";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
rules: [
shield({ mode: "LIVE" }),
detectBot({ mode: "LIVE", allow: ["CATEGORY:SEARCH_ENGINE"] }),
tokenBucket({
mode: "LIVE",
refillRate: 10,
interval: 60,
capacity: 20,
}),
],
});
export async function POST(req: Request) {
const decision = await aj.protect(req, { requested: 1 });
if (decision.isDenied()) {
const status = decision.reason.isRateLimit() ? 429 : 403;
return NextResponse.json(
{ error: status === 429 ? "Too Many Requests" : "Forbidden" },
{ status },
);
}
return NextResponse.json({ ok: true });
}

Cloudflare Workers are the exception. There is no request adapter for workerd, so Shield, bot detection, and IP analysis are not available there. What does run on Workers is @arcjet/guard, which covers rate limiting, prompt injection, content moderation, and sensitive information. Import from @arcjet/guard and the fetch transport is selected for you:

import { launchArcjet, tokenBucket } from "@arcjet/guard";
const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
const limit = tokenBucket({
refillRate: 5,
intervalSeconds: 10,
maxTokens: 10,
});
export default {
async fetch(request: Request, env: unknown, ctx: ExecutionContext) {
const key = request.headers.get("cf-connecting-ip") ?? "anonymous";
const decision = await arcjet.guard({
label: "worker.api",
rules: [limit({ key, requested: 1 })],
});
ctx.waitUntil(arcjet.flush());
if (decision.conclusion === "DENY") {
return new Response("Too Many Requests", { status: 429 });
}
return new Response("ok");
},
};

Guard rate limits take seconds as numbers (intervalSeconds, windowSeconds, maxTokens), not the duration strings the request rules accept, and denial is decision.conclusion === "DENY" rather than isDenied(). Workers need compatibility date 2025-09-01 or later. Because a module-scoped client cannot reach a per-invocation context, pass Cloudflare's ExecutionContext in and call flush() through waitUntil.

Pin the adapters you use: @arcjet/node@1.10.0, @arcjet/next@1.10.0, and @arcjet/guard@1.10.0. All of them are ESM-only, and the supported Node.js range is >=22.21.0 <23 || >=24.5.0. Node.js 23 is not supported and Node.js 20 is end-of-life.

How does Arcjet fit serverless and edge architectures?

Arcjet runs beside the handler instead of assuming every decision happens at a centralized gateway. That scales with the function, keeps edge latency, and lets each service enforce the same primitives: Shield, detectBot, and a rate limit keyed on application identity.

It complements a WAF or gateway. The platform absorbs volumetric attack. The SDK distinguishes your largest customer from a scraper because it sees the user, the plan, and the route. Generic edge rules are poor at that distinction.

Companion tutorials apply the same protect() shape on long-lived and GraphQL servers: how to secure a Node.js/Express API, how to secure a GraphQL API, and GraphQL rate limiting and bot detection.

Frequently asked questions

Is serverless more secure than traditional servers?

Platforms reduce some host-management risk, but they do not remove application-layer risk. Each function is an entry point and must enforce its own authentication, authorization, and abuse controls.

Do you still need a WAF with serverless?

Yes, for volumetric filtering and coarse signatures at the edge. You still need application-layer Shield, identity-aware rate limits, and bot policy in the function, where user and route context exist.

How do you rate-limit AWS Lambda functions?

Call Arcjet protect() inside the handler with @arcjet/node before expensive work. That adapter takes an ArcjetNodeRequest, not a Fetch Request, so map the event onto headers, method, and url, and pass ipSrc from the platform's trusted source IP.

What is the biggest security risk in microservices?

Inconsistent enforcement and implicit trust between services. A misconfigured worker or stolen credential can overwhelm a dependency that the perimeter never sees.

Why does edge deployment change the security model?

Edge runtimes exist to avoid a round-trip to a central region. Forcing every request through a distant inspection tier cancels that benefit. Run controls in the isolate that already handles the request.

Should Arcjet replace the gateway?

No. Keep the edge for DDoS and coarse filtering. Use Arcjet in the function for per-user quotas, Shield, and bot lists that need application context.

Application security in your code

Protect your application with Arcjet

Get rate limits, bot detection, and attack blocking in your request handlers.