Fix: Python FileNotFoundError: [Errno 2] No such file or directory
Part of: Python Errors
Quick Answer
How to fix Python FileNotFoundError No such file or directory caused by relative vs absolute paths, wrong working directory, missing files, pathlib usage, and Docker container file access.
The Error
You run a Python script and get:
Traceback (most recent call last):
File "app.py", line 3, in <module>
with open("config.json") as f:
FileNotFoundError: [Errno 2] No such file or directory: 'config.json'Or variations:
FileNotFoundError: [Errno 2] No such file or directory: 'data/input.csv'FileNotFoundError: [Errno 2] No such file or directory: '/home/user/project/output.txt'Python tried to open a file that does not exist at the specified path. The file either doesn’t exist, exists at a different location, or the path is wrong.
Why This Happens
FileNotFoundError is Python’s wrapper around the operating system’s ENOENT (errno 2). The OS opened the path resolution dance: walked each component of the path, asked the filesystem whether it existed, and got back “no” on at least one step. The error message includes only the path you passed in, not the absolute path Python actually attempted, which is why so many bug reports look like “the file is right there but Python cannot find it.”
Three things drive almost every instance of this error. First, relative paths are resolved against the current working directory at the moment of the call, which is rarely the directory containing the script. python app.py from /tmp makes open("config.json") look for /tmp/config.json, not <wherever app.py lives>/config.json. Second, the working directory is set by whoever launched Python, so the same script behaves differently when launched from an IDE, a terminal, a systemd unit, a cron job, or a CI runner. Third, paths cross filesystem boundaries silently — a Docker volume mount, a WSL2 share, or a network drive can change which directory os.getcwd() reports versus which directory actually contains your data.
There is also a quieter category of bugs where the file exists but the user running Python does not have execute permission on a parent directory. The OS reports ENOENT in some of those cases rather than EACCES, because without traverse permission the filesystem cannot say whether the file is there or not. The error wording is misleading, but the fix — fix the directory permissions — is the same.
Common causes:
- Relative path resolved from the wrong directory. You ran the script from a different directory than expected.
- The file genuinely doesn’t exist. A typo in the filename, it was never created, or it was deleted.
- IDE sets a different working directory. VS Code, PyCharm, and other IDEs may set the working directory to the project root, which differs from running in the terminal.
- Path separator issues on Windows. Backslashes in paths need escaping or should use forward slashes.
- File is in a Docker container but the path points to the host filesystem.
Platform and Environment Differences
The same open("data/input.csv") line behaves differently across Linux, macOS, Windows, WSL2, and Docker — not just because of path separators, but because of case sensitivity, the launcher’s working directory, and how each platform resolves “current directory.”
Linux filesystems (ext4, xfs, btrfs) are case-sensitive by default. Config.json and config.json are distinct files, and open("Config.json") fails if only config.json exists. This is the most common cause of “works on my Mac, breaks in Docker” — the developer’s HFS+/APFS folded the case silently and the Linux container did not.
macOS uses APFS, which is case-insensitive but case-preserving by default. open("Config.json") and open("CONFIG.JSON") both succeed and return the same file. This makes macOS forgiving locally but means tests written on Mac frequently fail on Linux CI. The same applies to Windows NTFS, which is also case-insensitive by default (though it can be made case-sensitive per-directory with fsutil.exe file SetCaseSensitiveInfo).
Path separators are forward-slash on Linux and macOS, backslash on Windows. Python accepts both on Windows — open("data/input.csv") works there — but open("data\\input.csv") only works on Windows. The portable approach is pathlib.Path("data") / "input.csv", which serializes to the platform’s native separator. Never hand-build paths with f-strings if you care about cross-platform behavior.
Working directory varies by launcher. Running python app.py from a terminal sets cwd to the terminal’s current directory. VS Code’s “Run Python File” sets cwd to either the workspace root or the file’s directory, depending on python.terminal.executeInFileDir. PyCharm reads “Working directory” from the Run Configuration, defaulting to the project root. A systemd unit sets cwd from WorkingDirectory= (defaults to /). A Linux cron job sets cwd to the user’s home directory. A Windows Task Scheduler job sets cwd to whatever Start in (optional) says, which is empty by default — and an empty value means C:\Windows\System32.
Docker containers have their own filesystem. /home/user/project/data.csv on the host is not visible inside the container unless you bind-mount it. The container’s working directory at process start is the WORKDIR from the Dockerfile, which defaults to /. A script that opens data/input.csv ends up trying /data/input.csv if WORKDIR was never set, and /app/data/input.csv if WORKDIR /app was set. Bind mounts also fail silently if the host path does not exist when the container starts — Docker creates an empty directory in its place rather than erroring.
WSL2 straddles two filesystems. Code under /home/<you>/ lives on the WSL2 ext4 filesystem and behaves like Linux. Code under /mnt/c/Users/<you>/ lives on the Windows NTFS filesystem mounted through 9P, which is case-insensitive and dramatically slower for many small reads. Tooling that watches files (webpack, pytest-watch) sometimes misses changes across the 9P boundary, producing a FileNotFoundError for a file that genuinely just got written. Keep project source on the Linux side for predictable behavior.
pathlib.Path.cwd() vs os.getcwd() return the same value but Path(__file__).parent returns the script’s directory regardless of how it was launched. Building paths from Path(__file__).parent is the single most reliable way to make Python file I/O immune to all of the above.
Fix 1: Use Absolute Paths Relative to the Script
The most reliable fix. Construct paths relative to the script file, not the working directory:
from pathlib import Path
# Get the directory where this script lives
script_dir = Path(__file__).parent
# Build paths relative to the script
config_path = script_dir / "config.json"
data_path = script_dir / "data" / "input.csv"
with open(config_path) as f:
config = json.load(f)Path(__file__).parent always returns the directory containing the current Python file, regardless of where you run it from.
The older os.path equivalent:
import os
script_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(script_dir, "config.json")Pro Tip: Use
pathlib.Pathinstead ofos.pathfor new code. It provides a cleaner API with/operator for path joining, and methods like.exists(),.read_text(), and.mkdir()built in.
Fix 2: Check Your Working Directory
If you use relative paths, verify where Python thinks you are:
import os
print(os.getcwd())This prints the current working directory. If it is different from where your file is, that explains the error.
Fix from the terminal:
cd /path/to/project
python app.pyFix in VS Code:
Add to .vscode/settings.json:
{
"python.terminal.executeInFileDir": true
}Or in launch.json:
{
"cwd": "${fileDirname}"
}Fix in PyCharm:
Go to Run → Edit Configurations → Working directory, and set it to your project root.
Fix 3: Check if the File Exists Before Opening
Add a check to give a clearer error message:
from pathlib import Path
filepath = Path("data/input.csv")
if not filepath.exists():
print(f"File not found: {filepath.absolute()}")
print(f"Current directory: {Path.cwd()}")
print(f"Files in directory: {list(Path.cwd().iterdir())}")
raise SystemExit(1)
with open(filepath) as f:
data = f.read()This prints the absolute path Python is trying to access and lists the files in the current directory, making debugging much easier.
Fix 4: Handle Windows Path Issues
Windows uses backslashes (\) in paths, which are escape characters in Python strings:
# WRONG — \n is a newline, \t is a tab
path = "C:\Users\name\test\new_file.txt"
# RIGHT — use raw strings
path = r"C:\Users\name\test\new_file.txt"
# RIGHT — use forward slashes (works on Windows)
path = "C:/Users/name/test/new_file.txt"
# BEST — use pathlib
path = Path("C:/Users/name/test/new_file.txt")Common Mistake: Using backslashes in Python path strings without the
rprefix.\n,\t,\r, and\bare all escape sequences. The path"C:\new\test\data"becomes"C:<newline>ew<tab>est<backspace>ata". Always use raw strings (r"...") or forward slashes on Windows.
Fix 5: Create Missing Directories
If the error is about the directory not existing (not just the file):
FileNotFoundError: [Errno 2] No such file or directory: 'output/results/data.csv'The output/results/ directory might not exist. Create it first:
from pathlib import Path
output_path = Path("output/results/data.csv")
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, "w") as f:
f.write("data")parents=True creates all intermediate directories. exist_ok=True doesn’t error if the directory already exists.
Fix 6: Fix Paths in Docker Containers
Inside a Docker container, the filesystem is different from your host machine. A path like /home/user/project/data.csv doesn’t exist in the container.
Fix: Mount the data directory as a volume:
docker run -v /host/path/to/data:/app/data myimageThen in your Python code, use the container path:
with open("/app/data/input.csv") as f:
data = f.read()In Docker Compose:
services:
app:
build: .
volumes:
- ./data:/app/dataMake sure the Dockerfile sets the correct working directory:
WORKDIR /app
COPY . .Fix 7: Handle subprocess FileNotFoundError
If the error comes from subprocess:
import subprocess
result = subprocess.run(["my-tool", "--version"], capture_output=True)
# FileNotFoundError: [Errno 2] No such file or directory: 'my-tool'This means the executable my-tool is not found in PATH.
Fix: Use the full path to the executable:
result = subprocess.run(["/usr/local/bin/my-tool", "--version"], capture_output=True)Or check if it exists first:
import shutil
if shutil.which("my-tool") is None:
print("my-tool is not installed or not in PATH")
else:
subprocess.run(["my-tool", "--version"])On Windows, you might need to add .exe:
subprocess.run(["my-tool.exe", "--version"])Fix 8: Use pathlib for Robust Path Handling
pathlib handles cross-platform path differences automatically:
from pathlib import Path
# Works on Linux, macOS, and Windows
base = Path(__file__).parent
config = base / "config" / "settings.json"
data = base / "data" / "input.csv"
# Check existence
if config.is_file():
content = config.read_text()
# List files in a directory
for f in (base / "data").glob("*.csv"):
print(f.name)
# Create directories
(base / "output").mkdir(exist_ok=True)Key pathlib methods:
| Method | What it does |
|---|---|
Path.exists() | Check if path exists |
Path.is_file() | Check if it’s a file |
Path.is_dir() | Check if it’s a directory |
Path.read_text() | Read file contents |
Path.write_text() | Write text to file |
Path.mkdir() | Create directory |
Path.glob() | Find files matching pattern |
Path.resolve() | Get absolute path |
Path.parent | Get parent directory |
For pathlib-specific issues, see Fix: Python pathlib not working.
Still Not Working?
If the file definitely exists but Python still can’t find it:
Check for invisible characters in the filename. Copy-pasting filenames from web pages or documents can introduce zero-width spaces or non-breaking spaces:
import os
for f in os.listdir("."):
print(repr(f)) # Shows hidden charactersCheck for symbolic links. A symlink might point to a target that was moved or deleted:
ls -la filenameCheck file encoding in the filename. Non-ASCII characters (accents, CJK characters) in filenames can cause issues on some systems. Rename the file to use only ASCII characters.
Check for race conditions. In multithreaded code, one thread might delete a file between another thread’s existence check and open call. Use try/except:
try:
with open(filepath) as f:
data = f.read()
except FileNotFoundError:
print(f"{filepath} was deleted or moved")Check for permission issues masquerading as FileNotFoundError. In rare cases, missing execute permission on a parent directory causes FileNotFoundError instead of PermissionError. Check parent directory permissions. For permission-related issues, see Fix: Python PermissionError.
Check the case of every path component on Linux and Docker. A script written on macOS or Windows often references Config.json or Data/Input.csv. The script runs locally because the filesystem folds case. In Docker on a Linux host, the same path returns FileNotFoundError. Run ls -la in the directory and compare each component byte-for-byte against the literal string in your code, including extension capitalization (.JSON vs .json).
Check the launcher’s working directory, not just the terminal’s. If the script runs from systemd, cron, Task Scheduler, or a Docker entrypoint, os.getcwd() is not what you think it is. Insert import os; print("cwd=", os.getcwd(), "argv0=", __file__) at the top of the script and re-run it under the actual launcher. Cron defaults cwd to the home directory; Task Scheduler defaults to C:\Windows\System32; systemd defaults to /. Set the directory explicitly in the unit file (WorkingDirectory=) or use Path(__file__).parent in the code.
Check WSL2 path translation. If the script lives under /mnt/c/... and opens a file written by a Windows-side process, the 9P file watcher may not have synced yet. A quick time.sleep(0.1) between write and read on the same boundary often confirms this. The permanent fix is to keep both writer and reader on the same side of the boundary — either both in WSL2 or both in Windows.
Check for Python’s module not found. If the error is about importing a module (not opening a file), you have a different issue. See Fix: ModuleNotFoundError instead.
If the error occurs specifically with an import statement and you see a traceback mentioning IndentationError, the file might exist but have syntax errors that prevent loading.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: joblib Not Working — Parallel Backends, Memory Cache, and Pickling Errors
How to fix joblib errors — Parallel n_jobs slower than expected, Memory cache miss, backend loky vs threading vs multiprocessing, pickling lambda not supported, dump load file size, and pytest interference.
Fix: Marshmallow Not Working — Schema Errors, Load vs Dump, and Field Validation
How to fix Marshmallow errors — Schema not validated on dump, ValidationError messages format, unknown field handling, missing vs default, post_load object construction, and Marshmallow 3 to 4 migration.
Fix: Pipenv Not Working — Lock File Generation, Shell Activation, and Dependency Resolution
How to fix Pipenv errors — pipenv lock takes forever, Pipfile.lock not generated, shell activation broken, no virtualenv created, dependency conflict, and migration to uv or Poetry.
Fix: Copier Not Working — Template Updates, Question Conditions, and Migrations
How to fix Copier errors — copier.yml not found, conditional questions not appearing, update breaks generated project, migrations between versions, Jinja vs YAML escaping, and answers file conflict.