Python Error Guide: 'FileNotFoundError: [Errno 2] No such file or directory' — Fix Paths
Fix 'FileNotFoundError: [Errno 2] No such file or directory' in Python: fix wrong working directories, relative paths, and subprocess executables not on PATH.
- #python
- #automation
- #troubleshooting
- #errors
Stuck on this Bash & Python Automation error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
FileNotFoundError: [Errno 2] No such file or directory is Python’s wrapper around the OS ENOENT error: a path you referenced does not exist as named. It comes from two very different places, and telling them apart is the whole diagnosis. From open(), it means the file (or a parent directory) isn’t there:
Traceback (most recent call last):
File "report.py", line 9, in <module>
with open("config/settings.yaml") as fh:
FileNotFoundError: [Errno 2] No such file or directory: 'config/settings.yaml'
From subprocess, the same exception means the executable wasn’t found on PATH — not that a data file is missing:
FileNotFoundError: [Errno 2] No such file or directory: 'kubectl'
The quoted name at the end tells you which resource ENOENT applies to.
Symptoms
open()raisesFileNotFoundErrorfor a path that “exists” — but the script was launched from a different working directory.- A relative path works when you run the script from its own folder but fails from cron, systemd, or CI.
subprocess.run([...])raisesFileNotFoundErrorfor the command name even though the command runs fine in your shell.- Writing to a nested path fails because an intermediate directory doesn’t exist yet.
- The path in the error contains an unexpanded
~or$VAR, revealing an un-expanded home or environment reference.
Common Root Causes
- Wrong current working directory — relative paths resolve against
os.getcwd(), which under cron/systemd is usually/or the unit’sWorkingDirectory, not where the script lives. - Relative path assumptions —
open("config/x.yaml")assumes the caller’s CWD containsconfig/. - Missing parent directory — opening
logs/2026/run.logfor write fails iflogs/2026/doesn’t exist (opendoes not create parents). - Unexpanded
~or environment variables — Python does not expand~or$HOMEautomatically; the literal string is used as a path. subprocessexecutable not on PATH — the command isn’t installed, orPATHis minimal under cron/systemd, so the interpreter can’t find the binary.cwd=in subprocess pointing at a nonexistent directory — passing acwdthat doesn’t exist raisesFileNotFoundErrorfor that directory.- Symlink pointing at a deleted target — a dangling symlink resolves to a missing file.
- Typo or wrong extension —
.ymlvs.yaml, or a case-sensitivity mismatch on Linux.
Diagnostic Workflow
First determine which flavor you have — data file vs missing executable — by reading the quoted name. For a data-file case, print what the process actually sees:
import os
print("cwd:", os.getcwd())
target = "config/settings.yaml"
print("abs :", os.path.abspath(target))
print("exists:", os.path.exists(target))
print("dir listing:", os.listdir(os.path.dirname(os.path.abspath(target)) or "."))
Confirm from the shell, and reproduce the cron working directory:
pwd
ls -l config/settings.yaml 2>&1
( cd / && python /opt/app/report.py ) # simulate running from a different CWD
For the subprocess flavor, check whether the executable is resolvable — from Python, not just your shell:
import shutil
print(shutil.which("kubectl")) # None means it is not on PATH for this process
command -v kubectl # your interactive shell
env -i PATH=/usr/bin:/bin python -c "import shutil;print(shutil.which('kubectl'))" # minimal PATH
Reveal unexpanded home/variables in the path:
p = "~/config/settings.yaml"
import os
print(os.path.expanduser(p)) # expands ~
print(os.path.expandvars("$HOME/x.yaml")) # expands $HOME
Example Root Cause Analysis
A reporting script read a template with a relative path and ran fine by hand but failed under cron:
Traceback (most recent call last):
File "/opt/report/render.py", line 14, in <module>
template = open("templates/report.html").read()
FileNotFoundError: [Errno 2] No such file or directory: 'templates/report.html'
The developer ran it as cd /opt/report && python render.py, so templates/ was found relative to /opt/report. Cron ran it as python /opt/report/render.py with a working directory of /, so open("templates/report.html") resolved to /templates/report.html, which didn’t exist. The os.getcwd() check made it obvious:
# by hand: cwd: /opt/report
# via cron: cwd: /
The robust fix anchors every path to the script’s own location instead of the fragile working directory:
from pathlib import Path
BASE = Path(__file__).resolve().parent # directory containing render.py
template_path = BASE / "templates" / "report.html"
if not template_path.is_file():
raise SystemExit(f"Template missing: {template_path}")
template = template_path.read_text()
Path(__file__).resolve().parent makes the script location-independent, so it works identically from any working directory — cron, systemd, CI, or a shell.
Prevention Best Practices
- Anchor paths to the script, not the CWD — use
Path(__file__).resolve().parent(or a configured base directory) so relative paths don’t depend on where the process was launched. - Set
WorkingDirectory=/cwd— for systemd units setWorkingDirectory=, and for croncdinto the right directory first, so relative paths resolve as intended. - Create parents before writing —
Path(p).parent.mkdir(parents=True, exist_ok=True)beforeopen(p, "w"). - Expand
~and variables explicitly —os.path.expanduser()/os.path.expandvars()orPath.expanduser(); Python won’t do it for you. - Preflight and message clearly — check
path.is_file()and raise a descriptive error naming the resolved absolute path, so the fix is obvious. - For subprocess, verify the executable — use
shutil.which("cmd")first, or pass an absolute path, and set an explicitPATHfor cron/systemd jobs. - Prefer
pathlib— it makes path composition explicit and cross-platform, reducing string-concatenation mistakes.
Quick Command Reference
import os, shutil
from pathlib import Path
os.getcwd() # where relative paths resolve
Path(__file__).resolve().parent # script's own directory (anchor here)
Path(p).parent.mkdir(parents=True, exist_ok=True) # create parents before write
os.path.expanduser("~/x") # expand home
shutil.which("kubectl") # is the executable on PATH? (None = no)
Path(p).is_file() # preflight existence check
pwd; ls -l PATH # confirm from the shell
command -v CMD # is the subprocess executable resolvable?
Conclusion
FileNotFoundError: [Errno 2] means a named path doesn’t exist — but which path matters. From open(), it’s a data file or a missing parent directory, and the usual culprit is a relative path resolving against an unexpected working directory under cron or systemd. From subprocess, the identical error means the executable isn’t on PATH. Diagnose by printing os.getcwd() and the resolved absolute path (or shutil.which() for commands), then make paths robust by anchoring them to Path(__file__).resolve().parent, creating parents before writing, and expanding ~/variables explicitly.
Fixed it? Get 500 Bash & Python Automation & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.