Python Serverless on AWS Lambda · APIs and Events in Python

FastAPI on Lambda: Mangum and the Web Adapter

Why a FastAPI app deployed to Lambda returns 404 for every route, and how to run ASGI apps on Lambda well.

Scenario

Example app: a team moves an existing FastAPI service to Lambda behind an API Gateway REST API stage called prod, using the Mangum adapter.

What you see in production

Every request returns {"detail":"Not Found"} from FastAPI. The OpenAPI docs page loads but its requests fail. Startup work in @app.on_event("startup") also seems to run at unexpected times.

The naive code

from fastapi import FastAPI
from mangum import Mangum

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(item_id: str):
    return {"id": item_id}

handler = Mangum(app)      # requests arrive as /prod/items/42

Reproduce it

The router sees the stage prefix as part of the path unless it is stripped:

Stage prefixes and route matchingTry it yourself
ROUTES = {"/items/{item_id}"}

def match(path):
    parts = path.strip("/").split("/")
    for route in ROUTES:
        rparts = route.strip("/").split("/")
        if len(rparts) == len(parts) and all(r.startswith("{") or r == p for r, p in zip(rparts, parts)):
            return f"200 {route} -> {parts[-1]}"
    return "404 Not Found"

incoming = "/prod/items/42"
print("as received:          ", match(incoming))
print("stage prefix stripped:", match(incoming.removeprefix("/prod")))

Output

as received:           404 Not Found
stage prefix stripped: 200 /items/{item_id} -> 42

Root cause

REST API stages (and custom domain base paths) add a prefix to the path that the ASGI app does not know about. Frameworks match routes against the full path and return 404. Separately, ASGI lifespan events run at cold start per environment, not once per deployment, so startup work runs many times and must be cheap and idempotent.

The fix

  • Tell the adapter about the base path (Mangum(app, api_gateway_base_path="/prod")), use an HTTP API with the $default stage (no prefix), or a custom domain mapping without a base path. Set FastAPI's root_path so generated URLs (docs, redirects) include the prefix.
  • Alternatively, run the app unchanged with the AWS Lambda Web Adapter (an extension that proxies Lambda events to your web server, for example uvicorn in a container image or ZIP), which keeps the same code path as running in a container.
  • Keep start-up work light: create clients at import, avoid heavy work in lifespan handlers, and size memory to get enough CPU for FastAPI/Pydantic initialisation.

Trade-offs

  • One FastAPI "Lambda-lith" keeps the developer experience and shares warm environments across routes; separate functions per route give finer permissions and scaling.
  • Adapters add a little latency and init time compared with native handlers.
  • Long-lived features (WebSockets, background tasks after the response, streaming without response streaming support) do not map cleanly to Lambda.

Detect it in production

  • 404s for every route right after deployment; smoke tests against the deployed stage URL.
  • Init Duration of large apps; consider SnapStart or provisioned concurrency for user-facing APIs.
  • Background tasks that never complete (the environment freezes after the response).

Key points

  • Stage or base-path prefixes break routing unless the adapter strips them.
  • Mangum with api_gateway_base_path, HTTP API $default, or the Lambda Web Adapter.
  • Lifespan runs per environment; FastAPI background tasks do not survive the freeze.

Further reading: Mangum on GitHub · AWS Lambda Web Adapter · FastAPI: behind a proxy (root_path)

Practice questions

Why does a FastAPI app on Lambda behind a REST API stage return 404 for every route?

Answer

The stage name (or a custom domain base path) is part of the request path, for example /prod/items/42, but the app's routes do not include it. Configure the adapter to strip the base path, or use a setup without a prefix.

What is the AWS Lambda Web Adapter?

Answer

A Lambda extension that runs alongside your web server process and translates Lambda invocations into HTTP requests to it, so frameworks such as FastAPI, Flask or Express run without code changes, in ZIP or container image functions.

Why are FastAPI BackgroundTasks unreliable on Lambda?

Answer

They run after the response is sent, but Lambda freezes the execution environment when the invocation completes, so background work may not run or may resume during a later invocation. Hand such work to a queue instead.

Open in the app to mark lessons complete, take notes and track your progress.