You typed chmod +x deploy.sh, hit enter, and got back chmod: cannot access 'deploy.sh': No such file or directory. The file is right there in your editor. What's actually happening here is that chmod never looks at your editor's buffer — it asks the kernel to resolve the path you gave it, and the kernel says that path doesn't exist. Every cause below is just a different reason the kernel disagrees with your brain about where the file lives.
Cause 1: You're in the wrong working directory (by far the most common)
Open a fresh terminal, chmod a relative path, boom. Your shell's current directory is whatever pwd prints, not where you think you are. If you opened a new tab in tmux, or ssh'd into a box after a cd session, you're back in $HOME.
Check where you actually are:
pwd
ls -la deploy.sh
If ls also says no such file, you're not where you thought. Either cd to the right place or pass the full path. I can't count how many times someone has "fixed" this by copying the file into their home directory, which just leaves a stray duplicate that later runs the wrong version of the script. Don't do that — cd instead.
If you're in a script, that same bug bites harder. Scripts inherit the caller's working directory, not the script's location. So this:
#!/bin/bash
chmod +x helper.sh
fails the moment someone runs the script from /tmp. The robust fix is to anchor on the script's own directory:
#!/bin/bash
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
chmod +x "$SCRIPT_DIR/helper.sh"
The reason that one-liner works is BASH_SOURCE[0] gives you the path the script was invoked with, dirname strips the filename, and the cd && pwd resolves it to an absolute path even when the script was called through a symlink or a relative path.
Cause 2: A broken symlink, or chmod refusing to follow one
Symlinks trip people up in two directions. A symlink pointing at a target that no longer exists will make chmod return No such file or directory — because chmod resolves the link first, and the target isn't there. You can spot it with ls -l:
$ ls -l /usr/local/bin/node
lrwxrwxrwx 1 root root 26 Jan 12 09:14 /usr/local/bin/node -> /opt/node-v20/bin/node
$ ls /opt/node-v20
ls: cannot access '/opt/node-v20': No such file or directory
Someone uninstalled node 20 and left the symlink behind. chmod +x /usr/local/bin/node fails even though ls -l shows the link. The fix is either repoint the link (ln -sfn /opt/node-v22/bin/node /usr/local/bin/node) or delete it.
The opposite case: your file is a symlink, the target exists, and chmod silently changes permissions on the target, not the link (that's POSIX behavior — chmod follows symlinks by default). If you actually wanted to change the link itself, use chmod -h, though on most filesystems link permissions are ignored anyway. And if you're scripting something that should skip broken links, GNU find with ! -xtype l excludes them cleanly:
find . -maxdepth 1 -type f ! -xtype l -exec chmod 644 {} +
Cause 3: Shell expansion ate the filename (spaces, wildcards, quotes)
Filenames with spaces, globs that don't match, and missing quotes all collapse into this error. If there's no file matching *.log in the current directory, bash (without nullglob set) passes the literal string *.log to chmod, which then reports:
chmod: cannot access '*.log': No such file or directory
Notice the quotes around *.log in the error — that's the giveaway. chmod is looking for a file literally named *.log. The files you expected didn't match because you're in the wrong directory (see cause 1) or nothing matches your pattern.
Filenames with spaces are the other classic. This fails:
chmod 644 my report.txt
chmod sees two arguments — my and report.txt — and neither exists. Quote it:
chmod 644 "my report.txt"
If you're looping over filenames, always quote the variable and use -- so a filename starting with a dash doesn't get parsed as a flag:
for f in *.txt; do
chmod 644 -- "$f"
done
Better still, make the loop tolerate zero matches:
shopt -s nullglob
for f in *.txt; do chmod 644 -- "$f"; done
Without nullglob, an empty directory makes the loop run once with the literal string *.txt and chmod fails. With it, the loop body never executes. That one line has saved me more support tickets than any other bash option.
Quick reference
| Symptom | Likely cause | Fix |
|---|---|---|
| Error appears in a new shell/tab | Wrong working directory | pwd, then cd or use absolute path |
| Script works interactively, fails when run elsewhere | Script assumes caller's cwd | Anchor paths with BASH_SOURCE / script dir |
Error quotes the filename literally with a * | Glob didn't match anything | Check directory, or enable shopt -s nullglob |
| File has spaces in its name | Word splitting | Quote the path: chmod 644 "my file" |
ls -l shows the file but chmod can't find it | Broken symlink | Repoint with ln -sfn or delete the link |
Filename starts with - | Parsed as a flag | Use -- before the filename |
| Case mismatch | Linux is case-sensitive | README ≠ readme — check with ls |
One last thing worth internalizing: this error is ENOENT surfacing from the kernel's stat() call. chmod never got as far as changing permissions — it couldn't find the inode. So don't chase permission problems, SELinux, or filesystem mounts. Find the path the kernel can actually see, and chmod will start working.