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.
TL;DR
- Your server listens on the
host:portLambda passes inAWS_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--regionsfor 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.
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.
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.
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
Deploy (make sure your AWS CLI v2 has aws lambda-web):
aws lambda-web deploy --name my-api --code ./my-api --auth-type IamAuth
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}]'
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}'
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.
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
IamAuthendpoints must includeX-Amz-Content-SHA256, even with no body. -
delete-web-functionleaves 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 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.







Top comments (0)