DEV Community

Weifeng Liu
Weifeng Liu

Posted on Originally published at readelf.com

ldd says 'not a dynamic executable' — your static binary is fine

Short answer

ldd reads the dynamic section of an ELF file. A statically linked binary has none,
so ldd reports not a dynamic executable and exits 1. The binary is not broken.
It simply does not need a loader at runtime.

This is the most common false alarm in Linux binary troubleshooting: people read it as
"the executable is invalid" and start looking for a missing library that does not exist.

Confirm it in three commands

Take any static binary — a Go or Rust build, or busybox from a static image — and run:

$ ldd ./hello-static
        not a dynamic executable
$ echo $?
1
Enter fullscreen mode Exit fullscreen mode
$ file ./hello-static
./hello-static: ELF 64-bit LSB executable, x86-64, version 1 (SYSV),
statically linked, Go BuildID=rLXBd7VvG-TozsbigJ3a/..., stripped
Enter fullscreen mode Exit fullscreen mode
$ readelf -d ./hello-static
There is no dynamic section in this file.
Enter fullscreen mode Exit fullscreen mode

file says statically linked and readelf -d says no dynamic section. Both agree
with ldd. Nothing is missing.

What the message actually means

Tool Says Because
ldd not a dynamic executable no DT_NEEDED, no PT_INTERP
file statically linked same reason, different wording
readelf -d There is no dynamic section in this file. the .dynamic section is absent
`readelf -l \ grep -c INTERP` 0

Contrast with a dynamically linked binary on the same host:

$ readelf -l ./hello-new | grep -A1 INTERP
  INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
$ readelf -d ./hello-new | grep NEEDED
 0x0000000000000001 (NEEDED)   Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)   Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)   Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)   Shared library: [libc.so.6]
Enter fullscreen mode Exit fullscreen mode

An INTERP entry plus a list of NEEDED libraries is what ldd needs to do its job.
A static binary has neither.

Where this shows up in practice

Go and Rust builds. CGO_ENABLED=0 Go builds and musl-targeted Rust builds are static
by default. Running ldd on them is a category error, not a defect.

Distroless and scratch images. There is no shell, no binutils, sometimes no ldd.

Static busybox. busybox --install links applets into a single static binary; every
applet reports this message.

Jump hosts without binutils. A slim debian:12 image has neither file nor readelf:

$ file ./hello-static
sh: 1: file: not found
$ readelf -h ./hello-static
sh: 1: readelf: not found
Enter fullscreen mode Exit fullscreen mode

That is exactly the situation the ELF Inspector exists for — it
parses in your browser, so the target host needs no tooling and the binary is not uploaded.

When not a dynamic executable is a real problem

Rarely, but it matters:

  1. You expected a dynamic binary. If a build system silently produced a static binary, dlopen() will fail at runtime because there is no dynamic loader in the process.
  2. glibc NSS breaks. A fully static glibc binary cannot load NSS modules (getpwnam, DNS via getaddrinfo can behave oddly). Use -static with care, or prefer static musl.
  3. The binary was corrupted mid-transfer. Combined with a truncated file, ldd can also report this. Check the size and a checksum if the file came over the network.

In every other case the message is informational.

What to use instead

# what kind of file is it at all
file ./binary

# architecture, word size, endianness, type
readelf -h ./binary | head -20

# any dynamic dependencies (empty output = static)
readelf -d ./binary | grep NEEDED

# interpreter present?
readelf -l ./binary | grep INTERP

# the same information without installing binutils
# -> open the binary in the browser ELF Inspector
Enter fullscreen mode Exit fullscreen mode

The browser tool reports the same fields and adds the verdicts that are easy to get wrong
by hand: PIE, static vs shared object, full RELRO, stripped, and the required
GLIBC_* / GLIBCXX_* symbol versions.

One safety note about ldd

For dynamically linked binaries, ldd is not a pure reader. Most implementations
execute the program's own loader (LD_TRACE_LOADED_OBJECTS) to get the resolution result.
On an untrusted binary that means running its interpreter.

Static binaries are not affected — that is the one case where ldd genuinely cannot
execute anything. For untrusted files, prefer readelf -d or the browser tool, which only
parse data.

FAQ

Does not a dynamic executable mean the binary is corrupted? No. It means there is no
dynamic section to inspect. A corrupted file usually gives a different error
(cannot execute binary file, invalid ELF header).

Why does ldd exit 1 then? Because it could not perform the requested inspection.
Exit status reflects the tool's success, not the binary's validity.

How do I know whether a binary is static or dynamic without ldd? file prints
statically linked or dynamically linked; readelf -d prints the dynamic section or
says there is none.

Can a static binary still fail at runtime? Yes — on a kernel that does not support its
architecture, on a filesystem mounted noexec, or when it needs files it expects to find
(glibc static + NSS). Those are different errors than this one.

Top comments (0)