cannot execute binary file: Exec format error — causes and fixes
You try to run something and the shell refuses:
$ ./agent
bash: ./agent: cannot execute binary file: Exec format error
The instinct is to assume the file is corrupt. It almost never is. Exec format error
(ENOEXEC) means the kernel looked at the first bytes, could not find a format handler that
accepted them, and gave up. The file is intact — it is simply not something this kernel can run.
The quick diagnosis
Three commands settle it in about ten seconds:
$ file ./agent
./agent: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked,
interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, stripped
$ uname -m
x86_64
$ readelf -h ./agent | grep -E 'Class|Machine'
Class: ELF64
Machine: AArch64
file says aarch64, uname -m says x86_64. There is your answer: an ARM build on an Intel host.
No amount of chmod +x will change it.
What the kernel actually does
execve(2) never looks at the file extension. It reads the first 128 bytes and walks a list of
binfmt handlers, each of which gets a chance to claim the file. The ELF handler claims it when
bytes 0–3 are 0x7f 'E' 'L' 'F', then validates two more fields before anything else happens:
| Field | Meaning | Rejection |
|---|---|---|
EI_CLASS | 1 = 32-bit, 2 = 64-bit | value outside 1–2 is rejected |
EI_DATA | 1 = little-endian, 2 = big-endian | invalid value is rejected |
e_machine | target instruction set | must match a machine the kernel was built for |
If any of these fail, execve returns ENOEXEC and the dynamic loader is never invoked. This
single fact rules out an entire class of theories: when you see Exec format error, the problem is
never a missing library, a bad LD_LIBRARY_PATH, or a permissions issue. Those all produce
different messages, because they happen after the kernel has accepted the file.
A useful side effect of ENOEXEC: POSIX shells respond to it by re-executing the file through the
shell itself. That is why a text file with no shebang sometimes produces shell syntax errors instead
of an exec error — and why bash ./script.sh can “work” where ./script.sh does not.
The five real causes, in order of frequency
1. Architecture mismatch
By far the most common, and now the most common of the common: an arm64 image or binary on an
amd64 host, or the reverse. Apple Silicon made this routine — a laptop builds arm64, the CI
runner or the server is x86_64.
For containers the check is:
$ docker image inspect myapp:latest --format '{{.Architecture}}'
arm64
$ uname -m
x86_64
Fix it by asking for the right platform explicitly, rather than relying on whatever the builder defaulted to:
docker pull --platform linux/amd64 myapp:latest
docker buildx build --platform linux/amd64 -t myapp:amd64 .
Emulation (qemu-user-static registered through binfmt_misc) makes the wrong binary run, which
is worse than failing: it works, slowly and subtly differently, and nobody notices until production.
Docker Desktop enables this transparently, which is exactly how it ends up in production.
2. A shell script with Windows line endings or a BOM
$ ./deploy.sh
bash: ./deploy.sh: cannot execute binary file: Exec format error
$ file deploy.sh
deploy.sh: Bourne-Again shell script, ASCII text executable, with CRLF line terminators
The shebang line becomes #!/bin/bash\r, the kernel looks for an interpreter literally named
bash\r, does not find it, and returns ENOEXEC. A UTF-8 BOM (EF BB BF) in front of #! breaks
it the same way — the first two bytes are no longer #!.
dos2unix deploy.sh # or: sed -i 's/\r$//' deploy.sh
sed -i '1s/^\xEF\xBB\xBF//' deploy.sh # strip BOM
3. A truncated or replaced download
An HTML error page saved under the name agent, or a tarball that was actually a 404 body, fails
at the very first byte check — sometimes as Exec format error, sometimes as Syntax error when the
shell retries. Verify size and checksum before blaming the platform:
$ head -c 40 ./agent | od -c | head -2
0000000 < ! D O C T Y P E h t m l >
That is an HTML page, not an ELF header. 0x7f E L F is what you want to see.
4. A 32-bit binary on a host without multilib
An i386 executable on a 64-bit kernel needs CONFIG_IA32_EMULATION and a 32-bit loader
(/lib/ld-linux.so.2) plus 32-bit libc. Without them the kernel can still refuse at exec time:
sudo apt install libc6-i386 # Debian/Ubuntu
sudo dnf install glibc.i686 # RHEL family
If readelf -h reports ELF32 and Machine: Intel 80386 while the target is a minimal container
image, installing multilib is usually the wrong fix; rebuilding for the target is the right one.
5. Shebang pointing at a missing interpreter
$ ./tool
bash: ./tool: /usr/bin/env/python3: bad interpreter: No such file or directory
The message names the interpreter, not the format, but the same ENOEXEC path is involved. Note the
difference from cannot execute binary file — the kernel did recognise the script, and failed to
find what it points at.
When it is none of the above
Two rarer cases are worth knowing:
- Filesystem or mount problem. A binary on a
noexecmount producesPermission denied, notExec format error, so this is usually easy to rule out. A binary served from an NFS mount with the wrongarchoption is a different story. - Kernel architecture mismatch (not userland). A 32-bit kernel with a 64-bit userland cannot
run 64-bit binaries at all.
uname -mshows the kernel’s view; checkgetconf LONG_BITfor the userland width. If they disagree, nothing you install will help.
A note on the tools
file reads the ELF header and reports architecture, class and endianness in one line. readelf -h
gives you the same fields individually, which is what you want when scripting a check. If you cannot
install either — a locked-down jump host, a container with no binutils, a binary you would rather
not copy onto a server — the ELF Inspector reads the same structures in a
browser tab and reports e_machine, class and endianness without any upload.
Related: wrong ELF class covers the 32/64 mismatch that only shows up at link time, and GLIBC version not found covers the failure that happens immediately after this one is solved.
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.