# Lambda Web Functions

Source: /aws/services/lambda-web/

## Introduction

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](#api-coverage), which provides information on the extent of Lambda Web Functions' integration with LocalStack.

## Getting started

This guide is designed for users new to Lambda Web Functions and assumes basic knowledge of the AWS CLI and our [`lstk aws`](/aws/developer-tools/running-localstack/lstk/cloud-and-iac-commands/#aws) command.
You also need [Node.js](https://nodejs.org/) 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](/aws/services/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.

### Create the application

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:

```javascript title="index.js" showLineNumbers
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:

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

### Upload the code and the message to S3

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:

```bash
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
```

```bash title="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:

```bash
echo "Hello from LocalStack!" > message.txt
lstk aws s3 cp message.txt s3://web-function-code/message.txt
```

```bash title="Output"
upload: ./message.txt to s3://web-function-code/message.txt
```

### Create an execution role

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`:

```json title="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`:

```json title="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:

```bash
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`](/aws/customization/configuration-options/#iam).

### Create the web function

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`):

```bash
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"
    }'
```

```bash title="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"
    }
}
```

### Wait for the endpoint

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

```bash
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.

### Call the endpoint

Retrieve the endpoint's domain name:

```bash
lstk aws lambda-web get-web-function-endpoint \
    --function-name my-web-function \
    --endpoint-name default \
    --query domainName \
    --output text
```

```bash title="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:

```bash
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
```

```bash title="Output"
Hello from LocalStack in us-east-1
Hello from LocalStack!
```

The endpoint also answers over HTTPS.

### Change the message

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

```bash
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
```

```bash title="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](#calling-aws-services-from-the-application) for how LocalStack wires it up.

## Endpoint domains

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:

```text
<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`](/aws/customization/configuration-options/#core).
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`:

```bash
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
```

```bash title="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:

```bash
curl http://2hnfqzbjh3-lambda-web-eu-west-1.localhost.localstack.cloud:4566/
curl http://f3i5kprisi-lambda-web-global.localhost.localstack.cloud:4566/
```

```bash title="Output"
Hello from LocalStack in eu-west-1
Hello from LocalStack in us-east-1
```

## Revisions and traffic shifting

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:

```bash
lstk aws s3api put-object \
    --bucket web-function-code \
    --key function.zip \
    --body function.zip
```

Then create the revision:

```bash
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
```

```bash title="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:

```bash
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:

```bash
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.

## IAM authentication

An endpoint with `--auth-type IamAuth` only serves requests signed with [Signature Version 4](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html) 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`](/aws/customization/configuration-options/#iam) is not enabled.
The default `test` credentials are allowed.

```bash
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
```

```bash title="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`:

```bash
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/
```

```bash title="Output"
Hello again from LocalStack in us-east-1
```

### Resource-based policies

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:

```bash
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.

### Cross-origin requests

Endpoints apply no CORS handling of their own: your application answers preflight requests and sets the CORS headers.
LocalStack's [CORS configuration](/aws/customization/configuration-options/#security), such as `EXTRA_CORS_ALLOWED_ORIGINS` and `DISABLE_CORS_CHECKS`, does not apply to endpoint requests.

## Serving through CloudFront

You can put a [CloudFront](/aws/services/cloudfront/) distribution in front of an `IamAuth` endpoint, as described in [Restrict access to an AWS Lambda Web Function origin](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-lambda-web.html).
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:

```bash
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](#iam-authentication), and forwards the viewer's `Authorization` header to the application:

```json title="distribution-config.json" showLineNumbers
{
  "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
  }
}
```

```bash
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`:

```json title="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:

```bash
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:

```bash
curl -k https://<distribution-id>.cloudfront.localhost.localstack.cloud/
```

```bash title="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.

:::note
LocalStack does not sign the origin requests with SigV4 or SigV4a; it marks them internally as coming from the distribution.
Only the `aws:SourceArn` and `aws:SourceAccount` conditions in the resource-based policy are evaluated, and other conditions are ignored.
LocalStack does not reject Lambda@Edge origin request and origin response triggers on cache behaviors with a Lambda Web origin, and does not return a `404` when the viewer's `Host` header is forwarded to the origin.
:::

## Scaling and throttling

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.

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

## Account settings

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

```bash
lstk aws lambda-web get-web-account-settings
```

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

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

:::note
LocalStack reports the account vCPU and request-rate quotas but does not enforce them.
You can set endpoint limits using `scalingConfig` and `throttleConfig` as described in [Scaling and throttling](#scaling-and-throttling).
:::

## Logs

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:

```bash
lstk aws logs describe-log-groups \
    --log-group-name-prefix /aws/lambda/web \
    --query 'logGroups[].logGroupName'
```

```bash title="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.

## Metrics

LocalStack publishes the following [Lambda Web metrics](https://docs.aws.amazon.com/lambda/latest/dg/web-functions-monitoring-metrics.html) 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`.

:::note
LocalStack publishes metrics in the region that serves the request and does not aggregate them in the function's home region.
LocalStack does not publish `EnvironmentCPUTime`, `EnvironmentMaxMemoryUsage`, `AccountActiveVCPUArm64`, `AccountRequestThrottles` or `AccountActiveVCPUArm64Throttles`.
LocalStack starts environments on demand, so `EndpointActiveEnvironments` can be lower than on AWS, which keeps environments warm.
:::

## Response streaming and timeouts

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.

## Request and response headers

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:

```bash
curl -i -X TRACE http://01w8hmqg36-lambda-web-us-east-1.localhost.localstack.cloud:4566/
```

```bash title="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](#logs) of the environment that served the request:

```bash
curl -i http://01w8hmqg36-lambda-web-us-east-1.localhost.localstack.cloud:4566/
```

```bash title="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`.

### Errors returned by the endpoint

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](#logs) for the reason. |
| `502` | `Platform.KMSDecryptionError` | Lambda cannot decrypt the revision with its [customer managed key](#customer-managed-keys). |
| `503` | `Runtime.OriginRequestError` | The application stopped while it handled the request. |
| `504` | `Runtime.RequestTimeout` | The application did not answer within `serviceConfig.timeoutSeconds`. |

## Calling AWS services from the application

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](/aws/customization/configuration-options/#lambda-web-functions) `LAMBDA_WEB_DISABLE_AWS_ENDPOINT_URL=1` to turn this off.

## Hot reloading

As for [Lambda functions](/aws/developer-tools/lambda-tools/hot-reloading/), 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`:

```bash
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
```

```bash title="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:

```bash
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/
```

```bash title="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.

## Customer managed keys

Set `kmsKeyArn` on a revision to encrypt its code and environment variables with a customer managed [KMS](/aws/services/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`](/aws/customization/configuration-options/#iam) is enabled.

## Tags

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](/aws/services/resource-groups-tagging-api/):

```bash
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'
```

```bash title="Output"
[
    "arn:aws:lambda:us-east-1:000000000000:web-function/my-web-function"
]
```

## CloudFormation

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](/aws/services/cloudformation/) templates.

## Sandboxes

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`](/aws/customization/configuration-options/#lambda-web-functions) to require Arm64 and check compatibility.

### Custom sandbox images

Use [`LAMBDA_WEB_RUNTIME_IMAGE_MAPPING`](/aws/customization/configuration-options/#lambda-web-functions) 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.

### Kubernetes

When LocalStack runs sandboxes with the [Kubernetes executor](/aws/customization/kubernetes/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`](/aws/customization/kubernetes/pod-configuration/) profile for the `lambda-web` service applies to them only.

## Persistence

Web functions, revisions, endpoints, tags and resource-based policies are restored with [persistence](/aws/developer-tools/snapshots/persistence/) and [Cloud Pods](/aws/developer-tools/snapshots/cloud-pods/).
After a restart or once a Cloud Pod is loaded, endpoints serve requests again, and environments start on the first request.

## Current Limitations

- Endpoint domains use a LocalStack-specific shape under `localhost.localstack.cloud` instead of `*.on.aws`.
  See [Endpoint domains](#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.

## API Coverage


### lambda-web API coverage

Source service: `lambda-web`. 20 of 20 tracked operations are implemented.

Service documentation: /aws/services/lambda-web/
License availability: available starting with the Hobby plan. See /aws/licensing/ for current plan details.

| Operation | Status |
| --- | --- |
| CreateWebFunction | Implemented |
| CreateWebFunctionEndpoint | Implemented |
| CreateWebFunctionRevision | Implemented |
| DeleteResourcePolicy | Implemented |
| DeleteWebFunction | Implemented |
| DeleteWebFunctionEndpoint | Implemented |
| DeleteWebFunctionRevision | Implemented |
| GetResourcePolicy | Implemented |
| GetWebAccountSettings | Implemented |
| GetWebFunction | Implemented |
| GetWebFunctionEndpoint | Implemented |
| GetWebFunctionRevision | Implemented |
| ListTags | Implemented |
| ListWebFunctionEndpoints | Implemented |
| ListWebFunctionRevisions | Implemented |
| ListWebFunctions | Implemented |
| PutResourcePolicy | Implemented |
| TagResource | Implemented |
| UntagResource | Implemented |
| UpdateWebFunctionEndpoint | Implemented |
