MonetizationOS Docs
Getting StartedIntegrate with MOS

AWS Lambda Proxy

The MonetizationOS AWS Lambda proxy runs as a Lambda Web Function in your own AWS account, behind an Amazon CloudFront distribution that serves your site. A single script builds the proxy and deploys it with a CloudFormation stack.

View on GitHub

What is a Lambda Web Function?

A Lambda Web Function runs a Node.js HTTP server on AWS Lambda. You deploy a .zip file, and Lambda creates an HTTPS endpoint, manages the execution environments, and scales them with traffic. CloudFront sits in front of that endpoint to serve your domain.

The proxy sits between your visitors and your origin server:

CloudFront terminates TLS on your domain, forwards each request to the Web Function without caching it, and adds its viewer location, network, and TLS headers. Paths that end in a static file extension, such as .css or .jpg, go straight to your origin instead, since the proxy would pass them through unchanged. The proxy itself connects to MonetizationOS from an AWS Region, so it sends that viewer data from CloudFront's headers with each Surface Decision API request.

Installation and configuration

Prerequisites

  • An AWS account, and the AWS CLI v2 with aws lambda-web support, authenticated with aws configure

  • Node.js 24, pnpm, zip, shasum, and openssl

  • An S3 bucket for the proxy code, with versioning enabled and a bucket policy that lets Lambda read it from your account:

    bucket-policy.json
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Principal": { "Service": "lambda.amazonaws.com" },
                "Action": ["s3:GetObject", "s3:GetObjectVersion"],
                "Resource": "arn:aws:s3:::<CODE_BUCKET>/*",
                "Condition": { "StringEquals": { "aws:SourceAccount": "<ACCOUNT_ID>" } }
            }
        ]
    }
  • Your MonetizationOS secret key, from environment settings

  • An origin URL that resolves straight to your origin server, in the form https://host[:port][/path], for example https://origin.example.com. If you later point your published domain at CloudFront, this URL must not resolve to CloudFront too

  • Ideally, an origin behind its own CDN with cacheable responses. Nothing in the stack caches requests to your origin, so its CDN is what keeps them off your servers

1. Clone and install

git clone https://github.com/MonetizationOS/aws-lambda-web-proxy.git
cd aws-lambda-web-proxy
pnpm install

To check the build before you deploy, run pnpm e2e. It builds the proxy and runs the bundle as Lambda would, against a fake origin and MonetizationOS API.

2. Deploy the stack

Deploy in the AWS Region nearest your origin. Each page waits on your origin and then on MonetizationOS, so the proxy belongs next to the origin, not the viewer. The script deploys to the Region your AWS CLI is configured for.

STACK=mos-proxy CODE_BUCKET=<CODE_BUCKET> ORIGIN_URL=https://origin.example.com \
  MOS_SECRET_KEY=<YOUR_SECRET_KEY> infra/deploy.sh

infra/deploy.sh:

  1. Builds the proxy and zips build/ without source maps
  2. Uploads the zip to s3://<CODE_BUCKET>/mos-proxy/, under a new key for each build
  3. On the first deploy of the stack, generates the shared origin secret, OriginVerifySecret
  4. Deploys infra/template.yaml with aws cloudformation deploy
  5. Under IamAuth, attaches the Web Function resource policy that lets only this distribution invoke the endpoint
  6. Prints the distribution's URL

The stack contains the Web Function, its revision and prod endpoint, an execution role, the CloudFront distribution, a CloudFront Function, and, under IamAuth, an Origin Access Control.

When the script finishes, its last line is the distribution's URL, in the form https://<DISTRIBUTION_ID>.cloudfront.net/.

3. Request a page through CloudFront

curl -i https://<DISTRIBUTION_ID>.cloudfront.net/

The response is your origin's page, served through the proxy.

Requests that skip CloudFront are refused. The stack outputs include EndpointDomainName, the Web Function endpoint's own domain:

aws cloudformation describe-stacks --stack-name mos-proxy \
  --query "Stacks[0].Outputs[?OutputKey=='EndpointDomainName'].OutputValue" --output text

A request straight to that domain is rejected by Lambda under IamAuth, and gets a 403 from the proxy under ApplicationManaged.

4. Configure the stack

The template sets each proxy environment variable on the Web Function revision from a stack parameter. infra/deploy.sh reads the variables in the Script variable column from your shell. Pass any other parameter as an argument, for example infra/deploy.sh AnonymousSessionCookieName=my-anon.

Environment variableStack parameterScript variableTemplate default
MONETIZATION_OS_SECRET_KEYMosSecretKeyMOS_SECRET_KEYNone, required for a new stack
ORIGIN_URLOriginUrlORIGIN_URLNone, required
SURFACE_SLUGSurfaceSlugSURFACE_SLUGweb
MONETIZATION_OS_HOSTMosHostMOS_HOSThttps://api.monetizationos.com
INJECT_SCRIPT_URLInjectScriptUrlINJECT_SCRIPT_URLNone, so no script is injected
AUTHENTICATED_USER_JWT_COOKIE_NAMEAuthenticatedUserJwtCookieName__session
ANONYMOUS_SESSION_COOKIE_NAMEAnonymousSessionCookieNameanon-session-id
MONETIZATION_OS_ENDPOINTS_PREFIXMosEndpointsPrefix/mos-endpoints/
ORIGIN_REQUEST_HEADERSOriginRequestHeaders{}

The variables mean the same as in the Cloudflare Proxy Worker reference. To use MonetizationOS web components, set INJECT_SCRIPT_URL to https://assets.monetizationos.com/web-components-latest.js. ORIGIN_REQUEST_HEADERS is a JSON object of headers added to every request to your origin:

infra/deploy.sh 'OriginRequestHeaders={"X-Api-Key": "secret", "X-Custom": "my-value"}'

Setting OriginRequestHeaders turns off static paths, since CloudFront can't add those headers to requests it sends straight to your origin.

MosSecretKey, OriginVerifySecret, and OriginRequestHeaders are NoEcho, so CloudFormation doesn't display them. They're still stored as revision environment variables and CloudFront origin headers, so anyone who can read the Web Function or distribution configuration can read them.

Stack settings

These parameters configure the stack rather than the proxy:

Stack parameterScript variableTemplate defaultDescription
MaxEnvironmentsMAX_ENVIRONMENTS0An optional cap on the endpoint's execution environments, from 2 to 10,000. 0 leaves it uncapped, so only your account's vCPU quota bounds scaling. At the cap, Lambda answers further requests with 429 instead of scaling
LogRetentionInDaysLOG_RETENTION_IN_DAYS14How long CloudWatch keeps the proxy's logs. It applies to both log groups the stack owns, and deleting the stack deletes them
StaticExtensionsSee belowFile extensions whose paths go straight to your origin

Leave MaxEnvironments at 0 in production. Set a cap only to bound the cost of a test stack.

Static paths

The proxy passes non-HTML responses through unchanged, so the CloudFront Function sends paths ending in a static file extension straight to ORIGIN_URL, without the Web Function. This keeps assets off the endpoint's request rate and execution environments. StaticExtensions lists the extensions, matched case-insensitively, and defaults to:

css,js,mjs,map,png,jpg,jpeg,gif,webp,avif,svg,ico,woff,woff2,ttf,otf,eot,mp4,webm,mp3,wasm

Paths under MONETIZATION_OS_ENDPOINTS_PREFIX always go to the proxy. To send every path through the proxy, for example when your origin only accepts requests from the proxy, set it to an empty string:

infra/deploy.sh StaticExtensions=

CloudFront doesn't cache these requests either, so your origin's CDN serves them. They reach your origin without the Origin Access Control signature or the shared secret.

Origin auth

OriginAuth sets how the Web Function endpoint keeps out requests that don't come through CloudFront. Set it with the ORIGIN_AUTH script variable:

  • IamAuth (default for new stacks): CloudFront signs each request with Origin Access Control, and Lambda rejects any other caller. The proxy checks the shared secret as well
  • ApplicationManaged: the endpoint is public, and only the shared secret keeps direct callers out

CloudFront sends the shared secret as the x-mos-origin-verify header. You never set it: infra/deploy.sh generates it on the first deploy and keeps it on later ones, as the OriginVerifySecret parameter, and the template passes it to the proxy as ORIGIN_VERIFY_SECRET. The proxy answers a request without the right value with a 403, and removes the header before requests reach your origin. See Web Function endpoint access control for how Lambda applies AuthType.

Optional settings

SURFACE_DECISIONS_IGNORE_PATHS and SURFACE_DECISIONS_COOKIES have no stack parameter. To set them, add them to the revision's EnvironmentVariables in infra/template.yaml, then redeploy:

infra/template.yaml
EnvironmentVariables:
    # ...
    SURFACE_DECISIONS_IGNORE_PATHS: "^/static/, ^/health$"
    SURFACE_DECISIONS_COOKIES: "^__session$, ^theme$, ^mos_"
  • SURFACE_DECISIONS_IGNORE_PATHS: comma-separated regular expressions. Matching paths still proxy to the origin and have origin links rewritten, but skip surface decisions and component transforms
  • SURFACE_DECISIONS_COOKIES: comma-separated regular expressions tested against cookie names. Matching cookies, from the request and from the origin response's Set-Cookie headers, are sent to the Surface Decision API as http.cookies. When a name appears in both, the origin's value is used

5. Serve your domain

To serve your site on your own domain, add Aliases and a certificate to the distribution in infra/template.yaml. Request the AWS Certificate Manager certificate in us-east-1, whichever Region the stack runs in:

infra/template.yaml
Distribution:
    Type: AWS::CloudFront::Distribution
    Properties:
        DistributionConfig:
            Aliases: [news.example.com]
            ViewerCertificate:
                AcmCertificateArn: <US_EAST_1_CERTIFICATE_ARN>
                SslSupportMethod: sni-only
            # ...

Redeploy, then create a CNAME, or a Route 53 alias record, from your domain to the distribution's cloudfront.net domain. See alternate domain names in the CloudFront docs. Keep ORIGIN_URL on a hostname that resolves to your origin server: if it resolves to CloudFront, each origin fetch returns to the proxy and the request loops.

The proxy rebuilds each page's public URL from the viewer's Host, so origin links are rewritten to your domain.

6. Redeploy

Redeploy after changes with the same command. MOS_SECRET_KEY can be left out, since the stack keeps its current key:

STACK=mos-proxy CODE_BUCKET=<CODE_BUCKET> ORIGIN_URL=https://origin.example.com infra/deploy.sh

Each deploy uploads a new zip and publishes a new Web Function revision. A parameter you leave out keeps its current value, and the shared origin secret is only generated for a new stack.

For canonical setup details and updates, see the project README.

Next steps

On this page