Symptom
Every invocation fails in a few milliseconds. The function log shows one of these, and none of your own log lines:
[ERROR] Runtime.ImportModuleError: Unable to import module 'lambda_function': No module named 'requests'
[ERROR] Runtime.ImportModuleError: Unable to import module 'app': No module named 'app'
{"errorType":"Runtime.ImportModuleError","errorMessage":"Error: Cannot find module 'index'"}
{"errorType":"Runtime.HandlerNotFound","errorMessage":"index.handler is undefined or not exported"}
The error is raised by the runtime while it loads your handler, so the cause is almost always in what was deployed, not in the business logic. It usually appears right after a new deployment, a runtime upgrade, a switch to arm64 or a layer change.
Quick map: message, cause, fix
| Message | Likely cause | Fix |
|---|---|---|
No module named '<your file>' / Cannot find module 'index' | Handler string doesn't match the file, or the code is inside a sub-folder of the zip | Fix the handler or re-zip from inside the folder |
No module named '<library>' | Dependency not packaged, or layer uses the wrong folder layout | Install into the package root or the layer's python/ / nodejs/node_modules/ |
HandlerNotFound / Handler 'x' missing on module | Function name in the handler string doesn't exist or isn't exported | Match file.function exactly; export it |
invalid ELF header, cannot open shared object file, _multiarray_umath | Native library built on Windows/macOS or for the wrong architecture | Build for Linux and the function's architecture |
Cannot find module 'aws-sdk' | Node.js 18+ runtimes ship AWS SDK v3 only | Move to @aws-sdk/* v3 clients or bundle v2 |
No module named 'distutils' / 'imp' | Removed from the Python 3.12 standard library | Upgrade the library that imports it |
EACCES / Permission denied on load | Zip built with files the runtime user can't read | Fix permissions before zipping |
1. Confirm what Lambda thinks it is running
Start with the configuration. Many "it works locally" cases are a mismatch here.
aws lambda get-function-configuration --function-name my-func \
--query '{Handler:Handler,Runtime:Runtime,Arch:Architectures,Layers:Layers[].Arn,PackageType:PackageType,LastUpdate:LastUpdateStatus}'
Reproduce the error from the CLI and see the tail of the log in one step:
aws lambda invoke --function-name my-func \
--cli-binary-format raw-in-base64-out --payload '{}' \
--log-type Tail out.json \
--query 'LogResult' --output text | base64 -d
If LastUpdateStatus is InProgress or Failed, wait or fix the failed update first. Invocations during an update may hit the previous version.
2. Check the handler string against the file
The handler is <file path without extension>.<function name>:
| Runtime | Code | Handler |
|---|---|---|
| Python | lambda_function.py with def lambda_handler(event, context) | lambda_function.lambda_handler |
| Python, sub-folder | src/app.py with def handler(...) | src/app.handler (Python treats src as a package path) |
| Node.js | index.mjs with export const handler = ... | index.handler |
| Node.js, built output | dist/index.js with exports.handler = ... | dist/index.handler |
Fix it in place:
aws lambda update-function-configuration --function-name my-func \
--handler lambda_function.lambda_handler
For Node.js, HandlerNotFound means the file loaded but the named export wasn't found. Check for a default export (export default) where a named handler export is expected, or a TypeScript build that didn't emit the file you point at.
3. Look inside the deployed zip
Download exactly what Lambda has, not what you think the pipeline uploaded:
URL=$(aws lambda get-function --function-name my-func --query 'Code.Location' --output text)
curl -s -o deployed.zip "$URL"
unzip -l deployed.zip | head -30
What you want to see for a Python function:
lambda_function.py
requests/__init__.py
urllib3/...
What causes the error:
my-func/lambda_function.py <- whole project inside a folder
my-func/requests/__init__.py
This happens when you zip the folder rather than its contents. Build the zip from inside the directory:
cd build && zip -r ../function.zip . && cd ..
aws lambda update-function-code --function-name my-func --zip-file fileb://function.zip
Tip
On Windows,
Compress-Archiveon a folder path also nests the folder. UseCompress-Archive -Path .\build\* -DestinationPath function.zip, or build the zip in your CI on Linux.
File permissions
The runtime reads your files as a non-root user. Zips produced on some Windows tools or with restrictive umasks can leave files unreadable, which shows up as Permission denied or EACCES. Fix before zipping:
find build -type d -exec chmod 755 {} +
find build -type f -exec chmod 644 {} +
4. Dependencies and layers
Packaged with the function
Install dependencies into the package root, next to the handler:
pip install -r requirements.txt --target build/
cp lambda_function.py build/
For Node.js, node_modules/ must sit at the zip root next to index.mjs. Bundlers (esbuild, webpack) avoid this entirely by emitting one file, but then the handler path must point at the bundle.
In a layer
Layers are extracted under /opt. The runtime only searches specific folders, so the folder names inside the layer zip matter:
| Runtime | Layer zip must contain |
|---|---|
| Python | python/ or python/lib/python3.x/site-packages/ |
| Node.js | nodejs/node_modules/ |
Check a layer's content and compatibility:
aws lambda get-layer-version-by-arn \
--arn arn:aws:lambda:eu-west-1:111122223333:layer:deps:7 \
--query '{Runtimes:CompatibleRuntimes,Arch:CompatibleArchitectures,Url:Content.Location}'
Download the Url and run unzip -l on it. A layer zipped as site-packages/requests/... without the python/ prefix is silently ignored.
Two other layer gotchas:
- Layers are applied in order and later layers overwrite files from earlier ones. Two layers carrying different versions of the same library give unpredictable imports.
- Updating a layer creates a new version. Functions keep the old version ARN until you update them.
aws lambda update-function-configuration --function-name my-func \
--layers arn:aws:lambda:eu-west-1:111122223333:layer:deps:8
Warning
--layersreplaces the whole list. Include every layer ARN the function needs, including extensions such as monitoring agents, or they are removed.
5. Native libraries and architecture
Packages with compiled code (numpy, pandas, cryptography, psycopg2, lxml, bcrypt, sharp) must be built for Amazon Linux and for the function's architecture. Wheels installed on a Windows or macOS laptop fail with invalid ELF header, cannot open shared object file or numpy's _multiarray_umath error.
Ask pip for Linux wheels that match the target:
# x86_64 function; use manylinux2014_aarch64 for arm64
pip install -r requirements.txt --target build/ \
--platform manylinux2014_x86_64 --implementation cp \
--python-version 3.12 --only-binary=:all:
If a package has no compatible wheel, build inside a container that matches the runtime, for example the public Lambda base image for your runtime (public.ecr.aws/lambda/python:3.12). Switching a function from x86_64 to arm64 needs a rebuilt package and arm64-compatible layers, even when the code is pure Python on top.
6. Runtime upgrades that break imports
If the error started right after changing the runtime:
- Node.js 18 and later include AWS SDK for JavaScript v3 only.
require('aws-sdk')fails. Move to@aws-sdk/client-*packages or bundle v2 yourself. - Python 3.12 removed
distutilsandimpfrom the standard library. Old versions of some libraries still import them; upgrade the library. - ES modules in Node.js:
importsyntax in a.jsfile without"type": "module"inpackage.jsonfails withCannot use import statement outside a module. Rename to.mjsor set the type. - The SDK bundled in the runtime (boto3, AWS SDK v3) is updated by AWS. If you need a fixed version, package it yourself.
Roll back fast while you fix the package by pointing the alias back at the last good version:
aws lambda update-alias --function-name my-func --name live --function-version 41
Prevent it next time
- Build packages in CI on Linux for the function's architecture, never from a developer laptop.
- Add a smoke test after every deploy:
aws lambda invokeagainst the new version and fail the pipeline if the response hasFunctionErrorset. - Publish versions and shift an alias; keep the previous version so a rollback is one command.
- Pin dependency versions and layer versions in infrastructure code, and set
CompatibleRuntimesandCompatibleArchitectureson every layer you publish. - Alarm on the
Errorsmetric for each function so an import failure pages someone in minutes, not when a customer reports it.