Writing

2026.05.05

Running the Obscura browser on AWS Lambda

Obscura is a stealth Chromium fork that exposes a Chrome DevTools Protocol (CDP) endpoint, which makes it a drop-in target for Playwright or Puppeteer when you want a less obviously automated browser.

Getting it running on AWS Lambda was annoying enough that I wrote a small package to make it boring instead: node-obscura-aws-lambda (npm).

It is two things in one repo:

The result is that you can npm install it into a normal Node Lambda function and call startObscura(). No custom runtime, no Lambda layer juggling, no container image required.

Why this is needed

The upstream Obscura release binaries are built against a recent Linux toolchain and dynamically link against glibc 2.35+. AWS Lambda’s Node.js runtimes ship on Amazon Linux 2023, which provides an older glibc. Trying to execute the upstream binary inside a Lambda function results in the familiar dynamic linker errors:

./obscura: /lib64/libc.so.6: version `GLIBC_2.35' not found

The fix is unglamorous: rebuild Obscura inside the same userspace Lambda actually runs on. That is the entire reason this package exists.

How the build works

The repo is Docker-first, so you do not need a local Rust toolchain. One npm script clones upstream Obscura, builds it inside an amazonlinux:2023 image, and packages obscura and obscura-worker into a reproducible tarball:

git clone https://github.com/chegger/node-obscura-aws-lambda
cd node-obscura-aws-lambda
npm run build:artifact

That produces dist/obscura-x86_64-linux-lambda.tar.gz plus a SHA256 and a small build-metadata file. There is also a smoke test that runs the binary inside the AWS Lambda Node 24 base image and waits for /json/version to come up:

npm run smoke:test

The published package version (v0.1.0 at the time of writing) maps to a GitHub release that hosts that tarball as a download asset. The npm package itself stays small; the binary is fetched on install.

How the wrapper works

The runtime side is intentionally small. On npm install, a postinstall script downloads the matching tarball, verifies the platform is Linux x64, extracts the binaries into binaries/linux-x64-lambda/, and marks them executable.

At runtime, startObscura() picks an open port, spawns obscura serve --port <port>, polls /json/version until the CDP endpoint is ready, and returns the HTTP and WebSocket endpoints plus a close() handle. The API is intentionally identical to @chegger/node-obscura so you can swap one for the other.

Using it from a Lambda

Install:

npm install @chegger/node-obscura-aws-lambda playwright-core

Handler:

const { chromium } = require('playwright-core');
const { startObscura } = require('@chegger/node-obscura-aws-lambda');

exports.handler = async () => {
  const obscura = await startObscura({ stealth: true });
  const browser = await chromium.connectOverCDP(obscura.endpoint);

  try {
    const context = browser.contexts()[0] || (await browser.newContext());
    const page = await context.newPage();
    await page.goto('https://example.com');
    return { title: await page.title() };
  } finally {
    await browser.close();
    await obscura.close();
  }
};

If you bundle the Lambda with aws-cdk-lib/aws-lambda-nodejs, force Docker bundling so the package’s Linux binary gets installed inside a Lambda-shaped environment rather than on your local machine:

const myLambdaFunction = new lambda.NodejsFunction(this, 'MyLambdaFunction', {
  // ...
  runtime: l.Runtime.NODEJS_24_X,
  architecture: l.Architecture.X86_64,
  bundling: {
    forceDockerBundling: true,
    nodeModules: ['@chegger/node-obscura-aws-lambda'],
  },
});

A few practical notes for Lambda:

Customising it

A few environment variables let you point the build and install at different sources, which is useful if you want to pin a specific upstream Obscura tag or host the binary somewhere private:

Why publish it

Rebuilding a stealth browser against an old glibc is not interesting work. Doing it once, pinning it to a Lambda-shaped runtime, and publishing both the artifact and a small wrapper means the next person (often a future me) can just npm install and move on.

If you run into issues, please open one on GitHub. Patches that add arm64 support or a precompiled nodejs22.x variant are very welcome.