AWS Lambda Web Adapter
Run web applications on AWS Lambda — with zero code changes.
AWS Lambda Web Adapter lets developers build web apps with familiar frameworks (Express.js, Next.js, Flask, SpringBoot, ASP.NET, Laravel, and anything that speaks HTTP 1.1/1.0) and run them on AWS Lambda. The same Docker image can run on AWS Lambda, Amazon EC2, AWS Fargate, and local machines.

Key Features
- Supports Amazon API Gateway (REST & HTTP API), Lambda Function URLs, and Application Load Balancer
- Works with Lambda managed runtimes, custom runtimes, and Docker OCI images
- Supports Lambda Managed Instances for multi-concurrent request handling
- Framework and language agnostic — no new code dependencies
- Automatic binary response encoding
- Graceful shutdown support
- Response payload compression (gzip/brotli)
- Response streaming
- Multi-tenancy via tenant ID propagation
- Non-HTTP event trigger support (SQS, SNS, S3, DynamoDB, Kinesis, Kafka, EventBridge, Bedrock Agents)
How It Works
AWS Lambda Web Adapter runs as a Lambda Extension. When the Docker image runs inside AWS Lambda, the adapter starts automatically alongside your application. When running outside Lambda (EC2, Fargate, locally), the adapter does not run at all — your app just works as a normal web server.
- Lambda starts the adapter extension and your web application
- The adapter performs a readiness check against your app (default:
http://127.0.0.1:8080/) - Once your app responds, the adapter starts the Lambda runtime client
- Incoming Lambda events are converted to HTTP requests and forwarded to your app
- Your app's HTTP responses are converted back to Lambda event responses

Pre-built Binaries
Pre-compiled binaries are available from the public ECR repository:
public.ecr.aws/awsguru/aws-lambda-adapter
Multi-arch images are provided for both x86_64 and arm64 architectures.
Quick Start
AWS Lambda Web Adapter works with Lambda functions packaged as both Docker images and Zip packages. Choose the approach that fits your workflow.
Which Packaging Method?
| Method | Best For |
|---|---|
| Docker Images | Full control over runtime, multi-stage builds, consistent local/cloud environments |
| Zip Packages | AWS managed runtimes, simpler deployment, smaller package sizes |
Minimal Example (Docker)
Add one line to your existing Dockerfile:
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:1.1.0 /lambda-adapter /opt/extensions/lambda-adapter
That's it. Your web app now runs on Lambda.
Minimal Example (Zip)
- Attach the Lambda Web Adapter layer to your function
- Set
AWS_LAMBDA_EXEC_WRAPPERto/opt/bootstrap - Set your function handler to your app's startup script (e.g.
run.sh)
Port Configuration
By default, the adapter expects your app on port 8080. Set the AWS_LWA_PORT environment variable (or PORT) to change it.
Avoid ports 9001 and 3000. Port 9001 is used by the Lambda Runtime API, and port 3000 is used by the CloudWatch Lambda Insight extension. Inside Lambda, your app runs as a non-root user and cannot listen on ports below 1024.
Docker Images
To use Lambda Web Adapter with Docker images, package your web app in a Dockerfile and add one line to copy the adapter binary:
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:1.1.0 /lambda-adapter /opt/extensions/lambda-adapter
Non-AWS base images can be used since the Runtime Interface Client ships with the adapter.
Example: Node.js Express App
FROM public.ecr.aws/docker/library/node:20-slim
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:1.1.0 /lambda-adapter /opt/extensions/lambda-adapter
ENV PORT=7000
WORKDIR "/var/task"
ADD src/package.json /var/task/package.json
ADD src/package-lock.json /var/task/package-lock.json
RUN npm install --omit=dev
ADD src/ /var/task
CMD ["node", "index.js"]
This works with any base image except AWS managed base images. To use AWS managed base images, override the ENTRYPOINT to start your web app.
Custom Port
If your app listens on a port other than 8080, set the AWS_LWA_PORT environment variable:
ENV AWS_LWA_PORT=3000
Available Images
Pre-compiled multi-arch images (x86_64 and arm64) are available at:
public.ecr.aws/awsguru/aws-lambda-adapter:1.1.0
Zip Packages
AWS Lambda Web Adapter works with AWS managed Lambda runtimes via Lambda Layers.
Setup Steps
1. Attach the Lambda Web Adapter Layer
AWS Commercial Regions
| Architecture | Layer ARN |
|---|---|
| x86_64 | arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:30 |
| arm64 | arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerArm64:30 |
AWS China Regions
| Region | Architecture | Layer ARN |
|---|---|---|
| cn-north-1 (Beijing) | x86_64 | arn:aws-cn:lambda:cn-north-1:041581134020:layer:LambdaAdapterLayerX86:30 |
| cn-northwest-1 (Ningxia) | x86_64 | arn:aws-cn:lambda:cn-northwest-1:069767869989:layer:LambdaAdapterLayerX86:30 |
2. Set the Exec Wrapper
Configure the Lambda environment variable:
AWS_LAMBDA_EXEC_WRAPPER=/opt/bootstrap
3. Set the Function Handler
Set your function handler to your web application's startup script, e.g. run.sh.
SAM Template Example
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs20.x
Handler: run.sh
Layers:
- !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:30
Environment:
Variables:
AWS_LAMBDA_EXEC_WRAPPER: /opt/bootstrap
AWS_LWA_PORT: 7000
Windows Users
If you create your Zip package on Windows, your startup script (e.g. run.sh) must meet two requirements to work in the Lambda Linux runtime:
- Use LF line endings — Windows defaults to CRLF (
\r\n), which causes/bin/shto fail withcannot execute: required file not found. - Set Unix file permissions to 755 — Zip files created on Windows don't preserve Unix execute permissions.
Most zip utilities on Windows don't handle Unix permissions. You can work around this by using WSL, a build script that sets permissions explicitly, or a tool like 7-Zip with the -mcu flag. See #611 for more details.
For a complete working example, see the Express.js in Zip example.
Environment Variables
All configuration is done through environment variables, set either in your Dockerfile or as Lambda function configuration.
Reference Table
| Variable | Description | Default |
|---|---|---|
AWS_LWA_PORT | Traffic port your app listens on (falls back to PORT) | 8080 |
AWS_LWA_READINESS_CHECK_PORT | Readiness check port | Same as AWS_LWA_PORT |
AWS_LWA_READINESS_CHECK_PATH | Readiness check path | / |
AWS_LWA_READINESS_CHECK_PROTOCOL | Readiness check protocol: http or tcp | http |
AWS_LWA_READINESS_CHECK_HEALTHY_STATUS | HTTP status codes considered healthy (e.g. 200-399 or 200,201,204,301-399) | 100-499 |
AWS_LWA_ASYNC_INIT | Enable asynchronous initialization for slow-starting apps. Ignored under SnapStart and Provisioned Concurrency (logged) | false |
AWS_LWA_REMOVE_BASE_PATH | Base path to remove from the request path. Strips exactly one leading occurrence and only on a path-segment boundary: with /api, /api/api/order → /api/order and /apiorder is passed through unchanged; a configured trailing slash (/api/) is normalized. | None |
AWS_LWA_ENABLE_COMPRESSION | Enable gzip/br compression (buffered mode only) | false |
AWS_LWA_INVOKE_MODE | Invoke mode: buffered or response_stream | buffered |
AWS_LWA_PASS_THROUGH_PATH | Path for non-HTTP event payloads | /events |
AWS_LWA_AUTHORIZATION_SOURCE | Header name to replace with Authorization | None |
AWS_LWA_ERROR_STATUS_CODES | HTTP status codes that cause Lambda invocation failure (e.g. 500,502-504) | None |
AWS_LWA_LAMBDA_RUNTIME_API_PROXY | Proxy URL for Lambda Runtime API requests | None |
AWS_LWA_SNAPSTART_BEFORE_CHECKPOINT_PATH | Inner-app path the adapter POSTs to before a SnapStart snapshot | None |
AWS_LWA_SNAPSTART_AFTER_RESTORE_PATH | Inner-app path the adapter POSTs to after a SnapStart restore | None |
AWS_LWA_POOL_IDLE_TIMEOUT_SECONDS | Idle keep-alive (seconds, fractional allowed, e.g. 0.5) for the adapter's connection to your app. 0 turns connection reuse off entirely, so every request opens a fresh connection. A set-but-unusable value falls back to the default with a warn!. | 4 |
AWS_LWA_READINESS_CHECK_TIMEOUT_SECONDS | Seconds (fractional allowed, e.g. 0.5) the adapter waits for the app to report ready (cold-start init and after a SnapStart restore). On expiry the adapter fails rather than serving: cold-start init fails (the runtime never starts) and a restore fails. Unset, 0, or a negative value all mean wait indefinitely (no bound); a set-but-<= 0 or malformed value is ignored with a warn!. The async_init path keeps its own ~9.8s bound (non-fatal) and is unaffected. | unset / <= 0 (unbounded) |
Deprecated Variables
The following non-namespaced variables are deprecated and will be removed in v2.0. Migrate to the AWS_LWA_ prefixed versions.
| Deprecated | Replacement |
|---|---|
HOST | N/A |
READINESS_CHECK_PORT | AWS_LWA_READINESS_CHECK_PORT |
READINESS_CHECK_PATH | AWS_LWA_READINESS_CHECK_PATH |
READINESS_CHECK_PROTOCOL | AWS_LWA_READINESS_CHECK_PROTOCOL |
REMOVE_BASE_PATH | AWS_LWA_REMOVE_BASE_PATH |
ASYNC_INIT | AWS_LWA_ASYNC_INIT |
AWS_LWA_READINESS_CHECK_MIN_UNHEALTHY_STATUS | AWS_LWA_READINESS_CHECK_HEALTHY_STATUS |
PORTis not deprecated and remains a supported fallback forAWS_LWA_PORT.
Readiness Check
When a new Lambda execution environment starts, the adapter boots as a Lambda Extension, followed by your web application. The adapter needs to know when your app is ready to receive traffic.
How It Works
- The adapter sends HTTP GET requests to your app at
http://127.0.0.1:8080/ - It retries every 10 milliseconds
- Once it receives an HTTP response with status code >= 100 and < 500, the app is considered ready
- The adapter then starts the Lambda runtime client and begins forwarding invocations
Configuration
| Variable | Description | Default |
|---|---|---|
AWS_LWA_READINESS_CHECK_PORT | Port to check | Same as AWS_LWA_PORT |
AWS_LWA_READINESS_CHECK_PATH | Path to check | / |
AWS_LWA_READINESS_CHECK_PROTOCOL | http or tcp | http |
AWS_LWA_READINESS_CHECK_HEALTHY_STATUS | Status codes considered healthy | 100-499 |
TCP Readiness Check
If your app doesn't have an HTTP health endpoint ready at startup, you can use TCP-based readiness checking:
AWS_LWA_READINESS_CHECK_PROTOCOL=tcp
This checks only that the port is accepting TCP connections.
Custom Health Status Codes
You can customize which HTTP status codes are considered healthy:
# Only 2xx and 3xx are healthy
AWS_LWA_READINESS_CHECK_HEALTHY_STATUS=200-399
# Specific codes
AWS_LWA_READINESS_CHECK_HEALTHY_STATUS=200,201,204,301-399
Response Streaming
Lambda Web Adapter supports Lambda response streaming, allowing your app to stream responses back to clients as they are generated.
Enabling Response Streaming
Set the invoke mode environment variable:
AWS_LWA_INVOKE_MODE=response_stream
This should match your Lambda Function URL's invoke mode configuration.
When to Use
Response streaming is useful for:
- Server-sent events (SSE)
- Large file downloads
- Real-time data feeds
- Reducing time-to-first-byte for slow-generating responses
Limitations
- Compression (
AWS_LWA_ENABLE_COMPRESSION) is not supported with response streaming. If both are enabled, compression is automatically disabled with a warning. - Response streaming works with Lambda Function URLs and API Gateway. ALB does not support streaming.
Examples
- FastAPI with Response Streaming
- FastAPI with Response Streaming in Zip
- Next.js Response Streaming
- SpringBoot Response Streaming
- FastHTML with Response Streaming
Response Compression
Lambda Web Adapter supports gzip and Brotli compression for response bodies in buffered mode.
Enabling Compression
AWS_LWA_ENABLE_COMPRESSION=true
Behavior
When enabled:
- Responses are compressed using gzip or Brotli based on the client's
Accept-Encodingheader - Responses with
Content-Typestarting withimageare not compressed - Responses smaller than 32 bytes are not compressed
Limitations
Compression is not supported with response streaming (AWS_LWA_INVOKE_MODE=response_stream). If both are enabled, compression is automatically disabled and a warning is logged.
Async Initialization
Lambda managed runtimes offer up to 10 seconds for function initialization with burst CPU. If your function can't complete initialization within that window, Lambda restarts it and bills for the init time.
How It Works
When AWS_LWA_ASYNC_INIT is enabled:
- The adapter performs readiness checks for up to 9.8 seconds
- If the app isn't ready by then, the adapter signals Lambda that init is complete
- Readiness checking continues during the first handler invocation
- This avoids the restart penalty while using the free init CPU burst
Enabling
AWS_LWA_ASYNC_INIT=true
When to Use
Enable this when your application has a long startup time (e.g. loading large ML models, warming caches, establishing connection pools) that might exceed the 10-second init window.
Not applied under SnapStart or Provisioned Concurrency
AWS_LWA_ASYNC_INIT only makes sense for on-demand cold starts, where the initialization phase is short and the adapter must report init complete before a slow application is ready. SnapStart and Provisioned Concurrency have no such limit, and reporting early there is harmful: SnapStart would snapshot a half-initialized application, and Provisioned Concurrency would mark the environment ready while the application is still booting — the latency you provisioned concurrency to avoid.
The adapter therefore ignores the setting when AWS_LAMBDA_INITIALIZATION_TYPE is snap-start or provisioned-concurrency, and logs a warning so the override is visible.
Logging
Lambda Web Adapter supports Lambda's Advanced Logging Controls. Configure log level and format through the Lambda console, API, or CLI under Monitoring and operations tools > Logging configuration.
Environment Variables
| Variable | Description | Default |
|---|---|---|
AWS_LAMBDA_LOG_LEVEL | Log level: DEBUG, INFO, WARN, ERROR. Takes precedence over RUST_LOG | INFO |
AWS_LAMBDA_LOG_FORMAT | Log format: JSON or TEXT | TEXT |
You can also set RUST_LOG as a fallback if AWS_LAMBDA_LOG_LEVEL is not configured.
JSON Logging
When log format is set to JSON, log entries are formatted as JSON objects, making them easier to query with CloudWatch Logs Insights.
Request & Lambda Context
Lambda Web Adapter forwards API Gateway request context and Lambda invocation context to your web application via HTTP headers.
Request Context
API Gateway sends metadata (requestId, requestTime, apiId, identity, authorizer) for each request. This is forwarded in the x-amzn-request-context header as a JSON string.
The identity and authorizer fields are particularly useful for client authorization.
// Express.js example
app.get('/', (req, res) => {
const requestContext = JSON.parse(req.headers['x-amzn-request-context']);
const sourceIp = requestContext.identity?.sourceIp;
const userId = requestContext.authorizer?.claims?.sub;
});
See the API Gateway docs for the full request context schema.
Lambda Context
The Lambda invocation context (function name, memory, timeout, request ID, etc.) is forwarded in the x-amzn-lambda-context header as a JSON string.
# Flask example
@app.route('/')
def index():
lambda_context = json.loads(request.headers.get('x-amzn-lambda-context', '{}'))
function_name = lambda_context.get('function_name')
remaining_time = lambda_context.get('deadline_ms')
See the Lambda Context docs for the full list of available properties.
Non-HTTP Event Triggers
Lambda Web Adapter supports all non-HTTP event triggers including SQS, SNS, S3, DynamoDB, Kinesis, Kafka, EventBridge, and Bedrock Agents.
How It Works
The adapter forwards the raw event payload to your web application via an HTTP POST to a configurable path (default: /events).
- Lambda receives a non-HTTP event (e.g. SQS message)
- The adapter POSTs the event JSON body to
http://127.0.0.1:{port}/events - Your app processes the event and returns a JSON response
- The adapter forwards the response back to Lambda
Configuration
AWS_LWA_PASS_THROUGH_PATH=/events
Example Handler
# FastAPI
@app.post("/events")
async def handle_event(request: Request):
event = await request.json()
# Process SQS, SNS, S3, etc.
return {"statusCode": 200, "body": "processed"}
// Express.js
app.post('/events', (req, res) => {
const event = req.body;
// Process the event
res.json({ statusCode: 200, body: 'processed' });
});
Examples
Multi-Tenancy
Lambda Web Adapter supports multi-tenancy by automatically propagating the tenant ID from the Lambda runtime context to your web application.
How It Works
When the Lambda runtime includes a tenant_id in the invocation context, the adapter forwards it as an X-Amz-Tenant-Id HTTP header. If no tenant ID is present, the header is omitted.
Reading the Tenant ID
# FastAPI
@app.get("/")
def handler(request: Request):
tenant_id = request.headers.get("x-amz-tenant-id")
// Express.js
app.get('/', (req, res) => {
const tenantId = req.headers['x-amz-tenant-id'];
});
No additional configuration is required.
Lambda Managed Instances
Lambda Web Adapter supports Lambda Managed Instances, which allows a single Lambda execution environment to handle multiple concurrent requests. This can improve throughput and reduce costs for I/O-bound workloads.
How It Works
When running on Lambda Managed Instances, the adapter automatically handles concurrent invocations by forwarding multiple requests to your web application simultaneously. Since most web frameworks (Express.js, FastAPI, Spring Boot, etc.) are already designed to handle concurrent requests, your application should work without modification.
Considerations
When using Lambda Managed Instances, keep these points in mind:
- Shared state: Global variables and in-memory caches are shared across concurrent requests. Ensure your application handles shared state safely.
- Connection pooling: Use connection pools for databases and external services rather than single connections.
- File system: The
/tmpdirectory is shared across concurrent requests. Use unique file names or implement file locking to avoid conflicts. - Resource limits: Memory and CPU are shared across concurrent requests. Monitor resource usage under concurrent load.
Lambda Managed Instances works with both buffered and response streaming modes.
Check out the FastAPI with Response Streaming on Lambda Managed Instances example.
SnapStart
Lambda SnapStart snapshots an initialized execution environment and restores it on later cold starts, reducing startup latency. Because the adapter runs your web application as a separate process, the application does not have direct access to the SnapStart lifecycle. The adapter bridges this gap with two optional HTTP hooks.
Hooks
| Variable | When the adapter calls it | Use it to |
|---|---|---|
AWS_LWA_SNAPSTART_BEFORE_CHECKPOINT_PATH | Before the snapshot is taken | Drain or close resources that will not survive the snapshot |
AWS_LWA_SNAPSTART_AFTER_RESTORE_PATH | After restore, before serving traffic | Reconnect, refresh credentials, reseed randomness, regenerate unique identifiers |
Both hooks are opt-in and independent — each fires only when its variable is set.
How it works
The adapter always registers for the SnapStart lifecycle; the Lambda runtime invokes the hooks only when your function runs under SnapStart. When it does, the adapter participates as follows:
-
Before checkpoint — if
AWS_LWA_SNAPSTART_BEFORE_CHECKPOINT_PATHis set, the adapter sends an emptyPOSTto that path on your application, then signals Lambda that it is ready for the snapshot. Initialization has already waited for your application to report ready by this point, so the snapshot is never taken of a still-booting app. -
After restore — Lambda restores the environment. The adapter first refreshes its own HTTP connection to your application (so it never reuses a connection captured in the snapshot); then, if
AWS_LWA_SNAPSTART_AFTER_RESTORE_PATHis set, sends an emptyPOSTto that path; and finally re-runs the readiness check before admitting traffic.
Note:
AWS_LWA_ASYNC_INITis ignored under SnapStart (and under Provisioned Concurrency). It works around the short initialization limit for on-demand cold starts by reporting init complete before the application is ready; neither of those environments has that limit, and finishing early would snapshot — or serve — a half-initialized application. The adapter logs a warning when it ignores the setting.
Each hook is an empty POST, and your application must respond with a 2xx status. A non-2xx response or a connection failure fails the SnapStart phase — initialization for the before-checkpoint hook, restore for the after-restore hook — rather than serving traffic against an improperly prepared application. The adapter does not impose its own deadline on a hook: Lambda already bounds both phases. The bound on the after-restore hook is not your function timeout — Lambda allows the whole Restore phase only 10 seconds, shared by the runtime load, the after-restore hook, and the readiness check that follows it, and overrunning it fails the restore with Lambda.SnapStartTimeoutException. Keep that in mind when a hook does slow work such as draining a large connection pool. The final readiness check runs on every restore (whether or not an after-restore path is configured). By default this readiness wait is unbounded; set AWS_LWA_READINESS_CHECK_TIMEOUT_SECONDS (fractional seconds allowed, e.g. 0.5) to bound it, in which case a restore whose application does not report ready within that timeout fails — a value above the 10-second Restore limit cannot take effect on a restore, since Lambda times the phase out first. The same variable also bounds the initial cold-start readiness check, which under SnapStart is what guarantees the application is ready before the snapshot: when set and the application does not report ready within the timeout, initialization fails (the Lambda runtime never starts) rather than snapshotting or serving an app that never came up.
Why you need the hooks
State captured in a snapshot is shared across every restored environment. Two classes of problem follow:
- Stale connections. Database connections, cached DNS, and keep-alive HTTP connections captured in the snapshot are dead by the time the environment is restored. Close them in the before-checkpoint hook and re-establish them in the after-restore hook.
- Uniqueness and entropy. Values seeded once at initialization — random number generators, UUID seeds, security tokens — become identical across every restored environment. Reseed them in the after-restore hook.
Securing the hook paths
The hook paths are control-plane operations. External requests (via API Gateway or ALB) that target a configured hook path receive 403 Forbidden and are never forwarded to your application. The guard matches the hook route strictly: it canonicalizes both the configured path and the incoming request path (percent-decoding, collapsing //, . and .. segments, and comparing case-insensitively) before comparing, so alternate spellings that resolve to the same route are blocked too. Choose paths your normal application traffic does not use (for example, /snapstart/before and /snapstart/after).
Three kinds of value are rejected at startup, because the adapter cannot guard the route they name. In each case initialization fails with an error naming the offending path, rather than running with a state-mutating route left reachable:
- A path whose decoded form contains a percent sign (for example
/snapstart/after%25, which decodes to/snapstart/after%), or a malformed%escape (a trailing%, or%zz). Web frameworks disagree on how to route these — some reject them outright, others decode them leniently — so the adapter cannot guarantee it blocks every spelling that reaches the route, and a partially protected hook path is worse than an obviously invalid one. Percent-encoding that decodes to an ordinary path is fine (/snapstart/%61fteris accepted and guarded as/snapstart/after), but a plain unencoded path is clearest. - A path that collapses to the application root (
/,//,/..,/.,/foo/..,/%2f, …). Guarding the root would return403for every request to/, so the guard cannot cover it — and the hook would stillPOSTto/on every lifecycle event, failing the phase on any application that does not handlePOST /. Use a dedicated path instead. - A path that resolves to the same route as
AWS_LWA_PASS_THROUGH_PATH(default/events). Non-HTTP trigger events are rewritten onto the pass-through path before the guard runs, so every such event would be answered with403instead of reaching your application.
Leaving a hook variable unset (or empty) is different from either: it simply means that hook does not fire.
Warning: this 403 guard exists only when the adapter is in the request path — i.e. when your app runs on Lambda behind the adapter. The hook routes are ordinary application routes that mutate state (the examples close and re-establish the connection pool), so if you run the same image or app without the adapter (Amazon ECS, Amazon EKS, a local Docker host), those routes are reachable and unauthenticated. In that case, do not expose them publicly, or protect them yourself.
Example
from fastapi import FastAPI, Response
app = FastAPI()
pool = None # your database/connection pool
@app.post("/snapstart/before")
async def before_checkpoint():
# Close resources that won't survive the snapshot.
if pool is not None:
await pool.close()
return Response(status_code=200)
@app.post("/snapstart/after")
async def after_restore():
# Re-establish resources and reseed anything that must be unique.
global pool
pool = await create_pool()
return Response(status_code=200)
Configure the function with:
AWS_LWA_SNAPSTART_BEFORE_CHECKPOINT_PATH=/snapstart/before
AWS_LWA_SNAPSTART_AFTER_RESTORE_PATH=/snapstart/after
For complete, deployable applications see the fastapi-snapstart-zip example (zip packaging) and the fastapi-snapstart example (container image).
Graceful Shutdown
When Lambda is about to shut down an execution environment, it sends a SIGTERM signal to the runtime and a SHUTDOWN event to each registered extension. Your application can catch SIGTERM to perform cleanup tasks.
Example: Express.js
const server = app.listen(port, () => {
console.log(`Listening on port ${port}`);
});
process.on('SIGTERM', () => {
console.info('SIGTERM received, shutting down gracefully');
server.close(() => {
console.info('Server closed');
process.exit(0);
});
});
Use Cases
- Close database connections
- Flush logs or metrics
- Complete in-progress requests
- Release external resources
For more details, see the graceful shutdown with AWS Lambda repository.
Base Path Removal
When using API Gateway with multiple resources routed to different Lambda functions, your app may not be aware of the base path prefix.
Example
If your API Gateway has:
/orders/{proxy+}→ Orders Lambda/catalog/{proxy+}→ Catalog Lambda
The Orders app doesn't know about the /orders prefix. Set AWS_LWA_REMOVE_BASE_PATH to strip it:
AWS_LWA_REMOVE_BASE_PATH=/orders
A request to /orders/123 will be forwarded to your app as /123.
See the SpringBoot example for a working implementation.
Authorization Header
When using Lambda Function URLs with IAM auth type, the Authorization header is reserved for IAM authentication. If your backend app also needs an Authorization header, you can use a different header name and have the adapter rename it.
Configuration
Set AWS_LWA_AUTHORIZATION_SOURCE to the header name your client will use:
AWS_LWA_AUTHORIZATION_SOURCE=X-Custom-Auth
The adapter will replace X-Custom-Auth with Authorization before forwarding the request to your app.
This feature is disabled by default.
Error Status Codes
You can configure specific HTTP status codes to cause Lambda invocations to fail, triggering Lambda's built-in error handling (retries, DLQ processing).
Configuration
AWS_LWA_ERROR_STATUS_CODES=500,502-504,422
Supports individual codes and ranges, comma-separated.
Behavior
When your web application returns any of the configured status codes, the Lambda invocation is marked as failed. This is useful for:
- Triggering automatic retries for transient errors
- Routing failed invocations to a Dead Letter Queue (DLQ)
- Integrating with Lambda Destinations for failure handling
This feature is disabled by default.
Request Interception
The AWS_LWA_LAMBDA_RUNTIME_API_PROXY environment variable redirects Lambda Runtime API requests to a custom proxy URL. The proxy can intercept requests and apply operations such as inspection, modification, tracing, or payload capturing.
Configuration
AWS_LWA_LAMBDA_RUNTIME_API_PROXY=http://127.0.0.1:9002
How It Works
- The proxy intercepts requests between the adapter and the Lambda Runtime API
- The event payload received by your web app is wrapped inside the GET response body
- This proxy does not affect the extension registration API
- It is meant only for interacting with data received and sent by the web application
Use Cases
- Request/response tracing
- Payload capturing and logging
- Obfuscation of sensitive data
- Header modification
Examples Overview
The repository includes working examples for many popular web frameworks, packaged as both Docker images and Zip packages.
Python
| Example | Packaging | Streaming |
|---|---|---|
| FastAPI | Docker | No |
| FastAPI in Zip | Zip | No |
| FastAPI SnapStart | Docker | No |
| FastAPI SnapStart Zip | Zip | No |
| FastAPI Background Tasks | Docker | No |
| FastAPI Response Streaming | Docker | Yes |
| FastAPI Response Streaming Zip | Zip | Yes |
| FastAPI Streaming on LMI | Docker | Yes |
| FastAPI Backend Streaming (IAM Auth) | Docker | Yes |
| Flask | Docker | No |
| Flask in Zip | Zip | No |
| FastHTML | Docker | No |
| FastHTML in Zip | Zip | No |
| FastHTML Response Streaming | Docker | Yes |
| FastHTML Response Streaming Zip | Zip | Yes |
| Bedrock Agent FastAPI | Docker | No |
| Bedrock Agent FastAPI Zip | Zip | No |
JavaScript / TypeScript
| Example | Packaging | Streaming |
|---|---|---|
| Express.js | Docker | No |
| Express.js in Zip | Zip | No |
| Next.js | Docker | No |
| Next.js in Zip | Zip | No |
| Next.js Response Streaming | Docker | Yes |
| SQS Express.js | Docker | No |
| Remix | Docker | No |
| Remix in Zip | Zip | No |
| SvelteKit SSR Zip | Zip | No |
| Bun GraphQL | Zip | No |
| Bun GraphQL Streaming | Zip | Yes |
Java
| Example | Packaging | Streaming |
|---|---|---|
| SpringBoot | Docker | No |
| SpringBoot in Zip | Zip | No |
| SpringBoot Response Streaming | Zip | Yes |
| Javalin in Zip | Zip | No |
.NET
| Example | Packaging | Streaming |
|---|---|---|
| ASP.NET MVC | Docker | No |
| ASP.NET MVC in Zip | Zip | No |
| ASP.NET Web API Zip | Zip | No |
Rust
| Example | Packaging | Streaming |
|---|---|---|
| Actix Web in Zip | Zip | No |
| Axum in Zip | Zip | No |
Go
| Example | Packaging | Streaming |
|---|---|---|
| Gin | Docker | No |
| Gin in Zip | Zip | No |
| Go HTTP Zip | Zip | No |
Other Languages
| Example | Packaging | Streaming |
|---|---|---|
| Nginx | Docker | No |
| Nginx in Zip | Zip | No |
| Deno Oak in Zip | Zip | No |
| Ruby Sinatra | Docker | No |
Observability
| Example | Packaging |
|---|---|
| Datadog | Docker |
| Datadog in Zip | Zip |
Community Examples
Local Debugging
Lambda Web Adapter lets you develop locally with familiar tools and debuggers — just run your web app and test it directly.
Running Locally
Since the adapter only activates inside the Lambda execution environment, your app runs as a normal web server locally:
# Node.js
node index.js
# Python
uvicorn main:app --port 8080
# Java
./mvnw spring-boot:run
Simulating Lambda with SAM CLI
To simulate the full Lambda runtime environment locally, use AWS SAM CLI:
sam local start-api
Note:
sam localstarts a Lambda Runtime Interface Emulator on port 8080. Your web application should avoid port 8080 if you plan to usesam local. SetAWS_LWA_PORTto a different port.
Testing Individual Invocations
sam local invoke MyFunction --event event.json
Building from Source
AWS Lambda Web Adapter is written in Rust and based on the AWS Lambda Rust Runtime.
Prerequisites
Clone the Repository
git clone https://github.com/aws/aws-lambda-web-adapter.git
cd aws-lambda-web-adapter
Build with cargo-lambda
cargo-lambda handles cross-compilation for Lambda targets automatically — no need to install cross-compiler toolchains manually.
Build for x86_64:
cargo lambda build --release --extension --target x86_64-unknown-linux-musl
Build for arm64:
cargo lambda build --release --extension --target aarch64-unknown-linux-musl
The compiled extension is placed under target/lambda/extensions/.
Package as Docker Image
After building with cargo-lambda, you can package the binary into a minimal container:
# x86_64
printf 'FROM scratch\nADD target/lambda/extensions/. /\n' | docker build --platform=linux/amd64 -t aws-lambda-adapter:latest-x86_64 -f- .
# arm64
printf 'FROM scratch\nADD target/lambda/extensions/. /\n' | docker build --platform=linux/arm64 -t aws-lambda-adapter:latest-aarch64 -f- .
Or use the Makefile targets which combine both steps:
make build-image-x86
make build-image-arm64
Running Tests
cargo fmt -- --check
cargo clippy -- -Dwarnings
cargo nextest run
Architecture
Overview
AWS Lambda Web Adapter is a Lambda Extension that bridges the gap between Lambda's event-driven invocation model and traditional HTTP web applications.
Supported Triggers
- Amazon API Gateway REST API
- Amazon API Gateway HTTP API (v2 event format)
- Application Load Balancer (ALB)
- Lambda Function URLs
- Non-HTTP triggers (SQS, SNS, S3, DynamoDB, Kinesis, Kafka, EventBridge, Bedrock Agents) via pass-through
Request Flow
- Lambda receives an event from a trigger (API Gateway, ALB, Function URL, etc.)
- The adapter converts the Lambda event into a standard HTTP request
- The HTTP request is forwarded to your web application on the configured port
- Your app processes the request and returns an HTTP response
- The adapter converts the HTTP response back into a Lambda event response
- Lambda returns the response to the caller
Extension Lifecycle
The adapter runs as a Lambda Extension (since v0.2.0):
- Init phase: Lambda starts the adapter extension and your web application process
- Readiness check: The adapter polls your app until it responds (every 10ms)
- Invoke phase: The adapter starts the Lambda runtime client and forwards events
- Shutdown phase: Lambda sends SIGTERM, allowing graceful shutdown
When running outside Lambda (EC2, Fargate, local), the adapter does not start — your app runs as a normal web server.
Binary Response Encoding
The adapter automatically detects binary responses based on the Content-Type header and encodes them appropriately for the Lambda response format.
Technology Stack
- Written in Rust
- Built on AWS Lambda Rust Runtime (
lambda_httpcrate) - Uses
hyperas the HTTP client - Uses
towerfor middleware (compression) - Compiled to static musl binaries for x86_64 and aarch64
Changelog
For the full changelog with all releases and contributors, see the GitHub Releases page.
Highlights
v0.7.1 (2023-08-18)
- Lambda Context support via
x-amzn-lambda-contextheader - Expanded to new AWS regions including China regions
- Customizable healthy status codes for readiness check
v0.7.0 (2023-04-15)
- Lambda Response Streaming support
- Namespaced environment variables (
AWS_LWA_prefix) - Tightened HTTP readiness check
v0.6.3 (2023-03-10)
- TLS/HTTPS support for web applications
- Proper URL encoding handling
v0.6.2 (2023-02-17)
- Optional gzip compression of responses
v0.6.0 (2022-12-18)
- Request Context forwarding via
x-amzn-request-contextheader
v0.5.0 (2022-10-13)
- Upgraded to
lambda_http0.7
v0.4.0 (2022-09-12)
- Async initialization support
- Refactored into a library, published to crates.io
v0.3.2 (2022-03-29)
- Base path removal support
- Published OCI images to ECR public repo
v0.2.0 (2022-02-07)
- Rewritten as a Lambda Extension (breaking change)
- No longer requires changing ENTRYPOINT
v0.1.0 (2021-09-15)
- Initial release