Lambda "[Errno 13] Permission denied" and "EACCES: permission denied": Fix the Zip File Permissions (Python, Node.js, Bootstrap, Layers)
"[Errno 13] Permission denied: '/var/task/lambda_function.py'" on AWS Lambda means a file in your deployment package isn't readable by the user Lambda runs your code as. In Node.js the same problem reads EACCES: permission denied, open '/var/task/index.js'. Lambda runs your function as an ordinary, non-root user, so every file in your zip must be readable by everyone (permission 644), and every program or script you run must also be executable (755). A zip built on a machine with strict default permissions carries files marked 600, readable only by their owner, and Lambda can't open them. The fix is one command before zipping, chmod -R a+rX, or a short script that repairs the permissions inside an existing zip. Here's the surprise that sends people the wrong way: in Python this isn't an "Unable to import module" error at all. The file is in the right place with the right name, and Lambda reports a PermissionError. Your code is fine. The zip is carrying a lock you can't see.
Jake runs a phone repair shop. His friend Ethan, a developer, wrote him a Lambda function that resizes the photos customers upload, using a small helper script for the conversion. Ethan's new work laptop came locked down by his employer's IT team, and the first zip he built on it failed on the very first request with "[Errno 13] Permission denied." The same code, zipped on his old laptop, worked. Once he fixed that, the helper script failed with the same error number for a different reason. A week later a colleague's Go version of the function, packaged as a container image, failed to start at all. This page is what Ethan worked out: how Lambda's permissions work in plain English, every version of the error by runtime, how a file ends up unreadable, a two-minute check of any zip, the fixes for Linux, macOS and Windows, executables and helper scripts, custom runtimes, layers and container images, and the "permission denied" errors that are a different problem.
New to Lambda? Our plain-English guide to AWS Lambda explains functions, packages and runtimes in about ten minutes. You can follow this page without it; every step starts from zero.
What "Permission denied" means on Lambda, in plain English
Every file on a Linux system, which is what Lambda runs on, carries three sets of permissions: one for the file's owner, one for its group, and one for everyone else. Each set says whether that audience can read the file, write to it, and execute it as a program. The short form you'll see everywhere is a three-digit number, one digit per audience:
| Number | Shown as | Owner | Everyone else | On Lambda |
|---|---|---|---|---|
| 644 | -rw-r--r-- | Read, write | Read | Right for code and data files |
| 755 | -rwxr-xr-x | Read, write, run | Read, run | Right for folders, scripts, binaries, bootstrap |
| 600 | -rw------- | Read, write | Nothing | Fails: Lambda can't read it |
| 700 | -rwx------ | Read, write, run | Nothing | Fails for files |
| 644 on a script | -rw-r--r-- | Read, write | Read | Fails when you try to run it |
When Lambda unpacks your zip into /var/task, it keeps each file's permissions as they were stored in the zip. Then it runs your function as an ordinary user, not as the owner of those files and not as an administrator. To that user, your files are "everyone else." So the last digit is the one that matters. A 600 file looks perfect on your laptop, where you're the owner, and is completely locked on Lambda.
Lambda's own rule is short: 644 for regular files, 755 for folders and anything executable. Folders get some help: if your package's folders aren't readable, Lambda sets them to 755 for you. Files don't get that help.
Jake: "So the file is in the box, but it's sealed with tape only Ethan's laptop can open."
Ethan: "Exactly like that. Lambda opens the shipping box fine. Then it finds a parcel inside addressed 'for the sender's eyes only,' and it's not allowed to open it."
Every version of the error, by runtime
The cause is always a missing permission bit, but each runtime reports it its own way. These are the messages from Lambda's Python 3.13, Node.js 22 and custom runtime images, running as a non-root user like Lambda does:
| Where | What you see | Missing permission |
|---|---|---|
| Python handler or module | "errorType": "PermissionError", [Errno 13] Permission denied: '/var/task/lambda_function.py' | Read (file is 600 or 700) |
| Python, running a helper | [Errno 13] Permission denied: '/var/task/bin/convert.sh' | Execute (file is 644) |
| Node.js handler or module | "errorType": "Error", EACCES: permission denied, open '/var/task/index.js' | Read |
| Custom runtime or Go | Runtime.InvalidEntrypoint, failed to exec /var/task/bootstrap, log says Permission denied | Execute on bootstrap |
| Container image | IMAGE Launch error: fork/exec ...: permission denied | Execute on the entrypoint |
| Layer | [Errno 13] Permission denied: '/opt/python/shoplayer.py' | Read, inside the layer |
Notice the first row. Python's Lambda runtime turns import failures into Runtime.ImportModuleError, "Unable to import module," but a permission problem isn't an import failure; it's an operating-system error. So it arrives as a plain PermissionError with a Python traceback, and searching for "Unable to import module" fixes won't help. In Node.js the error object also carries "code": "EACCES" and "errno": -13, the same error number 13 that Python prints.
The path in the message is the key clue in every case. It names the exact file that's locked: your handler, a module it imports, a script it runs, or a file in a layer under /opt.
What it looks like in CloudWatch Logs
Because the failure happens while Lambda is loading your code, before your handler ever runs, it shows up in the function's logs during the startup phase. In Python, the runtime first prints a general warning and then the real error:
LAMBDA_WARNING: Unhandled exception. The most likely cause is an issue in the function code. However, in rare cases, a Lambda runtime update can cause unexpected function behavior. ...
[ERROR] PermissionError: [Errno 13] Permission denied: '/var/task/lambda_function.py'
Read past the warning. It's the runtime's general advice for any startup crash and says nothing about permissions; the [ERROR] line underneath is the one that names the file. In Node.js, the equivalent log line starts with ERROR Uncaught Exception and includes "code":"EACCES".
Because the code never finishes loading, every invocation fails, not just some. Each new execution environment tries to load the same package, hits the same locked file and gives up. Retrying, raising the timeout or adding memory changes nothing, and that's a quick way to tell this apart from errors that come and go under load: a permissions problem fails on the first request and on every request after it, until a fixed package is deployed.
Jake: "So it's not flaky. It's broken every single time."
Ethan: "Every single time, which is actually good news. Errors that always happen are the easy ones. Fix the zip once and it's fixed for good."
Find and fix it in five steps
Whatever the runtime, the same five steps get you from the error to a working deploy:
- Read the path in the message. It names the exact file Lambda couldn't open or run, under
/var/taskfor your package or/optfor a layer. - Decide which bit is missing. Code and data files need read for everyone; programs, scripts and
bootstrapalso need execute. Errno 30 instead of 13 means a different problem: see the look-alikes. - Check the zip. Run
zipinfo function.zip, or the checker script below, and look at the last three characters of each file's permissions. - Fix the permissions where the zip is built.
chmod -R a+rXon the package folder,chmod 755on programs, then zip again; or rewrite an existing zip with the fixer script. - Redeploy and invoke once. A test invocation right after
update-function-codeconfirms the fix before real traffic arrives.
How a file ends up unreadable
Nobody sets 600 on purpose. It arrives from the environment where the package is built:
- A strict umask. Every new file's permissions come from a setting called the umask. The common default gives new files
644. Security-hardened laptops, some company-managed machines and some build servers use077instead, which makes every new file600and every new folder700. That includes files fromgit clone,pip installandnpm install, because they all create files through the same setting. - Files copied from somewhere private, such as a key, certificate or config file from your home folder that was deliberately set to
600. - Archives that stored strict permissions, extracted and then zipped again.
- Scripts and binaries without the execute bit, often because they were downloaded or copied rather than installed, or because Git only remembers the execute bit if it was set when the file was committed.
Here's a strict umask at work. These files were created with umask 077:
package:
-rw------- 1 root root 2 Oct 11 13:46 app.py
drwx------ 2 root root 4096 Oct 11 13:46 bin
package/bin:
-rwx------ 1 root root 18 Oct 11 13:46 run.sh
Every one of them works for its owner and fails on Lambda. That was Ethan's new laptop exactly: same code, stricter default.
Jake: "So the IT team's security setting broke my photos."
Ethan: "In a way, yes. Their setting is sensible for a laptop full of private files. It's just wrong for a package that a different user on another computer has to open."
Check any zip in two minutes
You can see the stored permissions without unzipping or deploying anything. On Linux and macOS, zipinfo lists them in the first column:
zipinfo function.zip
Archive: function.zip
Zip file size: 534 bytes, number of entries: 3
-rw------- 2.0 unx 192 b- defN 80-Jan-01 00:00 lambda_function.py
-rw------- 2.0 unx 27 b- defN 80-Jan-01 00:00 shopcalc.py
-rw-r--r-- 2.0 unx 25 b- defN 80-Jan-01 00:00 bin/convert.sh
The first two are 600 and will fail. The script is readable but not executable, so it fails as soon as the code tries to run it.
For a check that works on Windows too, and also knows which files should be executable, save this as zip_permissions.py. Given one zip, it reports; given a second file name, it writes a fixed copy:
"""Check, and optionally fix, the Unix permissions stored inside a Lambda .zip.
python3 zip_permissions.py function.zip # report
python3 zip_permissions.py function.zip fixed.zip # write a copy with 644 files and 755 executables
"""
import stat
import sys
import zipfile
EXECUTABLE_NAMES = ("bootstrap",)
EXECUTABLE_SUFFIXES = (".sh",)
def wants_exec(name, mode):
base = name.rsplit("/", 1)[-1]
return base in EXECUTABLE_NAMES or name.endswith(EXECUTABLE_SUFFIXES) or "/bin/" in "/" + name or bool(mode & 0o111)
src = zipfile.ZipFile(sys.argv[1])
problems = 0
for info in src.infolist():
if info.is_dir():
continue
mode = (info.external_attr >> 16) & 0o777
need = 0o755 if wants_exec(info.filename, mode) else 0o644
ok = mode and (mode & need) == need
if not ok:
problems += 1
print(f"{'ok ' if ok else 'BAD'} {stat.filemode(stat.S_IFREG | mode) if mode else 'no-unix-mode'} {info.filename}")
print(f"{problems} file(s) need fixing")
if len(sys.argv) > 2:
with zipfile.ZipFile(sys.argv[2], "w", zipfile.ZIP_DEFLATED) as out:
for info in src.infolist():
data = src.read(info)
if info.is_dir():
info.external_attr = (stat.S_IFDIR | 0o755) << 16 | 0x10
else:
mode = (info.external_attr >> 16) & 0o777
info.external_attr = (stat.S_IFREG | (0o755 if wants_exec(info.filename, mode) else 0o644)) << 16
out.writestr(info, data)
print(f"wrote {sys.argv[2]}")
On the zip above, it reports three problems:
BAD -rw------- lambda_function.py
BAD -rw------- shopcalc.py
BAD -rw-r--r-- bin/convert.sh
3 file(s) need fixing
It treats a file as needing 755 if it's named bootstrap, ends in .sh, sits in a bin folder, or already has an execute bit for anyone. If your helper binaries live elsewhere or have other names, add them to EXECUTABLE_NAMES or EXECUTABLE_SUFFIXES. A file reported as no-unix-mode came from a tool that didn't record Unix permissions at all, which is common for zips made on Windows; the fixer gives those files explicit modes too.
The fix: set 644 and 755 before you zip
On Linux or macOS, one command gives every file read access for everyone and every folder, plus anything already executable, execute access too:
chmod -R a+rX package
The capital X matters. It adds execute only to folders and to files that are already executable for someone, so .py and .js files become 644 while scripts stay runnable. After running it on the folder above:
package:
-rw-r--r-- 1 root root 2 Oct 11 13:46 app.py
drwxr-xr-x 2 root root 4096 Oct 11 13:46 bin
package/bin:
-rwxr-xr-x 1 root root 18 Oct 11 13:46 run.sh
Exactly Lambda's rule: 644 for the code, 755 for the folder and the script. Then zip as usual from inside the folder, and the permissions travel with the files:
cd package
zip -r ../function.zip .
If a script or binary wasn't executable to begin with, give it the bit explicitly before zipping: chmod 755 package/bin/*.
Fix a zip you already have
If you only have the zip, from a build server, a colleague or a Windows tool, the script from the previous section writes a corrected copy:
python3 zip_permissions.py function.zip fixed.zip
Checking the new file shows every entry fixed:
ok -rw-r--r-- lambda_function.py
ok -rw-r--r-- shopcalc.py
ok -rwxr-xr-x bin/convert.sh
0 file(s) need fixing
The original zip, deployed, failed with [Errno 13] Permission denied: '/var/task/lambda_function.py'. The fixed copy ran and returned its result on the first try. Deploy it the usual way:
aws lambda update-function-code --function-name photo-resize --zip-file fileb://fixed.zip
Make it permanent in your build
Fixing one zip by hand fixes one deploy. To stop it coming back, put the chmod -R a+rX step into whatever builds your package: the build script, the CI job, or the Makefile, right before the zip command. If the build runs on a machine with a strict umask, you can also set a normal one for the build step only, with umask 022 at the top of the script, so every file the build creates starts out as 644.
Jake: "One command. That's it?"
Ethan: "One command, in the right place. Put it in the build script and nobody on any laptop can break it again."
Helper programs and scripts: the execute bit
Functions that call other programs, such as an image converter, a PDF tool or a shell script, need those programs to be executable, not just readable. Ethan's photo function ran a small script, and with the script stored as 644 it failed like this:
{"errorMessage": "[Errno 13] Permission denied: '/var/task/bin/convert.sh'", "errorType": "PermissionError"}
Same error number, different missing bit. With the script stored as 755, the same function returned {"out": "converted\n"}. The fix is chmod 755 on every program before zipping, or the zip fixer above, which treats anything in bin/ as executable.
If you can't rebuild the package right now, there's a runtime workaround. /var/task is read-only, so you can't change permissions there, but /tmp is writable. Copy the program there once and make it executable:
import os, shutil, subprocess
TOOL = "/tmp/convert.sh"
def handler(event, context):
if not os.path.exists(TOOL):
shutil.copy("/var/task/bin/convert.sh", TOOL)
os.chmod(TOOL, 0o755)
out = subprocess.run([TOOL], capture_output=True, text=True).stdout
return {"out": out}
That ran with the script still stored as 644 in the zip. The copy happens once per execution environment, on the first request after a cold start, because /tmp survives between requests to the same environment. Treat it as a stopgap; fixing the package is cleaner.
Node.js: "spawn EACCES"
Node.js reports a non-executable helper differently from Python. Starting a 644 script with child_process.execFile gives:
EACCES | spawn /tmp/run.sh EACCES
The spawn in the message tells you Node was trying to run the file, not read it, so the missing bit is execute. The fixes are the same: chmod 755 before zipping, or copy the program to /tmp and fs.chmodSync(path, 0o755) it there before the first run.
Custom runtimes and Go: bootstrap must be executable
On the provided.al2023 runtime, which Go, Rust and custom runtimes use, Lambda starts your function by running a program called bootstrap. If that file isn't executable, nothing starts:
{"errorMessage": "failed to exec /var/task/bootstrap", "errorType": "Runtime.InvalidEntrypoint"}
The function's log shows the reason: /var/task/bootstrap: Permission denied. The same bootstrap stored as 755 started and answered normally.
On Linux and macOS, go build produces an executable file, so zipping it directly keeps the bit. Windows is the trap: Windows has no Unix execute bit, so a bootstrap zipped with Windows tools often arrives without one. The aws-lambda-go project includes a small tool for exactly this case, build-lambda-zip, which stores files in the zip with full read and execute permissions:
go install github.com/aws/aws-lambda-go/cmd/build-lambda-zip@latest
build-lambda-zip -o bootstrap.zip bootstrap
Run the second command from the folder that contains your compiled bootstrap, then deploy bootstrap.zip. The Python fixer above works as well; it marks any file named bootstrap as 755.
Container images: COPY the right permissions
Lambda also runs container images as a non-root user, so the same rule applies inside the image. A program copied in without the execute bit fails before your function starts. Developers who hit this report the error as:
IMAGE Launch error: fork/exec /usr/local/bin/docker-php-entrypoint: permission denied
Set the permissions in the Dockerfile itself, so it doesn't depend on the machine that builds the image. COPY --chmod does it in one line:
FROM public.ecr.aws/lambda/provided:al2023
COPY --chmod=755 bootstrap ${LAMBDA_TASK_ROOT}/bootstrap
CMD ["handler"]
Built with --chmod=644 instead, the same image failed with Runtime.InvalidEntrypoint when run as a non-root user; with --chmod=755 it answered normally. If your build tool doesn't support COPY --chmod, add RUN chmod 755 ${LAMBDA_TASK_ROOT}/bootstrap after the COPY. For code files copied into an image, RUN chmod -R a+rX ${LAMBDA_TASK_ROOT} covers everything at once.
Layers: the same rule under /opt
A layer is unpacked into /opt the same way your function is unpacked into /var/task, permissions included. A layer built on a strict-umask machine fails with the layer's path in the message:
[Errno 13] Permission denied: '/opt/python/shoplayer.py'
Run the same check and fix on the layer's zip before publishing it: python3 zip_permissions.py layer.zip fixed-layer.zip, then publish the fixed file as a new layer version. Functions keep using the layer version they were configured with, so after publishing, point each function at the new version. Our guide to "Unable to import module" shows the folder layout layers need, which is the other half of getting a layer right.
The fix for each build environment
The right fix depends on where the package is built. Here's the short version for each:
| Where you build | Fix | Notes |
|---|---|---|
| Linux or macOS | chmod -R a+rX package, then zip | Add umask 022 to build scripts on strict-umask machines |
| Windows | Build and zip in WSL or a Linux container, or run the fixer on the finished zip | Windows zip tools often store no Unix permissions at all |
| Go on Windows | build-lambda-zip -o bootstrap.zip bootstrap | Keeps bootstrap executable |
| CI pipeline | umask 022 at the start of the job, or a chmod step before zipping | Fresh runners can have a stricter default than your laptop |
| Container image | COPY --chmod=755 for programs, RUN chmod -R a+rX for code | Lambda runs images as a non-root user |
| Layer | Fix the layer zip, publish a new version, update functions | Functions keep using the old version until you switch |
Catch it at startup with a five-line self-check
If packages come from several people or tools, a small check at the top of your handler file can name every problem file in the function's logs the first time it starts, before any request fails in a confusing place:
import os
def unreadable_files(root="/var/task"):
"""List package files the function itself cannot read or run."""
problems = []
for folder, _, files in os.walk(root):
for name in files:
path = os.path.join(folder, name)
if not os.access(path, os.R_OK):
problems.append(path)
elif "/bin/" in path and not os.access(path, os.X_OK):
problems.append(path + " (not executable)")
return problems
PROBLEMS = unreadable_files()
if PROBLEMS:
print("Permission problems in the package:", PROBLEMS)
On a package with a 600 config file and a non-executable script, it reported both at once:
["/var/task/config.json", "/var/task/bin/convert.sh (not executable)"]
It runs once per cold start, because it sits outside the handler. The handler file itself has to be readable for the check to run at all, so this catches problems in everything else: config files, data files, modules imported later, and helper programs. Remove it once your build sets permissions reliably.
"But 600 is safer": secrets and permissions on Lambda
People often set a file to 600 on purpose, because it holds something sensitive: an API key, a service-account file, a certificate. On a shared laptop that's good practice. Inside Lambda it protects nothing and breaks the function, because the only user who could ever read the file is your function itself. Lambda execution environments are never shared across functions, and the microVMs they run in are never shared across AWS accounts. A 644 file in your package is readable by your function and nobody else on that machine.
The real risk with secrets in a zip is elsewhere. Anyone allowed to call GetFunction on your function receives a download link for the whole deployment package, and the zip itself tends to end up in build artifacts and repositories. Keep secrets out of the package entirely: store them in AWS Secrets Manager or Systems Manager Parameter Store and read them at startup. Our guide to AWS Secrets Manager explains how, and its companion on Secrets Manager versus Parameter Store covers which costs less.
Jake: "So locking the file was the right instinct in the wrong place."
Ethan: "Right instinct, wrong place. On Lambda the safe move is to not ship the secret at all. Fetch it when the function starts, and nobody who downloads the code ever sees it."
"Permission denied" errors that are a different problem
Several errors look like this one and need completely different fixes:
| Message | What it really is | Fix |
|---|---|---|
[Errno 30] Read-only file system: '/var/task/out.txt' | Your code tried to write into the package folder | Write to /tmp instead |
EACCES: permission denied, scandir '/mnt/...' | An EFS file system's own file permissions | Fix the EFS access point's user and folder permissions |
AccessDenied or AccessDeniedException | An AWS permission, from IAM | Fix the function's execution role policy |
invalid ELF header | A binary built for another operating system | Rebuild the dependency for Linux |
The first one is the most common mix-up. Errno 13 means "you can't open this file"; Errno 30 means "nobody can write here." Lambda's package folder is read-only for everyone, by design, and only /tmp is writable. Our guide to Lambda's read-only file system covers that one, and our guide to "invalid ELF header" covers binaries built on the wrong platform. For EFS, the access point's user and folder settings decide what the function can open; our EFS mount guide covers the mount side.
Errno 13 in Python outside Lambda, on Linux and macOS
"[Errno 13] Permission denied" isn't special to Lambda. It's the operating system's standard answer whenever a program tries something its user isn't allowed to do, and Python reports it the same way everywhere. Three versions come up constantly on Linux and macOS machines and in containers.
Writing into a system folder. As an ordinary user, the system Python's own folders are read-only to you. A program that writes there, or pip install --target pointed at one, fails:
PermissionError: [Errno 13] Permission denied: '/var/lang/lib/python3.13/site-packages/x.txt'
Recent versions of pip notice this on a plain pip install and quietly install into your user folder instead, under ~/.local, so you often won't see the error from pip at all. The clean fix is a virtual environment, which belongs to you entirely:
python3 -m venv .venv
.venv/bin/pip install tabulate
Running sudo pip install makes the error go away too, but it mixes your packages into the system's, which is how system tools get broken. Use a virtual environment instead.
Running a script that isn't executable. The shell and Python say it differently:
sh: line 1: /tmp/run.sh: Permission denied
PermissionError: [Errno 13] Permission denied: '/tmp/run.sh'
The shell's version also exits with code 126, which in shell scripts always means "found it, but couldn't run it." chmod +x /tmp/run.sh fixes both.
Reading a file that belongs to another user, such as a key or log file set to 600 by someone else: you need that user's access, a group that can read it, or a copy you're allowed to read. That's the same situation Lambda is in with a 600 file in your zip.
When it happens on your own machine, these steps find it fast:
- Find the path in the message and run
ls -lon it, andls -ldon its folder. - Check who you are with
id, and compare with the owner and group in the listing. - Pick the smallest fix:
chmod +xfor a script, a virtual environment for Python packages, a path in your home folder for output files. - Reach for sudo last, and never for
pip installinto the system Python.
One look-alike: on Linux and macOS, trying to open() a folder as if it were a file gives a different error, IsADirectoryError: [Errno 21] Is a directory, not Errno 13. If you see 21, check that your path ends in a file name.
SAM, Serverless Framework, CDK and CI pipelines
Deployment tools build the zip for you, so they inherit the permissions of the files they find. They don't usually change permissions on their own, which means the fix still belongs before the tool runs:
- In CI, add
chmod -R a+rXon the source and dependency folders as a step before the package or deploy command, or setumask 022at the start of the job. - Locally on a strict-umask laptop, check
umaskin your terminal. If it prints0077, either run the build in a shell withumask 022or keep thechmodstep in your build script. - For any tool, check the zip it produced with
zipinfoor the checker script before the first deploy. It takes seconds, and it tells you whether the tool kept your permissions.
Container-based builds, like sam build --use-container or a Docker build stage, sidestep the laptop's umask because the files are created inside the container. For images, the Dockerfile's COPY --chmod or RUN chmod line is the permanent fix.
Still failing? Troubleshooting by symptom
It works locally and fails on Lambda
Locally you're the file's owner; on Lambda you're "everyone else." Check the zip with zipinfo, and look at the last digit of each file's permissions.
The error names a file I didn't write
A dependency was installed with strict permissions. chmod -R a+rX on the whole package folder, including dependencies, fixes all of them at once.
The handler works but a script it runs fails
The script is readable but not executable. chmod 755 it before zipping, or copy it to /tmp and os.chmod it at runtime.
It started failing after I changed laptops or CI runners
The new machine has a stricter umask. Run umask there; 0077 explains it.
Only the container image version fails
Check the entrypoint and bootstrap permissions inside the image. Use COPY --chmod=755 or a RUN chmod line.
It's Errno 30, not 13
That's the read-only package folder. Write to /tmp instead.
Lambda Errno 13 permission denied: frequently asked questions
What does [Errno 13] Permission denied mean in AWS Lambda?
A file in your deployment package isn't readable, or a program isn't executable, by the non-root user Lambda runs your code as. The path in the message names the file. Set files to 644 and folders and executables to 755 before zipping.
How do I fix Errno 13 permission denied in Lambda?
Run chmod -R a+rX on the package folder before creating the zip, and chmod 755 any scripts or binaries. For an existing zip, rewrite it with corrected permissions using a small Python script, then deploy the fixed zip.
What permissions do Lambda deployment files need?
644 (rw-r--r--) for regular files and 755 (rwxr-xr-x) for folders and anything executable, such as scripts, binaries and bootstrap. Lambda fixes unreadable folders to 755 itself, but not files.
Why does Lambda say EACCES permission denied for index.js?
index.js is stored in the zip without read permission for other users, often 600 from a strict umask. Lambda's Node.js runtime can't open it. Run chmod -R a+rX before zipping.
Why does Lambda run my code as a non-root user?
Lambda runs functions as an ordinary user with limited rights, for safety. To that user, your files belong to someone else, so they need read permission for everyone.
Is Errno 13 the same as Unable to import module?
No. Unable to import module means Python couldn't find or load the module. Errno 13 means it found the file and wasn't allowed to read it; in Python it arrives as a PermissionError, not Runtime.ImportModuleError.
What is the difference between Errno 13 and Errno 30 on Lambda?
Errno 13 means a file can't be opened because of its permissions. Errno 30 means the code tried to write to a read-only location, usually /var/task. Fix 13 with chmod before zipping; fix 30 by writing to /tmp.
How do I check file permissions inside a zip?
On Linux and macOS, run zipinfo function.zip and read the first column. On any system, a short Python script that reads each entry's external_attr shows and can fix the stored permissions.
Why does my bootstrap fail with permission denied?
bootstrap must be executable (755). If it isn't, Lambda reports Runtime.InvalidEntrypoint, failed to exec /var/task/bootstrap. Zipping on Windows often loses the execute bit; build-lambda-zip from aws-lambda-go keeps it.
How do I zip a Go Lambda function on Windows?
Install build-lambda-zip with go install github.com/aws/aws-lambda-go/cmd/build-lambda-zip@latest, then run build-lambda-zip -o bootstrap.zip bootstrap. It stores the file with execute permission, which Windows zip tools usually don't.
Can I chmod files inside /var/task at runtime?
No. /var/task is read-only. Copy the file to /tmp, chmod it there, and run or read the copy. It's a workaround; fixing permissions before zipping is cleaner.
How do I fix permission denied in a Lambda container image?
Set permissions in the Dockerfile with COPY --chmod=755 for executables, or add RUN chmod -R a+rX on the code folder. Lambda runs images as a non-root user, so files must be readable and entrypoints executable by others.
Do Lambda layers need the same permissions?
Yes. Layers are unpacked into /opt with their stored permissions, and a 600 file fails with its /opt path in the message. Fix the layer zip, publish a new version, and update each function to use it.
What is umask and why does it matter for Lambda?
umask decides the permissions of new files. The usual 022 gives files 644; a strict 077 gives 600, which Lambda can't read. Check it with the umask command and use 022 for build steps.
Why did my Lambda deploy break after changing laptops?
The new machine probably has a stricter umask, common on company-managed laptops, so every file you create or install is 600. Add chmod -R a+rX to your build before zipping.
What does Errno 13 Permission denied mean in Python?
The operating system refused an action your user isn't allowed to do: reading a file without read permission, writing into a protected folder, or running a script without the execute bit. The path in the message is the file it refused.
How do I fix Errno 13 when installing Python packages?
Use a virtual environment: python3 -m venv .venv, then .venv/bin/pip install the package. Recent pip versions fall back to a user install automatically; the error usually means --target points at a protected folder.
What does spawn EACCES mean in Node.js?
Node tried to run a file that isn't executable. Set the execute bit with chmod 755 before packaging, or on Lambda copy the program to /tmp and chmod it there before running it.
Is 644 permission a security risk on Lambda?
No. Lambda execution environments are never shared across functions, so a 644 file is readable only by your own function there. Keep real secrets out of the zip anyway, in Secrets Manager or Parameter Store, because anyone who can download the package can read it.
How do I set exactly 644 on files and 755 on folders?
Run find package -type f -exec chmod 644 {} + for files and find package -type d -exec chmod 755 {} + for folders, then chmod 755 on your scripts and binaries. chmod -R a+rX does almost the same in one command and keeps executables runnable.
Can I fix the permissions in a zip without unzipping it?
Not with zip itself, but a short Python script can rewrite every entry's stored permissions into a new zip without extracting anything to disk: 644 for files, 755 for scripts, binaries and bootstrap. The zip_permissions.py script on this page does exactly that.
Does Lambda fix file permissions automatically?
Only for folders: Lambda sets folders it can't access to 755. Files keep the permissions stored in the zip, so unreadable files stay unreadable.
What does EACCES permission denied scandir /mnt mean in Lambda?
That's EFS, not your package. The EFS access point's user ID and folder permissions don't allow the function to read the folder. Fix the access point's POSIX user and root directory permissions.
For readers preparing for an AWS certification
We have been covering AWS certifications alongside our fixes, so here is a short extra for readers studying for one. If you only came for the fix, you already have it; feel free to skip this part.
Exams that test this topic: Developer – Associate (DVA-C03 from December 1, 2026).
Here are the exam-style questions this topic tends to raise, with the reasoning you need to answer them:
1. Why does a Lambda function get "Errno 13 Permission denied"?
It tries to write outside /tmp, or a bundled binary lacks execute permission.
2. How do you give a bundled binary execute permission?
Set the file mode to 755 before zipping; zips built on Windows often lose permissions.
3. Which user does Lambda code run as?
A restricted non-root user, so system paths are read-only.
4. How do you run a binary from a layer?
Place it under /opt/bin with execute permission; that path is on PATH.
5. Which deployment type preserves file permissions reliably?
Container images, built from a Dockerfile.
6. Can a function change file permissions at runtime?
Only for files it creates in /tmp.
Exam codes and dates change often. Our AWS certification changes page keeps track of what is current.
Ethan's build script now starts with umask 022 and runs chmod -R a+rX right before it zips, so the locked-down laptop builds the same package his old one did. The helper script lives in bin/ with its execute bit set, and his colleague's container image copies its bootstrap with --chmod=755. Jake's photos resize on the first try again. Ethan added a last line to the project's checklist, under "before you deploy," and read it out to Jake as a rule for anything that has to work on someone else's computer.
📌 If you keep one line from this page
Lambda opens your files as a stranger: before you zip, run chmod -R a+rX so every file is 644 and every folder and program is 755.
Errno 13 is a lock on the file; Errno 30 is a read-only folder. Check any zip with zipinfo before it ships.
Revision note. Written October 11, 2026, for every function that worked on the laptop and nowhere else. "A key that only fits your own pocket opens nothing for anyone else," as the old saying goes; give Lambda a key it can use.