DEV Community

Cover image for AWS Lambda Web Functions: your HTTP server on Lambda, with real concurrency and active-CPU billing
Matias Martinez
Matias Martinez

Posted on Originally published at blog.cloudacademy.ar

AWS Lambda Web Functions: your HTTP server on Lambda, with real concurrency and active-CPU billing

On October 12, AWS launched Lambda Web Functions, and it's the biggest change to Lambda's programming model in years. Instead of writing a handler that receives a JSON event, you ship a plain HTTP server (Express, Hono, Fastify, React Router) that listens on a port. Lambda puts a managed HTTPS endpoint in front of it, scales it with traffic (down to zero), and bills you for the CPU time you actually burn, not for the time your request spent waiting on Postgres.

Along with the launch, the existing functions were renamed Lambda Event Functions (naming only, no code changes), so Lambda now comes in three shapes: Event Functions, Web Functions, and MicroVMs.

The three shapes of AWS Lambda

TL;DR

  • Your server listens on the host:port Lambda passes in AWS_LAMBDA_HTTP_ENDPOINT. No adapter, no wrapper.
  • Each execution environment serves many requests at once: up to 128, default 64.
  • Billing is active CPU + memory consumed while the environment lives + requests. In us-east-1 (Arm): $0.0000276944 per vCPU-second, $0.0000036667 per GB-second, $0.30 per million requests.
  • One-command deploy: aws lambda-web deploy --name my-app --code ./my-app. Add --regions for a global endpoint with latency-based routing.
  • Immutable revisions plus weighted endpoints give you canary, blue/green and rollback out of the box.
  • Node.js 24 on Arm64 only, 2 GB per environment, 3–900 s request timeout.
  • Available in us-east-1, us-east-2, us-west-2, ap-northeast-1 and eu-west-1. Not in São Paulo yet.

Why this matters

Running web apps on Lambda was always possible, but awkward. You wrapped your framework with serverless-http or the Lambda Web Adapter, and each environment still handled exactly one request. If your API took 200 ms because it was waiting on a database, you paid for 200 ms of compute while the CPU idled most of that time. A burst of 50 concurrent requests meant up to 50 environments and 50 cold starts.

That model fits short, event-driven functions. It fits LLM-backed APIs terribly: a request that streams tokens for three seconds while the CPU mostly waits is the worst case for wall-clock billing and single-request isolation. Web Functions is AWS's answer, and it's no coincidence the launch post lists streaming inference endpoints among the first use cases.

The resource model: functions, revisions, endpoints

  • Web Function: the top-level resource.
  • Revision: an immutable snapshot of code and configuration (timeout, per-environment concurrency, env vars, execution role). Any change means a new revision, and a new revision doesn't inherit settings from the previous one.
  • Endpoint: the HTTPS entry point, with its own domain, auth type and scaling limits. It routes to one or two revisions by weight.

Web Function resource model: prod endpoint split 90/10, staging endpoint at 100% on revision 2. Image: AWS documentation

Endpoints come in three types, fixed at creation: HomeRegion (one Region, one domain), MultiRegion (several Regions behind a global *.lambda-web.global.on.aws domain with best-effort latency routing), and PerRegion (one domain per Region, you route).

Each environment goes through init, ready and shutdown. Your server must start listening within 10 seconds; listening on the port is the readiness signal, there's no separate health check. On shutdown you get SIGTERM and one second to clean up.

Execution environment lifecycle. Image: AWS documentation

Concurrency and scaling

Setting Where Range Default
maxConcurrencyPerEnvironment Revision 1–128 64
maxEnvironments Endpoint, per Region 2–10,000 (optional) none
rateLimit Endpoint, per Region 0–10,000 RPS (optional) none
timeoutSeconds Revision 3–900 30

Account quotas apply per Region even if you set nothing: 10,000 RPS per endpoint, 100,000 RPS per account, and 2,000 vCPUs. Each environment uses 2 vCPUs, so that's at most 1,000 environments per Region. Lambda keeps headroom to absorb gradual increases; bursts larger than the headroom wait on cold starts. When you hit a limit you get a 429 (not queued); a rateLimit of 0 returns 503 Platform.EndpointDisabled, which doubles as a kill switch.

Scaling headroom vs. traffic burst. Image: AWS documentation

Hands-on

The only code change versus running locally is where you listen:

const express = require('express');
const app = express();
app.get('/', (req, res) => res.json({ message: 'Hello from Lambda Web Functions' }));

const [host, port] = (process.env.AWS_LAMBDA_HTTP_ENDPOINT || 'localhost:3000').split(':');
app.listen(port, host);

process.on('SIGTERM', () => process.exit(0)); // you get ~1s
Enter fullscreen mode Exit fullscreen mode

Deploy (make sure your AWS CLI v2 has aws lambda-web):

aws lambda-web deploy --name my-api --code ./my-api --auth-type IamAuth
Enter fullscreen mode Exit fullscreen mode

Note that deploy creates an ApplicationManaged (public, no auth) endpoint by default. Great for hello world, not for production accounts. The docs include an SCP that denies creating such endpoints via the lambda:Request/WebFunctionAuthType condition key.

Canary and rollback are just weight updates:

aws lambda-web update-web-function-endpoint \
  --function-name my-api --endpoint-name prod \
  --revision-weights '[{"revisionId": "rev-abc12", "weight": 90}, {"revisionId": "rev-def45", "weight": 10}]'

# rollback (include the mode so it works from any state)
aws lambda-web update-web-function-endpoint \
  --function-name my-api --endpoint-name prod \
  --auto-deployment-mode Disabled \
  --revision-weights '[{"revisionId": "rev-abc12", "weight": 100}]'
Enter fullscreen mode Exit fullscreen mode

Canary deployment with revision weights. Image: AWS documentation

Protect your downstream database without a new revision:

aws lambda-web update-web-function-endpoint \
  --function-name my-api --endpoint-name prod \
  --scaling-config '{"maxEnvironments": 50}' \
  --throttle-config '{"rateLimit": 8000}'
Enter fullscreen mode Exit fullscreen mode

For a custom domain and AWS WAF, put CloudFront in front with Origin Access Control (origin type lambda, signing behavior always-amz-auth) against an IamAuth endpoint. CloudFront origin support launched the same day at no extra cost.

CloudFront + WAF in front of an IamAuth Web Function endpoint. Image: AWS documentation

Outbound VPC access uses a network connector (aws lambda-core create-network-connector) referenced by version-qualified ARN from the revision. It only works with HomeRegion endpoints, and you still need a NAT gateway for internet egress.

Gotchas

  • "Active" doesn't mean "working." Lambda doesn't check that your server answers. Always smoke-test after deploying.
  • Hardcoded ports never become ready. Read AWS_LAMBDA_HTTP_ENDPOINT.
  • Global mutable state is now a concurrency bug, just like in a container.
  • 2 GB is shared by heap, buffers and every in-flight request. V8's default heap is 1,024 MB.
  • DNS is cached per environment; ship a new revision after private DNS changes.
  • Signed requests to IamAuth endpoints must include X-Amz-Content-SHA256, even with no body.
  • delete-web-function leaves the default IAM role, S3 packages and log groups behind.

Pricing, honestly

AWS's own examples assume one environment for a 30-day month: an SSR app with 15M requests costs $25.78; an I/O-heavy API with 10M requests (200 ms, 50 ms CPU) costs $19.61; an LLM streaming endpoint with 5M requests of 3 s (200 ms CPU) costs $33.94.

I ran the same workloads through Event Function pricing (x86 rate listed on the same page, my assumption of 512 MB, no free tier): roughly $18.67 for the I/O API, $21.75 for SSR, and about $126 for LLM streaming. So Web Functions isn't automatically cheaper for short requests (the vCPU-second rate is high and requests cost 50% more), but for long, I/O-bound requests like token streaming it's a different order of magnitude.

What gets billed in a 200 ms request

What to check before adopting

  • Regions: five at launch, no South America. For users in Argentina or Brazil, us-east-1 is the closest option.
  • Data residency: multi-Region endpoints replicate code, and request metrics and logs are centralized asynchronously in the home Region.
  • Cross-Region data transfer: a global endpoint serving from Tokyo but querying a database in Virginia pays in latency and transfer.
  • Maturity: one runtime, brand-new APIs, IaC tooling still catching up. LocalStack 2026.10.0 already emulates it for local testing.

My take

The interesting part isn't "Express on Lambda"; we could already do that. It's that AWS changed both the billing unit and the isolation unit of its most used serverless service. For ten years Lambda made us think in one request per environment and pay for waiting time. That made sense for short event handlers and stopped making sense for apps that spend seconds waiting on a model. Web Functions brings Lambda close to a well-scaled container, without a cluster, and with revisions, canaries and multi-Region built in. The weak spots are the launch scope: Node.js only and five Regions. I'd try it now on a non-critical Node.js service, especially one that talks to Bedrock, and wait for the runtime and Region roadmap before making it a platform standard.

Originally published in Spanish on CloudAcademy.ar, with the full CLI walkthrough, VPC connector setup and SCP.

Resources

Top comments (0)