Linux Error Guide: 'cannot open shared object file' Missing Shared Libraries
Fix 'error while loading shared libraries: cannot open shared object file' on Linux using ldd, ldconfig, and LD_LIBRARY_PATH to resolve missing libraries.
- #linux-admins
- #troubleshooting
- #errors
- #libraries
Exact Error Message
When you run a dynamically-linked binary and the runtime dynamic linker (ld.so) cannot locate one of its dependencies, you see this:
$ ./myapp
./myapp: error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory
The same failure under systemd shows up in the journal, often masquerading as an exec problem:
$ journalctl -u myapp.service --no-pager -n 5
systemd[1]: myapp.service: Failed to execute /usr/local/bin/myapp: error while loading shared libraries: libpq.so.5: cannot open shared object file: No such file or directory
systemd[1]: myapp.service: Main process exited, code=exited, status=127/n/a
systemd[1]: myapp.service: Failed with result 'exit-code'.
The tell-tale status=127 is the conventional “command/library not found” exit code. The message always names a specific soname (here libssl.so.1.1 or libpq.so.5), and the resolution always comes down to making that exact soname resolvable.
What the Error Means
A dynamically-linked ELF binary does not contain the library code it uses. Instead it records a list of NEEDED sonames in its header. When you exec the binary, the kernel hands control to the dynamic linker (/lib64/ld-linux-x86-64.so.2 on x86-64). The linker reads each NEEDED entry and searches for a matching file. If any one cannot be found, the linker aborts before main() ever runs and prints cannot open shared object file.
The search order the linker uses is:
- The directories in the binary’s
DT_RPATH(only if noDT_RUNPATHis present —RPATHis the deprecated, non-overridable form). - The colon-separated paths in the
LD_LIBRARY_PATHenvironment variable. - The directories in the binary’s
DT_RUNPATH(the modern form; searched afterLD_LIBRARY_PATH, so it is user-overridable). - The
ld.socache (/etc/ld.so.cache), built byldconfigfrom/etc/ld.so.conf, the snippets in/etc/ld.so.conf.d/, and the trusted defaults. - The default system directories
/lib,/usr/lib(and their 64-bit variants).
If the soname is not found in any of these, you get the error. Note that an entirely different but related message — wrong ELF class: ELFCLASS64 — appears when a file is found but is the wrong architecture (a 64-bit binary trying to load a 32-bit library, or vice versa).
Common Causes
- The library is not installed. A binary built on one machine is copied to another that lacks the dependency package (common with
libssl,libpq,libicu). - The library exists but is not in the search path. It was installed under
/optor/usr/local/liband nold.so.conf.dsnippet points there, so it is absent from the cache. - The cache is stale. Files were dropped into a known directory but
ldconfigwas never re-run, so/etc/ld.so.cachedoes not know about them. - A version (soname) mismatch. The binary needs
libssl.so.1.1but the system only shipslibssl.so.3. These are not interchangeable — a soname bump signals an ABI break. - Wrong architecture. A 32-bit binary on a 64-bit-only host, or an arm64 library on an x86-64 box. This usually yields
wrong ELF classrather thancannot open. LD_LIBRARY_PATHlost. A service or cron job runs with a stripped environment that drops theLD_LIBRARY_PATHan interactive shell had set.
How to Reproduce the Error
You can produce the failure deterministically by hiding a library the linker needs. Find a real dependency, temporarily move it, and run the binary:
# Identify a NEEDED library for a test binary (read-only)
ldd $(which curl)
If a library that ldd lists were removed or renamed out of the search path, the next invocation of that binary would print error while loading shared libraries: <soname>: cannot open shared object file. In practice the reproduction is involuntary: deploy a statically-unbundled binary onto a minimal container image (e.g. alpine, distroless, or a slim base) that lacks the runtime library, and it fails on first launch.
Diagnostic Commands
All of the following are read-only — they inspect, they do not modify anything.
# 1. Which dependencies resolve, and which do not?
ldd /usr/local/bin/myapp
linux-vdso.so.1 (0x00007ffd2b3fe000)
libpq.so.5 => not found
libssl.so.1.1 => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9c1a000000)
The => not found lines are your culprits.
# 2. Does the ld.so cache know about the soname?
ldconfig -p | grep -i libssl
libssl.so.3 (libc6,x86-64) => /lib/x86_64-linux-gnu/libssl.so.3
Here the cache has libssl.so.3 but the binary needs libssl.so.1.1 — a soname mismatch, not a missing-path problem.
# 3. Does the file exist anywhere on disk at all?
find / -name 'libssl.so*' 2>/dev/null
# 4. What sonames and rpaths are baked into the binary?
readelf -d /usr/local/bin/myapp | grep -E 'NEEDED|RPATH|RUNPATH'
0x0000000000000001 (NEEDED) Shared library: [libpq.so.5]
0x0000000000000001 (NEEDED) Shared library: [libssl.so.1.1]
0x000000000000001d (RUNPATH) Library runpath: [/opt/myapp/lib]
# 5. Architecture check (32 vs 64 bit, x86 vs arm)
file /usr/local/bin/myapp
file /lib/x86_64-linux-gnu/libssl.so.3
/usr/local/bin/myapp: ELF 64-bit LSB executable, x86-64, ...
/usr/lib/i386-linux-gnu/libfoo.so.1: ELF 32-bit LSB shared object, Intel 80386, ...
A 64-bit binary against a 32-bit library is the wrong ELF class case.
# 6. Other ways to read headers and the configured paths
objdump -p /usr/local/bin/myapp | grep -E 'NEEDED|RPATH|RUNPATH'
cat /etc/ld.so.conf
ls /etc/ld.so.conf.d/
echo "$LD_LIBRARY_PATH"
# 7. Trace exactly where the linker looks and gives up
LD_DEBUG=libs ldd /usr/local/bin/myapp
LD_DEBUG=libs prints each directory the linker tries for every soname, which pinpoints whether your RUNPATH, LD_LIBRARY_PATH, or the cache was consulted.
Step-by-Step Resolution
1. Confirm the missing soname. Run ldd and note every => not found line. That exact string (e.g. libssl.so.1.1) is what you must satisfy.
2. If the library is simply not installed, install the package. Use your distro’s tools to find which package provides the soname, then install it. For example (these write to the system):
# Debian/Ubuntu
apt-get install libssl1.1
# RHEL/Fedora
dnf install openssl-libs
# Find which package owns a file
dpkg -S libssl.so.1.1 # Debian
dnf provides '*/libssl.so.1.1' # RHEL
3. If the library exists but is outside the search path, register the directory and rebuild the cache. Drop a snippet into /etc/ld.so.conf.d/ and run ldconfig. Note that ldconfig without arguments, as root, writes /etc/ld.so.cache — it is a fix step, not a diagnostic:
echo "/opt/myapp/lib" > /etc/ld.so.conf.d/myapp.conf
ldconfig # rebuilds /etc/ld.so.cache (writes!)
ldconfig -p | grep libssl # verify it now appears
4. For a quick, non-persistent fix, export LD_LIBRARY_PATH. This is ideal for testing or wrapper scripts, but it is per-process and easy to lose:
LD_LIBRARY_PATH=/opt/myapp/lib ./myapp
For a systemd unit, set it in the unit instead so the service environment keeps it:
[Service]
Environment=LD_LIBRARY_PATH=/opt/myapp/lib
ExecStart=/usr/local/bin/myapp
5. If it is a soname-version mismatch, do not symlink blindly. Linking libssl.so.1.1 -> libssl.so.3 will load but may crash or corrupt data because the ABI changed. Prefer installing the correct major version. Only when you are certain the libraries are compatible should you create a soname symlink:
ln -s /usr/lib/x86_64-linux-gnu/libfoo.so.2.3 /usr/lib/x86_64-linux-gnu/libfoo.so.2
ldconfig
6. If it is a wrong-architecture error, install the matching multiarch package (e.g. enable i386 and install the 32-bit library) or rebuild the binary for the host’s architecture. A symlink cannot bridge a 32-bit/64-bit gap.
7. Bake the path into the binary at build time when you control compilation. Embedding a RUNPATH removes the runtime guesswork:
gcc -o myapp myapp.c -L/opt/myapp/lib -lfoo -Wl,-rpath,/opt/myapp/lib
-Wl,-rpath produces a DT_RUNPATH on modern toolchains, which is overridable by LD_LIBRARY_PATH — preferable to the old non-overridable DT_RPATH.
Prevention and Best Practices
- Run
ldconfigafter any library install outside the package manager. Manual drops into/optor/usr/local/libare invisible until the cache is rebuilt. - Use
RUNPATH, notRPATH. Build with-Wl,--enable-new-dtagsso users can override the path. For relocatable installs, use$ORIGIN:-Wl,-rpath,'$ORIGIN/../lib'. - Test on a clean image. Run new binaries inside a minimal container (
distroless,slim) to surface missing dependencies before production. - Pin soname major versions in your deploy pipeline. A
libfoo.so.2 -> .so.3jump is an ABI break; treat it like a breaking dependency upgrade. - Bundle or statically link for portable artifacts. Tools like
patchelf,ldd-driven packagers, or fully static builds avoid host-library drift entirely. - In systemd units, set
LD_LIBRARY_PATHexplicitly rather than relying on a login shell’s environment, which services never inherit.
See our Linux administration guides for more runtime-linking deep dives, and the troubleshooting category for related fixes.
Related Errors
wrong ELF class: ELFCLASS64/ELFCLASS32— the file was found but is the wrong architecture; install the correct multiarch build.undefined symbol: <name>— the library loaded but is the wrong version; a symbol the binary expects is missing from the ABI./lib64/ld-linux-x86-64.so.2: No such file or directory— the dynamic linker itself is missing (common in minimal/musl-based containers like Alpine running glibc binaries).status=127from systemd — the generic “library or executable not found” exit code; check the journal for the underlyingcannot open shared object fileline.
Frequently Asked Questions
Q: What is the difference between LD_LIBRARY_PATH and the ld.so cache?
A: LD_LIBRARY_PATH is a per-process environment variable checked before the cache and before a binary’s RUNPATH. The cache (/etc/ld.so.cache, built by ldconfig) is system-wide and persistent. Use LD_LIBRARY_PATH for quick tests; use a ld.so.conf.d snippet plus ldconfig for a permanent system-wide fix.
Q: I ran ldconfig but ldd still says “not found”. Why?
A: ldconfig only indexes directories it knows about — the defaults plus whatever is listed in /etc/ld.so.conf and /etc/ld.so.conf.d/. If your library lives in an unlisted directory, add a .conf snippet pointing to it first, then re-run ldconfig. Also confirm the file’s soname actually matches what the binary needs.
Q: Can I just symlink libssl.so.1.1 to libssl.so.3?
A: Only if you are certain the two are ABI-compatible, which a soname major-version change explicitly says they are not. A wrong symlink lets the program start but can cause crashes or silent data corruption. Install the correct version instead.
Q: Is RPATH or RUNPATH better?
A: RUNPATH (DT_RUNPATH). It is searched after LD_LIBRARY_PATH, so administrators and users can override it. The older DT_RPATH is searched first and cannot be overridden, which is why it is deprecated. Build with -Wl,--enable-new-dtags to get RUNPATH.
Q: Why does my program work in my shell but fail as a systemd service?
A: Services do not inherit your interactive shell’s environment, including any LD_LIBRARY_PATH you exported in .bashrc. Either install the library system-wide and rebuild the cache, or set Environment=LD_LIBRARY_PATH=... directly in the unit file.
Download the Free 500-Prompt DevOps AI Toolkit
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.