Skip to content
Get Started for Free

Lambda Web Functions

Lambda Web Functions run a web application on AWS Lambda. Instead of writing an event handler, you deploy an HTTP server, and Lambda forwards each request to it as plain HTTP. A web function has revisions, which hold its code and runtime settings, and endpoints, which give it an HTTPS domain and decide which revisions serve traffic.

LocalStack allows you to use the Lambda Web APIs to create web functions, revisions, and endpoints, and to send requests to your application running in a local sandbox container. LocalStack emulates both the control plane and the data plane: endpoints serve real HTTP and HTTPS traffic from your application, with IAM authentication and resource-based policies, weighted traffic shifting between revisions, scaling and throttling limits, HomeRegion, PerRegion and MultiRegion endpoints, response streaming, CloudWatch logs and metrics, CloudFront origins, and CloudFormation resources. The supported APIs are available on our API Coverage section, which provides information on the extent of Lambda Web Functions’ integration with LocalStack.

This guide is designed for users new to Lambda Web Functions and assumes basic knowledge of the AWS CLI and our lstk aws command. You also need Node.js and npm to install the application’s dependencies.

Start your LocalStack container using your preferred method. LocalStack runs your application in a Docker container, the same way it runs Lambda functions, so LocalStack needs access to the Docker socket. We will deploy a small Node.js application that serves a message stored in S3, and call it with curl. The application uses the AWS SDK exactly as it would on AWS, with no LocalStack-specific configuration.

A web function runs an HTTP server. Lambda tells the server where to listen through the AWS_LAMBDA_HTTP_ENDPOINT environment variable, as host:port.

Create a file called index.js with the following code. The root path answers with a greeting, and /message returns the content of message.txt from S3:

index.js
const http = require("node:http");
const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3");
// The SDK picks up the region, the execution role's credentials and, on LocalStack, the endpoint
const s3 = new S3Client();
// Lambda Web tells the application where to listen, as "host:port"
const [host, port] = process.env.AWS_LAMBDA_HTTP_ENDPOINT.split(":");
const server = http.createServer(async (request, response) => {
if (request.url === "/message") {
try {
const object = await s3.send(new GetObjectCommand({
Bucket: process.env.BUCKET_NAME,
Key: "message.txt",
}));
response.writeHead(200, { "Content-Type": "text/plain" });
response.end(await object.Body.transformToString());
} catch (error) {
console.error(error);
response.writeHead(500, { "Content-Type": "text/plain" });
response.end(`${error.name}: ${error.message}\n`);
}
return;
}
response.writeHead(200, { "Content-Type": "text/plain" });
response.end(`Hello from LocalStack in ${process.env.AWS_REGION}\n`);
});
server.listen(Number(port), host);

Install the AWS SDK next to index.js and package both into a ZIP file:

Terminal window
npm init -y
npm install @aws-sdk/client-s3
zip -qr function.zip index.js package.json node_modules

A web function reads its code from S3, and the bucket must have versioning enabled. Create a bucket, enable versioning, and upload the ZIP file:

Terminal window
lstk aws s3 mb s3://web-function-code
lstk aws s3api put-bucket-versioning \
--bucket web-function-code \
--versioning-configuration Status=Enabled
lstk aws s3api put-object \
--bucket web-function-code \
--key function.zip \
--body function.zip
Output
make_bucket: web-function-code
{
"ETag": "\"059f0068320e7e288f17f4384997822f\"",
"ChecksumCRC64NVME": "3F8iNjgz1SE=",
"ChecksumType": "FULL_OBJECT",
"ServerSideEncryption": "AES256",
"VersionId": "AaDOtgehhgxOSToJ7925iM0I9npozJ6I"
}

If versioning is not enabled, creating the web function fails.

Store the message the application serves in the same bucket:

Terminal window
echo "Hello from LocalStack!" > message.txt
lstk aws s3 cp message.txt s3://web-function-code/message.txt
Output
upload: ./message.txt to s3://web-function-code/message.txt

A web function needs an execution role that Lambda can assume. The application runs with this role’s credentials, so the role also grants read access to the message. Create a file called trust-policy.json:

trust-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}
]
}

Create a file called message-policy.json that allows the application to read message.txt:

message-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::web-function-code/message.txt"
}
]
}

Then create the role and attach the policy:

Terminal window
lstk aws iam create-role \
--role-name web-function-role \
--assume-role-policy-document file://trust-policy.json
lstk aws iam put-role-policy \
--role-name web-function-role \
--policy-name read-message \
--policy-document file://message-policy.json

The role’s policies only restrict the application when you enable ENFORCE_IAM.

Create the web function, its first revision, and its first endpoint in one call with create-web-function. The revision points at the code in S3, uses the nodejs24.x runtime, and passes the bucket name to the application as an environment variable. nodejs24.x is currently the only runtime Lambda Web offers. The endpoint is a HomeRegion endpoint that anyone can call (ApplicationManaged):

Terminal window
lstk aws lambda-web create-web-function \
--function-name my-web-function \
--revision-config '{
"buildConfig": {
"codeConfig": {"s3Object": {"bucket": "web-function-code", "key": "function.zip"}},
"runtimeConfig": {"runtime": "nodejs24.x"}
},
"serviceConfig": {
"executionRoleArn": "arn:aws:iam::000000000000:role/web-function-role",
"environmentVariables": {"BUCKET_NAME": "web-function-code"}
}
}' \
--endpoint-config '{
"endpointName": "default",
"endpointType": "HomeRegion",
"authType": "ApplicationManaged"
}'
Output
{
"functionName": "my-web-function",
"functionArn": "arn:aws:lambda:us-east-1:000000000000:web-function/my-web-function",
"state": "Active",
"stateReason": "The function is active.",
"createdAt": "2026-09-23T14:40:44.226209+00:00",
"updatedAt": "2026-09-23T14:40:44.226209+00:00",
"revision": {
"revisionArn": "arn:aws:lambda:us-east-1:000000000000:web-function/my-web-function/revision/rev-yut6x",
"revisionId": "rev-yut6x",
"state": "Pending",
"stateReason": "The revision is being activated.",
"createdAt": "2026-09-23T14:40:44.444580+00:00"
},
"endpoint": {
"endpointArn": "arn:aws:lambda:us-east-1:000000000000:web-function/my-web-function/endpoint/default",
"endpointName": "default",
"endpointType": "HomeRegion",
"domainName": "01w8hmqg36-lambda-web-us-east-1.localhost.localstack.cloud:4566",
"authType": "ApplicationManaged",
"autoDeploymentMode": "Disabled",
"revisionWeights": [
{
"revisionId": "rev-yut6x",
"weight": 100
}
],
"regions": [
"us-east-1"
],
"state": "Pending",
"stateReason": "The endpoint is being activated and is not yet invokable in any region.",
"createdAt": "2026-09-23T14:40:44.444646+00:00",
"updatedAt": "2026-09-23T14:40:44.444646+00:00"
}
}

The revision and the endpoint start in the Pending state. Wait until the endpoint is Active:

Terminal window
lstk aws lambda-web wait web-function-endpoint-active \
--function-name my-web-function \
--endpoint-name default

The command returns without output once the endpoint is active.

Retrieve the endpoint’s domain name:

Terminal window
lstk aws lambda-web get-web-function-endpoint \
--function-name my-web-function \
--endpoint-name default \
--query domainName \
--output text
Output
01w8hmqg36-lambda-web-us-east-1.localhost.localstack.cloud:4566

Send a request to it with curl. The first request starts a sandbox for the revision, so it can take a few seconds:

Terminal window
curl http://01w8hmqg36-lambda-web-us-east-1.localhost.localstack.cloud:4566/
curl http://01w8hmqg36-lambda-web-us-east-1.localhost.localstack.cloud:4566/message
Output
Hello from LocalStack in us-east-1
Hello from LocalStack!

The endpoint also answers over HTTPS.

The application reads the message from S3 on every request. Upload a new message and call the endpoint again, without redeploying anything:

Terminal window
echo "Hello again, straight from LocalStack S3!" > message.txt
lstk aws s3 cp message.txt s3://web-function-code/message.txt
curl http://01w8hmqg36-lambda-web-us-east-1.localhost.localstack.cloud:4566/message
Output
upload: ./message.txt to s3://web-function-code/message.txt
Hello again, straight from LocalStack S3!

The application’s SDK client needed no endpoint, region or credentials configuration. See Calling AWS services from the application for how LocalStack wires it up.

On AWS, an endpoint’s domain has the shape <id>.<shard>.lambda-web.<region>.on.aws. LocalStack serves the domain as a single label under localhost.localstack.cloud, because the LocalStack certificate only covers *.localhost.localstack.cloud and a wildcard matches a single label:

<id>-lambda-web-<region|global>.localhost.localstack.cloud:<port>

Read the domain from domainName (or regionalEndpoints.*.domainName) rather than building it, and avoid matching on the AWS *.on.aws shape in CORS, cookie or proxy rules, so the same code works locally and on AWS. The host and port come from LOCALSTACK_HOST. An endpoint only answers on the exact domain it was issued: other hostnames that resolve to LocalStack do not reach it, and a Host in upper case or with the scheme’s default port spelled out is answered with 404 and Platform.InvalidEndpoint.

The endpointType decides how many domains an endpoint has and which region serves each one. The function’s home region is always one of the endpoint’s regions. PerRegion and MultiRegion endpoints need at least one more region in --regions:

  • HomeRegion: one domain, served by the function’s home region.
  • PerRegion: one domain for each region, served by that region. The domains share the same <id> and differ only in the region. They are listed under regionalEndpoints.
  • MultiRegion: one domain with global in place of the region. On AWS, the nearest region serves the request. In LocalStack, the home region serves it.

The following commands create a PerRegion and a MultiRegion endpoint for the function. An endpoint whose autoDeploymentMode is Disabled, the default, needs --revision-weights:

Terminal window
lstk aws lambda-web create-web-function-endpoint \
--function-name my-web-function \
--endpoint-name per-region \
--endpoint-type PerRegion \
--auth-type ApplicationManaged \
--regions eu-west-1 \
--revision-weights revisionId=rev-yut6x,weight=100 \
--query 'regionalEndpoints.*.domainName'
lstk aws lambda-web create-web-function-endpoint \
--function-name my-web-function \
--endpoint-name multi-region \
--endpoint-type MultiRegion \
--auth-type ApplicationManaged \
--regions eu-west-1 \
--revision-weights revisionId=rev-yut6x,weight=100 \
--query domainName
Output
[
"2hnfqzbjh3-lambda-web-eu-west-1.localhost.localstack.cloud:4566",
"2hnfqzbjh3-lambda-web-us-east-1.localhost.localstack.cloud:4566"
]
"f3i5kprisi-lambda-web-global.localhost.localstack.cloud:4566"

Once the endpoints are active, each regional domain is served by its own region:

Terminal window
curl http://2hnfqzbjh3-lambda-web-eu-west-1.localhost.localstack.cloud:4566/
curl http://f3i5kprisi-lambda-web-global.localhost.localstack.cloud:4566/
Output
Hello from LocalStack in eu-west-1
Hello from LocalStack in us-east-1

Each call to create-web-function-revision creates a new, immutable revision. For example, change the greeting in index.js to Hello again from LocalStack, run the zip command again, and upload the new code to the same key. Because the bucket is versioned, the first revision keeps the code it was created with:

Terminal window
lstk aws s3api put-object \
--bucket web-function-code \
--key function.zip \
--body function.zip

Then create the revision:

Terminal window
lstk aws lambda-web create-web-function-revision \
--function-name my-web-function \
--build-config '{
"codeConfig": {"s3Object": {"bucket": "web-function-code", "key": "function.zip"}},
"runtimeConfig": {"runtime": "nodejs24.x"}
}' \
--service-config '{
"executionRoleArn": "arn:aws:iam::000000000000:role/web-function-role",
"environmentVariables": {"BUCKET_NAME": "web-function-code"}
}' \
--query revisionId
Output
"rev-aa8go"

When the revision is active, split the endpoint’s traffic between two revisions with --revision-weights. The weights must add up to 100:

Terminal window
lstk aws lambda-web wait web-function-revision-active \
--function-name my-web-function \
--revision-id rev-aa8go
lstk aws lambda-web update-web-function-endpoint \
--function-name my-web-function \
--endpoint-name default \
--revision-weights revisionId=rev-yut6x,weight=50 revisionId=rev-aa8go,weight=50

LocalStack splits requests between the revisions according to the weights. Use lstk aws lambda-web wait web-function-endpoint-updated to wait until the update is applied.

To make a HomeRegion endpoint always serve the newest revision, set its autoDeploymentMode to LatestRevision. The endpoint switches to the newest revision right away, and to every new revision once it is active:

Terminal window
lstk aws lambda-web update-web-function-endpoint \
--function-name my-web-function \
--endpoint-name default \
--auto-deployment-mode LatestRevision

LatestRevision is only available on HomeRegion endpoints, and you cannot set --revision-weights on an endpoint that uses it.

An endpoint with --auth-type IamAuth only serves requests signed with Signature Version 4 for the lambda service and the endpoint’s region. The signing principal needs permission for lambda:InvokeWebFunctionEndpoint on the endpoint’s ARN. LocalStack enforces this even when ENFORCE_IAM is not enabled. The default test credentials are allowed.

Terminal window
lstk aws lambda-web create-web-function-endpoint \
--function-name my-web-function \
--endpoint-name private \
--endpoint-type HomeRegion \
--auth-type IamAuth \
--auto-deployment-mode LatestRevision \
--query domainName
Output
"7mszmdt4ie-lambda-web-us-east-1.localhost.localstack.cloud:4566"

An unsigned request is refused with 403 Forbidden. curl can sign the request with --aws-sigv4:

Terminal window
curl --aws-sigv4 "aws:amz:us-east-1:lambda" \
--user "test:test" \
-H "x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" \
http://7mszmdt4ie-lambda-web-us-east-1.localhost.localstack.cloud:4566/
Output
Hello again from LocalStack in us-east-1

The permission to invoke an endpoint can also come from the web function’s resource-based policy. Attach it to the function’s ARN with put-resource-policy, and scope each statement’s Resource to an endpoint ARN. Use get-resource-policy and delete-resource-policy to read or remove it:

Terminal window
lstk aws lambda-web put-resource-policy \
--resource-arn arn:aws:lambda:us-east-1:000000000000:web-function/my-web-function \
--policy file://policy.json

As on AWS, a principal in another account can invoke the endpoint only when both the resource-based policy and the principal’s own identity policy allow it.

Endpoints apply no CORS handling of their own: your application answers preflight requests and sets the CORS headers. LocalStack’s CORS configuration, such as EXTRA_CORS_ALLOWED_ORIGINS and DISABLE_CORS_CHECKS, does not apply to endpoint requests.

You can put a CloudFront distribution in front of an IamAuth endpoint, as described in Restrict access to an AWS Lambda Web Function origin. CloudFront invokes the endpoint on behalf of the distribution, and the web function’s resource-based policy decides whether the distribution may invoke it.

Create an origin access control with the lambda origin type and the always-amz-auth signing behavior. Use the sigv4 signing protocol for a HomeRegion or PerRegion endpoint, and sigv4a for a MultiRegion endpoint:

Terminal window
lstk aws cloudfront create-origin-access-control \
--origin-access-control-config '{
"Name": "my-lambda-web-oac",
"SigningProtocol": "sigv4",
"SigningBehavior": "always-amz-auth",
"OriginAccessControlOriginType": "lambda"
}' \
--query OriginAccessControl.Id

Create the distribution with the endpoint’s domainName as the origin and the ID of the origin access control. The following distribution-config.json uses the private endpoint from IAM authentication, and forwards the viewer’s Authorization header to the application:

distribution-config.json
{
"CallerReference": "my-web-function-distribution",
"Comment": "Lambda Web origin",
"Enabled": true,
"Origins": {
"Quantity": 1,
"Items": [
{
"Id": "lambda-web",
"DomainName": "7mszmdt4ie-lambda-web-us-east-1.localhost.localstack.cloud:4566",
"OriginAccessControlId": "<origin-access-control-id>",
"CustomOriginConfig": {
"HTTPPort": 80,
"HTTPSPort": 443,
"OriginProtocolPolicy": "https-only"
}
}
]
},
"DefaultCacheBehavior": {
"TargetOriginId": "lambda-web",
"ViewerProtocolPolicy": "allow-all",
"ForwardedValues": {
"QueryString": true,
"Cookies": {"Forward": "none"},
"Headers": {"Quantity": 1, "Items": ["Authorization"]}
},
"MinTTL": 0,
"DefaultTTL": 0,
"MaxTTL": 0
}
}
Terminal window
lstk aws cloudfront create-distribution \
--distribution-config file://distribution-config.json \
--query 'Distribution.[ARN,DomainName]'

Until the web function has a resource-based policy that allows the distribution, the endpoint refuses CloudFront’s requests with 403 Forbidden. Create a cloudfront-policy.json that allows the cloudfront.amazonaws.com service principal to invoke the endpoint, with the distribution’s ARN as aws:SourceArn:

cloudfront-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontOACAccess",
"Effect": "Allow",
"Principal": {"Service": "cloudfront.amazonaws.com"},
"Action": "lambda:InvokeWebFunctionEndpoint",
"Resource": "arn:aws:lambda:us-east-1:000000000000:web-function/my-web-function/endpoint/private",
"Condition": {
"StringEquals": {
"aws:SourceArn": "arn:aws:cloudfront::000000000000:distribution/<distribution-id>"
}
}
}
]
}

Attach the policy to the web function, using the function’s ARN rather than the endpoint’s:

Terminal window
lstk aws lambda-web put-resource-policy \
--resource-arn arn:aws:lambda:us-east-1:000000000000:web-function/my-web-function \
--policy file://cloudfront-policy.json

Requests to the distribution’s domain, with or without the :4566 port, then reach your application with the viewer’s Authorization header unchanged. The domain has two labels under localhost.localstack.cloud, which the LocalStack certificate does not cover, so pass -k to curl:

Terminal window
curl -k https://<distribution-id>.cloudfront.localhost.localstack.cloud/
Output
Hello again from LocalStack in us-east-1

LocalStack validates the configuration as AWS does, and rejects it with an IllegalOriginAccessConfiguration error when:

  • A Lambda Web origin uses an origin access control whose signing behavior is not always-amz-auth.
  • An origin access control with the always-amz-auth signing behavior is used for an origin that is not a Lambda Web endpoint.
  • A MultiRegion endpoint uses the sigv4 signing protocol, or a HomeRegion or PerRegion endpoint uses sigv4a.

Creating or updating an origin access control with the always-amz-auth signing behavior and an origin type other than lambda fails with an InvalidArgument error.

Each endpoint serves requests from sandboxes, called environments:

  • serviceConfig.maxConcurrencyPerEnvironment on the revision sets how many requests one environment serves at the same time (default 64).
  • scalingConfig.maxEnvironments on the endpoint limits how many environments it may run. The maximum is 10,000; a larger value is rejected with a ValidationException.
  • throttleConfig.rateLimit on the endpoint sets how many requests per second it serves in each region. Requests above the limit are throttled and do not reach your application.

If you omit scalingConfig or throttleConfig, LocalStack does not apply the corresponding endpoint limit.

Terminal window
lstk aws lambda-web update-web-function-endpoint \
--function-name my-web-function \
--endpoint-name per-region \
--throttle-config rateLimit=100

Use get-web-account-settings to view the account quotas and the number of web functions in your current account and region:

Terminal window
lstk aws lambda-web get-web-account-settings

If you created only the web function in this guide, the response is:

Output
{
"accountQuotas": {
"maxTotalArmVCpus": 2000,
"maxTotalRateLimit": 10000,
"maxRevisionsPerFunction": 50,
"maxEndpointsPerFunction": 10
},
"accountUsage": {
"functionCount": 1
}
}

Your application’s output goes to CloudWatch Logs. Unless you set serviceConfig.telemetryConfig.loggingConfig.logGroup, the log group is /aws/lambda/web/<function-name>. As on AWS, each event is written to a <log-group>.local group in the region the environment runs in, and copied to the log group in the function’s home region:

Terminal window
lstk aws logs describe-log-groups \
--log-group-name-prefix /aws/lambda/web \
--query 'logGroups[].logGroupName'
Output
[
"/aws/lambda/web/my-web-function",
"/aws/lambda/web/my-web-function.local"
]

If your application fails to start, check the log group for the reason.

LocalStack publishes the following Lambda Web metrics to CloudWatch:

Namespace Metric Dimensions Description
AWS/Lambda/Web ExecutedRequests FunctionName, EndpointName Requests that reached your application.
AWS/Lambda/Web HTTPResponseStatusClass4XX, HTTPResponseStatusClass5XX FunctionName, EndpointName Responses in each status class, including the errors the endpoint returns itself.
AWS/Lambda/Web ResponseBytes FunctionName Response body bytes each environment sent.
AWS/Lambda/Web EnvironmentActiveTime FunctionName Time each environment was running, in milliseconds.
AWS/Lambda/Web EnvironmentConcurrentRequests FunctionName The most requests each environment has served at the same time.
AWS/Lambda/Web/Usage ExecutedRequests FunctionName, EndpointName, ExecutionRegion Requests that reached your application.
AWS/Lambda/Web/Usage EndpointActiveEnvironments FunctionName, EndpointName, ExecutionRegion The endpoint’s environments in the region.
AWS/Lambda/Web/Usage EndpointActiveEnvironmentThrottles FunctionName, EndpointName, ExecutionRegion Requests throttled by scalingConfig.maxEnvironments.
AWS/Lambda/Web/Usage EndpointRequestThrottles FunctionName, EndpointName, ExecutionRegion Requests throttled by throttleConfig.rateLimit.

ResponseBytes, EnvironmentActiveTime and EnvironmentConcurrentRequests are reported by each environment when it starts, once a minute, and when it stops. Throttled requests are counted in HTTPResponseStatusClass4XX and in their throttle metric, but not in ExecutedRequests.

LocalStack relays the application’s response to the client as the application writes it, without buffering. serviceConfig.timeoutSeconds on the revision (default 30) limits the whole request, including the time spent streaming the response. If the limit is reached before the response starts, the endpoint returns an error. If the response has already started, it is cut off at the limit.

An endpoint accepts the GET, HEAD, POST, PUT, PATCH, OPTIONS and DELETE methods. A request with any other method is refused with 400 Bad Request and a lambda-web-error-type: Platform.MalformedRequest header, and does not reach your application:

Terminal window
curl -i -X TRACE http://01w8hmqg36-lambda-web-us-east-1.localhost.localstack.cloud:4566/
Output
HTTP/1.1 400 BAD REQUEST
Content-Type: text/html; charset=utf-8
lambda-web-error-type: Platform.MalformedRequest
x-amz-request-id: 3C7C964182EC999E
...

As on AWS, the request your application receives differs from the one the client sent:

  • Host is the endpoint domain the client used. In LocalStack it includes the gateway port.
  • X-Forwarded-For has the client’s address appended.
  • X-Forwarded-Proto and X-Forwarded-Port are replaced with the scheme and port the client connected to. On AWS they are always https and 443. In LocalStack they are the scheme and port of the LocalStack gateway, for example http and 4566.
  • lambda-web-request-id carries the request ID, and lambda-web-endpoint-name the name of the endpoint that received the request.
  • No X-Amzn-Trace-Id header is added. A trace header the client sends reaches your application unchanged.
  • Upgrade, Expect, Trailer, Proxy-Authenticate, Proxy-Authorization, Proxy-Connection, X-Real-IP, X-HTTP-Method, X-HTTP-Method-Override and X-Method-Override are removed, as is any lambda-web-* header the client sent.
  • A SigV4 Authorization header and its X-Amz-Security-Token are removed, on IamAuth and ApplicationManaged endpoints alike. Other Authorization schemes, and an X-Amz-Security-Token sent without a SigV4 signature, reach your application unchanged.

Every response carries a lambda-web-request-id header, including the errors the endpoint returns itself, except the 400 for an unsupported method. Responses from your application also carry a lambda-web-logstream header that names the log stream of the environment that served the request:

Terminal window
curl -i http://01w8hmqg36-lambda-web-us-east-1.localhost.localstack.cloud:4566/
Output
HTTP/1.1 200 OK
Content-Type: text/plain
lambda-web-logstream: 2026/09/23/my-web-function[rev-yut6x]692c8889e7af48df889ca26e9c6ce8df
lambda-web-request-id: FUUMGFQS6Z2L64PMXWCWKTPJQA
...
Hello from LocalStack in us-east-1

The endpoint sets these headers itself and replaces any value your application sends for lambda-web-request-id or lambda-web-logstream.

When the endpoint answers instead of your application, it returns an HTML error page, and most of these answers carry a lambda-web-error-type header that names the reason. LocalStack returns the same status codes and error types as AWS:

Status lambda-web-error-type Reason
400 Platform.MalformedRequest The request uses an unsupported method.
403 An IamAuth endpoint refused the request.
404 Platform.InvalidEndpoint No endpoint serves the domain.
409 Platform.EndpointNotActive The endpoint is not active yet.
429 Platform.EndpointMaxEnvThrottle All environments allowed by scalingConfig.maxEnvironments are busy.
429 Platform.EndpointRequestThrottle The request exceeds throttleConfig.rateLimit.
503 Platform.EndpointDisabled The endpoint’s rateLimit is 0.
502 Runtime.InitTimeout, Runtime.ImportCodeError, Application.InitCodeError, Application.TimeoutListenError, Runtime.ExitError and others The application failed to start. Check the logs for the reason.
502 Platform.KMSDecryptionError Lambda cannot decrypt the revision with its customer managed key.
503 Runtime.OriginRequestError The application stopped while it handled the request.
504 Runtime.RequestTimeout The application did not answer within serviceConfig.timeoutSeconds.

As on AWS, the sandbox carries the execution role’s credentials in AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN, and the region in AWS_REGION. LocalStack also sets AWS_ENDPOINT_URL to the LocalStack endpoint, so AWS SDK calls from your application reach LocalStack without any change to your code. AWS does not set this variable. Set the configuration option LAMBDA_WEB_DISABLE_AWS_ENDPOINT_URL=1 to turn this off.

As for Lambda functions, LocalStack can serve a revision’s code straight from a directory on your machine and reload it when the code changes. Set the revision’s code bucket to hot-reload and the key to the absolute path of the directory that contains index.js:

Terminal window
lstk aws lambda-web create-web-function \
--function-name my-hot-reload-function \
--revision-config '{
"buildConfig": {
"codeConfig": {"s3Object": {"bucket": "hot-reload", "key": "'"$(pwd)"'"}},
"runtimeConfig": {"runtime": "nodejs24.x"}
},
"serviceConfig": {
"executionRoleArn": "arn:aws:iam::000000000000:role/web-function-role"
}
}' \
--endpoint-config '{
"endpointName": "default",
"endpointType": "HomeRegion",
"authType": "ApplicationManaged"
}' \
--query endpoint.domainName
Output
"nfh7ijz9uu-lambda-web-us-east-1.localhost.localstack.cloud:4566"

Once the endpoint is active, edit index.js and call the endpoint again. About a second after the last change, LocalStack restarts your application in the same sandbox, so /tmp and the log stream are kept. Requests that arrive while the application restarts wait for it:

Terminal window
curl http://nfh7ijz9uu-lambda-web-us-east-1.localhost.localstack.cloud:4566/
sed -i '' 's/Hello from hot reloading/Hello again, reloaded/' index.js
curl http://nfh7ijz9uu-lambda-web-us-east-1.localhost.localstack.cloud:4566/
Output
Hello from hot reloading
Hello again, reloaded
  • The directory is mounted read-only at /var/task in the sandbox.
  • Changes inside node_modules do not trigger a reload, except an npm install, which triggers one when it finishes.
  • The key can contain environment variables such as $HOME, which are expanded in the LocalStack container’s environment, not your shell’s.
  • No code is fetched from S3, so the S3 bucket, code access and code size checks do not apply.
  • The application gets 60 seconds to start instead of 10, because reading code through a bind mount is slower.
  • Hot reloading is not available when LocalStack runs sandboxes on Kubernetes.

Set kmsKeyArn on a revision to encrypt its code and environment variables with a customer managed KMS key. As on AWS, LocalStack uses the key when it creates the revision and every time it starts a sandbox. If the key is disabled or Lambda may no longer use it, sandboxes do not start and requests are answered with 502 and Platform.KMSDecryptionError. Whether the key policy allows Lambda to decrypt is only checked when ENFORCE_IAM is enabled.

Web functions support tags, set with --tags on create-web-function or with tag-resource, untag-resource and list-tags. Endpoints and revisions use their function’s tags. Tagged web functions are also returned by the Resource Groups Tagging API:

Terminal window
lstk aws lambda-web tag-resource \
--resource arn:aws:lambda:us-east-1:000000000000:web-function/my-web-function \
--tags team=web
lstk aws resourcegroupstaggingapi get-resources \
--tag-filters Key=team,Values=web \
--query 'ResourceTagMappingList[].ResourceARN'
Output
[
"arn:aws:lambda:us-east-1:000000000000:web-function/my-web-function"
]

LocalStack supports the AWS::Lambda::WebFunction, AWS::Lambda::WebFunctionRevision and AWS::Lambda::WebFunctionEndpoint resource types, so you can deploy web functions, their revisions and their endpoints with CloudFormation templates.

Each environment runs as a Docker container from the docker.io/library/node:24-slim image. It is limited to two CPUs, 2 GiB of memory and 1,024 open files. The 512 MB limit for temporary storage is not enforced.

By default, LocalStack lets the container runtime choose the sandbox architecture, so the sandbox runs natively on x86 hosts. Native dependencies must then match the host architecture, and can work locally but fail on AWS, which runs web functions on Arm64. Set LAMBDA_WEB_IGNORE_ARCHITECTURE=0 to require Arm64 and check compatibility.

Use LAMBDA_WEB_RUNTIME_IMAGE_MAPPING to run sandboxes from your own image, for example a mirror of the Node.js image. It takes a pattern with a <runtime> placeholder, such as custom-repo/web-<runtime>:1, or a JSON object that maps a runtime to an image, such as {"nodejs24.x": "custom-repo/node:24-web"}. LAMBDA_RUNTIME_IMAGE_MAPPING does not apply to web functions. The image must:

  • Provide node on the PATH, in the major version of the runtime, and sh.
  • Use an entrypoint that runs its arguments as a command, as the official Node.js images do.
  • Need nothing under /var, because LocalStack copies the runtime and your code there.

When LocalStack runs sandboxes with the Kubernetes executor, each environment runs as a pod. Sandbox pods take the same K8S_LABELS, K8S_ANNOTATIONS, K8S_POD_SECURITY_CONTEXT, K8S_CONTAINER_SECURITY_CONTEXT and DOCKER_GLOBAL_IMAGE_PREFIX settings as other pods LocalStack creates, and a K8S_POD_CONFIG profile for the lambda-web service applies to them only.

Web functions, revisions, endpoints, tags and resource-based policies are restored with persistence and Cloud Pods. After a restart or once a Cloud Pod is loaded, endpoints serve requests again, and environments start on the first request.

  • Endpoint domains use a LocalStack-specific shape under localhost.localstack.cloud instead of *.on.aws. See Endpoint domains.
  • MultiRegion endpoints are always served by the function’s home region, not by the region nearest to the client.
  • The request body is not streamed to the application. LocalStack reads the whole body before it forwards the request.
  • Requests with very large headers can be rejected before they reach your application.

20 of 20 operations implemented

Available from the Hobby plan. Licensing details

Find an API

Search the full operation list, then sort the table to compare current support.

Loading operations…

Verified on Kubernetes
Loading operations…
Complete static API list All 20 operations and their current support status
OperationStatus
CreateWebFunctionImplemented
CreateWebFunctionEndpointImplemented
CreateWebFunctionRevisionImplemented
DeleteResourcePolicyImplemented
DeleteWebFunctionImplemented
DeleteWebFunctionEndpointImplemented
DeleteWebFunctionRevisionImplemented
GetResourcePolicyImplemented
GetWebAccountSettingsImplemented
GetWebFunctionImplemented
GetWebFunctionEndpointImplemented
GetWebFunctionRevisionImplemented
ListTagsImplemented
ListWebFunctionEndpointsImplemented
ListWebFunctionRevisionsImplemented
ListWebFunctionsImplemented
PutResourcePolicyImplemented
TagResourceImplemented
UntagResourceImplemented
UpdateWebFunctionEndpointImplemented
Was this page helpful?