Home / AWS

AWSTroubleshootingIntermediate

Lambda Runtime.ImportModuleError and 'Unable to import module': fix packaging, handlers and layers

A Lambda function fails before your code runs with Runtime.ImportModuleError or HandlerNotFound. Check the handler string, zip layout, layer paths, native dependencies and runtime changes.

Deepak Bhardwaj7 min read

Applies toAWS Lambda (zip and layer deployments), Python 3.9-3.13 and Node.js 18.x-22.x managed runtimes, x86_64 and arm64

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-Archive on a folder path also nests the folder. Use Compress-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

--layers replaces 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 distutils and imp from the standard library. Old versions of some libraries still import them; upgrade the library.
  • ES modules in Node.js: import syntax in a .js file without "type": "module" in package.json fails with Cannot use import statement outside a module. Rename to .mjs or 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 invoke against the new version and fail the pipeline if the response has FunctionError set.
  • 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 CompatibleRuntimes and CompatibleArchitectures on every layer you publish.
  • Alarm on the Errors metric for each function so an import failure pages someone in minutes, not when a customer reports it.