No such file or directory when the file is clearly there
$ ./app
bash: ./app: No such file or directory
$ ls -l ./app
-rwxr-xr-x 1 deploy deploy 1843200 Oct 25 12:04 ./app
$ file ./app
./app: ELF 64-bit LSB pie executable, x86-64, ..., dynamically linked,
interpreter /lib64/ld-linux-x86-64.so.2
Every instinct says the file is there. And it is. What is missing is the interpreter named in
PT_INTERP — and the kernel, having failed to find it, reports ENOENT against the path you
executed rather than against the path it could not open.
Why the message is wrong on purpose
execve(2) has one error code for “the thing I needed was not there”: ENOENT. Once the kernel has
handed control to the binary format handler and that handler asks the kernel to also open the
interpreter, a failure in that second open still returns ENOENT for the whole execve call. The
shell prints that against argv[0] because that is the only path it knows.
So the rule is: No such file or directory about a binary that exists = look at PT_INTERP.
Confirming it in one command
$ readelf -l ./app | grep interpreter
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
$ ls -l /lib64/ld-linux-x86-64.so.2
ls: cannot access '/lib64/ld-linux-x86-64.so.2': No such file or directory
There is the real missing file. On this host, ldd --version will show musl, or the system may be
a different architecture rootfs entirely.
Three common cases:
PT_INTERP says | Host is | Cause |
|---|---|---|
/lib64/ld-linux-x86-64.so.2 | Alpine (musl) | glibc binary in a musl image |
/lib/ld-musl-x86_64.so.1 | Ubuntu | musl binary on glibc |
/lib/ld-linux-aarch64.so.1 | x86-64 | wrong architecture — but then Exec format error usually wins instead |
The third row is worth knowing: if the architecture is wrong and the interpreter is wrong, which error you get depends on how far the kernel got. Both messages can appear for the same root cause.
The other causes of this exact message
Not all of them are interpreters. In order of frequency:
1. A shebang pointing at a path that does not exist. The message then names the interpreter
explicitly (/usr/bin/env/python3: bad interpreter), so it is usually easier.
2. A script with CRLF line endings. The interpreter becomes bash\r. bash ./script works,
./script does not. See Exec format error for the detail.
3. A bind mount or volume that hides the file. Inside a container, ls may show the host’s
path via a mount while the process sees an empty mount. stat from inside the container is the test.
4. A dynamically linked binary whose dynamic loader is present but whose ld.so.cache entry is
broken — rarer, and it normally produces a library error instead.
Fixing it properly
| Situation | Correct fix |
|---|---|
| glibc binary in Alpine | rebuild against musl, or use gcompat as a stopgap |
| musl binary on Ubuntu | build against glibc, or ship the musl loader |
| Wrong architecture | get the build for the right platform |
| You control the build | -static with musl removes the interpreter entirely |
Do not copy ld-linux-x86-64.so.2 from another distribution into the host. You will get a binary
that starts and then resolves glibc symbols against musl’s libc, failing at a random later point with
a message that has nothing to do with the original problem.
If dropping the loader into a directory is genuinely the right call — vendor distribution, no rebuild
possible — invoke it explicitly so PT_INTERP is bypassed:
./bundle/ld-linux-x86-64.so.2 --library-path ./bundle/lib ./app
That is self-contained and reversible, which copying into /lib64 is not.
Checking without a shell
The ELF Inspector shows the interpreter path and the detected libc family
(glibc, musl, or static) in the summary tab. If the interpreter is missing on the target, the
Dependency Doctor will show the complete DT_NEEDED list too — useful
for confirming that fixing the interpreter will not immediately expose the next problem.
Related: musl vs glibc explains the two families, and static vs dynamic linking covers why static builds have no interpreter to miss.
Every command in this guide was run against the distribution named in the output. If your output differs, the mechanism section explains why — and the ELF Inspector shows the same fields without needing a shell.