glibc and libstdc++ versions by distribution — measured, not remembered
Short answer
Ubuntu 24.04 ships glibc 2.39 and GLIBCXX 3.4.33. Debian 12 ships 2.36 / 3.4.30. CentOS 7 ships 2.17 / 3.4.19. Alpine has no glibc at all — it ships musl 1.2.5.
But the number that decides whether a binary runs is not the distribution you build on. It is the oldest one among every host you intend to ship to, checked against two independent gates: glibc and GLIBCXX.
The matrix
Every row was read out of the official image on 2026-11-14 — ldd --version for glibc,
the loader itself for musl, and the highest GLIBCXX_* string inside libstdc++.so.6.
None of it is typed from memory.
| Distribution | Release | glibc | musl | Highest GLIBCXX |
|---|---|---|---|---|
| Ubuntu | 16.04 | 2.23 | — | 3.4.21 |
| Ubuntu | 18.04 | 2.27 | — | 3.4.25 |
| Ubuntu | 20.04 | 2.31 | — | 3.4.28 |
| Ubuntu | 22.04 | 2.35 | — | 3.4.30 |
| Ubuntu | 24.04 | 2.39 | — | 3.4.33 |
| Ubuntu | 25.04 | 2.41 | — | 3.4.34 |
| Debian | 9 | 2.24 | — | 3.4.22 |
| Debian | 10 | 2.28 | — | 3.4.25 |
| Debian | 11 | 2.31 | — | 3.4.28 |
| Debian | 12 | 2.36 | — | 3.4.30 |
| Debian | 13 | 2.41 | — | 3.4.33 |
| Alpine | 3.16 | — | 1.2.3 | — |
| Alpine | 3.18 | — | 1.2.4 | — |
| Alpine | 3.20 | — | 1.2.5 | — |
| Alpine | 3.22 | — | 1.2.5 | — |
| CentOS | 7 | 2.17 | — | 3.4.19 |
| Rocky Linux | 8 | 2.28 | — | 3.4.25 |
| Rocky Linux | 9 | 2.34 | — | 3.4.29 |
| AlmaLinux | 8 | 2.28 | — | 3.4.25 |
| AlmaLinux | 9 | 2.34 | — | 3.4.29 |
| Fedora | 40 | 2.39 | — | 3.4.33 |
| Fedora | 41 | 2.40 | — | 3.4.33 |
| Fedora | 42 | 2.41 | — | 3.4.34 |
| Amazon Linux | 2 | 2.26 | — | 3.4.24 |
| Amazon Linux | 2023 | 2.34 | — | 3.4.33 |
| openSUSE Leap | 15.6 | 2.38 | — | 3.4.34 |
| Arch Linux | rolling | 2.44 | — | 3.4.36 |
Two gates, not one
A binary links against symbol versions, not symbol names. memcpy@GLIBC_2.14 is not
the same requirement as memcpy@GLIBC_2.2.5. Build on Ubuntu 24.04 and the loader on
Debian 12 refuses:
./app: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found
That is gate one. Gate two is the C++ runtime:
./app: /lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.32' not found
Gate two is the one people forget, because it only bites C++ builds — and it is checked
by a different library (libstdc++.so.6, not libc.so.6). A binary can pass the glibc
check and still die on GLIBCXX.
Notice the trap in the table: Rocky 9 and AlmaLinux 9 provide glibc 2.34 but only GLIBCXX 3.4.29. A binary built on Ubuntu 22.04 (GLIBCXX 3.4.30) satisfies the glibc requirement there and still fails.
Why copying the newer library does not work (measured)
The most common advice is "copy libstdc++.so.6 from the machine where it works".
Tested on 2026-11-14:
# built on ubuntu:24.04 with g++ 13.3
$ ./hello-new
built with 13.3.0
# same binary on debian:12
$ ./hello-new
./hello-new: libstdc++.so.6: version `GLIBCXX_3.4.32' not found
./hello-new: libstdc++.so.6: version `GLIBCXX_3.4.31' not found
# copy the newer libstdc++ across, keeping the SONAME
$ cp .../libstdc++.so.6.0.33 ./lib/libstdc++.so.6
$ LD_LIBRARY_PATH=./lib ldd ./hello-new | grep stdc++
libstdc++.so.6 => ./lib/libstdc++.so.6 # the copy IS being used
$ LD_LIBRARY_PATH=./lib ./hello-new
./hello-new: libc.so.6: version `GLIBC_2.38' not found
(required by ./lib/libstdc++.so.6)
The loader picked the bundled library. It still failed — because that newer
libstdc++ was built against glibc 2.38, and Debian 12 has 2.36.
The C++ runtime and the C runtime move as a pair. Pulling one forward drags the other.
What does not work
| Attempt | Why it fails |
|---|---|
Copy libstdc++.so.6 from the newer host |
It brings a newer glibc requirement with it (measured above) |
Copy libc.so.6 |
Loader, symbol versions and NPTL ABI are one unit |
ldconfig after dropping a file in |
Rebuilds the cache; cannot create symbol versions |
| Symlink to a version that is not installed | SONAME resolves, then the version check still runs |
LD_PRELOAD the newer library |
Same dependency problem, applied to every process in the shell |
| Install newer GCC on the target | Fixes future builds, not the already-compiled binary |
Choose a build target
| Constraint | Build on | Gives you |
|---|---|---|
| Widest possible reach | debian:10 or centos:7 |
glibc 2.28 / 2.17, GLIBCXX 3.4.25 / 3.4.19 |
| Container-only, modern | the runtime image | exact match, zero surprises |
| Single file, no deps at all | alpine + -static (musl) |
no loader, no glibc, no GLIBCXX |
| C++ with new standard library features | pin the toolchain, then verify both gates | new features and a known floor |
# the cheapest cross-distro build: oldest image that still compiles your code
docker run --rm -v "$PWD":/src -w /src debian:10 g++ -O2 -o app app.cpp
Check what your host provides
ldd --version | head -1 # glibc
/lib/ld-musl-x86_64.so.1 --version 2>&1 | head -2 # musl
grep -aoE 'GLIBCXX_3\.4\.[0-9]+' /usr/lib/x86_64-linux-gnu/libstdc++.so.6 \
| sort -V | tail -1 # GLIBCXX
Check what your binary demands
Do not infer it from the build host — read it out of the file:
- ELF Inspector — the Required symbol versions panel lists every
GLIBC_*andGLIBCXX_*entry from.gnu.version_r - Compatibility Checker — compares those entries against a chosen target release and names the gate that fails
Both run entirely in the browser. The binary is never uploaded.
FAQ
Does Ubuntu 24.04 have glibc 2.39? Yes — measured with ldd --version inside
ubuntu:24.04, and it provides GLIBCXX 3.4.33.
Can I run a glibc binary on Alpine? No, not without help. Alpine ships musl; glibc
symbol versions do not exist there. Build against musl, or use gcompat and accept the
edge cases.
Is GLIBCXX the same as glibc? No. GLIBCXX_* comes from libstdc++.so.6 (GCC's C++
runtime); GLIBC_* comes from libc.so.6. They are versioned independently — see the
Rocky 9 row above, where one is new and the other is not.
Why does copying libc.so.6 never work? Because the loader, the symbol version tables and the threading ABI are one artefact. Replacing one file leaves the rest mismatched, and the failure is usually worse than the original error.
Which distribution should I build on for a binary I ship publicly? The oldest one that still compiles your code — then verify with the tools above instead of trusting the label.