SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-21

Linuxバイナリが動かない原因はglibcとmuslの違いです

「GLIBC_2.34 not found」は、ビルド環境が必要なglibcの最低バージョンを決めたサインです。バイナリの要求を確認し、移植性を高める4つの配布方法を解説します。

古いサーバーで Linux バイナリが実行できない理由

移植性の高い Linux バイナリを作るのは困難です。glibc(GNU C library)は、一方向の互換性しか保証しないためです。古いバイナリは新しい glibc で実行できます。しかし、新しいバイナリは古い glibc では実行できません。コンパイルに使用したマシンが、デプロイ先のすべてのマシンに必要な最低バージョンを決めます。

エラーには、要求された正確なバージョンが示されます。

./mytool: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found (required by ./mytool)

ファイルが破損しているわけでも、設定に問題があるわけでもありません。動的ローダーはバイナリ内部に記録されたバージョンタグを読み取り、システムの libc にそのタグがあるか確認します。タグが見つからないため、プログラムの起動を拒否します。ファイルを再ダウンロードして chmod +x を実行しても、何も変わりません。必要な条件はファイル自体に書き込まれているためです。バイナリのビルド方法を変更するか、配布するものを変更する必要があります。

glibc のシンボルバージョニングが実際に行うこと

glibc がエクスポートするすべての関数には、バージョンタグが付いています。printf 内の libc.so.6 は、実際には printf@@GLIBC_2.2.5 です。glibc が関数の動作や ABI(アプリケーションバイナリインターフェイス)を変更した場合、古い関数を置き換えることはありません。古いコードを古いタグのまま保持し、新しいコードを新しいタグで追加します。そのため、1 つの libc.so.6 に同じシンボルの複数のバージョンが同時に存在します。2009 年のバイナリは、2009 年のタグが残っていることを確認できます。これが、フォワード互換性が非常に高い理由です。

リンク処理では、使用した内容が記録されます。バイナリには .gnu.version_r セクションが含まれ、実質的に「libc.so.6 から GLIBC_2.38 が必要」と示します。古い libc にはそのタグが存在しないため、ローダーは main の実行前に停止します。フォールバックはありません。フォールバックを許可すると、コンパイル時に使用した関数とは異なる関数が、気付かないうちにプログラムへ渡されるためです。

2021 年以降に最も多いきっかけは glibc 2.34 です。このリリースでは libpthreadlibdllibc に統合され、すべての C プログラムを開始する関数である __libc_start_mainGLIBC_2.34 タグへ移動しました。そのため、glibc 2.34 以降を搭載したディストリビューションでコンパイルした hello-world は、ソースコードが最新の機能を何も呼び出していなくても GLIBC_2.34 を要求します。これが、独自のコードを何年も変更していない人々の環境で、この問題が突然発生した理由です。

ディストリビューションが提供する glibc のバージョンはどれですか?

Chartglibc version in each distribution's own repositories
The data behind this chart
[
  {
    "distro": "CentOS 7",
    "glibc": 2.17
  },
  {
    "distro": "RHEL 8",
    "glibc": 2.28
  },
  {
    "distro": "Ubuntu 20.04",
    "glibc": 2.31
  },
  {
    "distro": "Debian 11",
    "glibc": 2.31
  },
  {
    "distro": "RHEL 9",
    "glibc": 2.34
  },
  {
    "distro": "Ubuntu 22.04",
    "glibc": 2.35
  },
  {
    "distro": "Debian 12",
    "glibc": 2.36
  },
  {
    "distro": "Ubuntu 24.04",
    "glibc": 2.39
  },
  {
    "distro": "Debian 13",
    "glibc": 2.41
  }
]

各ディストリビューションが独自のリポジトリで提供しているバージョンです。ディストリビューションが公開している情報に基づき、2026 年 8 月時点の内容です。CentOS 7 はサポート終了から長期間経過していますが、既存のサーバーで引き続き稼働しているため一覧に残しています。使用している環境で ldd --version を実行すると、1 行目に glibc のリリースが表示されます。getconf GNU_LIBC_VERSION でも確認できます。

この 9 行は、段階表として読み取ってください。glibc 2.41 の Debian 13 上でビルドすると、その結果は Debian 13 より古い環境では実行できません。glibc 2.31 の Ubuntu 20.04 上でビルドすると、同じソースはその行より上にあるすべての環境で実行でき、Debian 13 の環境も含まれます。サポート対象にする最も古いサーバーが、考慮すべき唯一のビルドホストです。そのため、標準化するディストリビューションによって、そこでコンパイルするすべてのソフトウェアの ABI の下限が決まります。これは、フリートを構築する前に、VPS で実行する OS の選択に伴う他のトレードオフと合わせて決めておく価値があります。

バイナリが必要とする glibc の最小バージョンを調べる方法

ファイルから読み取ります。ビルド情報がないベンダー提供のバイナリを含め、どのバイナリでも使えます。

objdump -T ./mytool | grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail -5

最後の行が下限です。objdump が見つからない場合は、binutils パッケージをインストールします。そのホストに何もインストールできない場合は、strings -a ./mytool | grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail -5 でほぼ同じ結果を得られます。タグはファイル内にプレーンテキストとして保存されているためです。

readelf -V ./mytool を使うと、同じ要件を構造化して確認できます。Version needs section '.gnu.version_r' という見出しのブロックを探してください。必要なライブラリごとにまとめられた、タグごとの Name: GLIBC_x.y 行が表示されます。

どの関数セットが下限を決めているかを調べるには、そのタグを grep します。objdump -T ./mytool | grep GLIBC_2.38。該当するのは単一のシンボルであることが多く、場合によっては回避できます。objdump -T が何も出力しない場合、そのバイナリは静的リンクされています。そのため、動的シンボルテーブルがなく、出力すべきバージョン要件もありません。

バイナリについて filereadelf -d から分かること

file を使うと、アーキテクチャとリンク形式を 1 行で確認できます。

file ./mytool

通常の glibc ビルドは次のように表示されます。

ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=..., for GNU/Linux 3.2.0, not stripped

静的ビルドでは statically linked と表示され、インタープリターは指定されません。musl ビルドでは別のインタープリターが指定されます。

ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, ...

重要なのはインタープリターの項目です。これはプログラムの実行前にカーネルが起動する動的ローダーであり、そのパスが対象マシン上に存在している必要があります。/lib/ld-musl-x86_64.so.1 は、誰かが musl をインストールしていない限り、Debian または Ubuntu サーバーには存在しません。

readelf -d ./mytool では、バイナリが要求する共有ライブラリを名前で一覧表示します。

Dynamic section at offset 0x2d58 contains 27 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)   Shared library: [libssl.so.3]
 0x0000000000000001 (NEEDED)   Shared library: [libc.so.6]
 0x000000000000001d (RUNPATH)  Library runpath: [$ORIGIN/../lib]

NEEDED の各エントリは、soname で照合される必須要件です。libssl.so.3 は OpenSSL 3 なので、libssl.so.1.1 しかないサーバーではそのバイナリをロードできません。理由は soname にあります。異なる soname は、意図的に互換性を持たない ABI を示します。RUNPATH はローダーが最初に検索する場所を指定し、$ORIGIN はバイナリを格納しているディレクトリに展開されます。これにより、自己完結型のバンドルが独自のライブラリを見つけられます。

信頼できないバイナリに対して ldd を実行しないでください。glibc では、ldd がローダーの下でプログラムを実行して依存関係を解決することがあるため、悪意のあるファイルによってコードが実行される可能性があります。readelfobjdump はバイト列を読み取るだけです。これらを行う前に、ダウンロードしたファイルがプロジェクトで実際に公開されたファイルであることを確認してください。公開されたチェックサムと照合してダウンロードを検証することだけが、手元のバイト列の出所を確認する手段です。

実際に表示されるエラーと、それぞれの意味

ローダーが、見つからない GLIBC のバージョンを指定する。 バイナリは、対象環境にあるものより新しい glibc を前提にコンパイルされています。対象環境に何かをインストールしても、安全には解決できません。以下の4つの選択肢から選んでください。

シェルが、はっきり見えているファイルに対して No such file or directory と報告する。 不足しているのはバイナリではなく、インタープリターです。ELF ヘッダーにローダーのパスがない場合、execveENOENT を返し、シェルは得られる唯一のメッセージを表示します。file を実行して、インタープリターのパスを確認してください。musl にリンクされたバイナリを glibc のみのサーバーで実行すると、まったく同じ症状になります。

cannot execute binary file: Exec format error は、アーキテクチャが一致していないことを示します。たとえば、arm64 サーバー上で x86-64 バイナリを実行している場合や、その逆です。file の出力の1行目で、手元のバイナリのアーキテクチャを確認できます。

error while loading shared libraries: libssl.so.3: cannot open shared object file は、NEEDED ライブラリがないか、別の soname で提供されていることを示します。一致するディストリビューションのパッケージをインストールしてください。パッケージ名はディストリビューションの系統によって異なります。そのため、README から apt の行をコピーする前に読み替えてください。dnf と apt のコマンド対応で、その対応関係を確認できます。

musl でビルドしたプログラムを glibc 上で正常に実行したときに、単独の Segmentation fault が表示される。 通常はスレッドのスタックサイズが原因です。これは選択肢2で説明しています。

移植可能な Linux バイナリをリリースする4つの方法

どの方法にも、実際のコストが伴います。見た目の簡潔さではなく、プログラムが実行時に何を行うかを基準に選択してください。

対応対象とする最も古いディストリビューション上でビルドする

最も地味ですが、通常はこれが正解です。サポート対象として保証する最も古いディストリビューションのコンテナイメージ内でコンパイルします。リンカーが記録できるのは、実際に使用した古い glibc が持つタグだけです。そのため、実行ファイルは glibc の動作をすべて維持した通常の動的リンクバイナリのまま、対応する下限をそのリリースまで下げられます。

docker run --rm -v "$PWD:/src" -w /src ubuntu:20.04 sh -c 'apt-get update && apt-get install -y build-essential && make'

出力は glibc 2.31 以上で動作します。思い込みで済ませず、確認してください。前述の objdump -T のワンライナーを結果に対して実行し、最も高いタグが想定したものになっていることを確認します。

コストはツールチェーンです。古いベースイメージには古いコンパイラも含まれるため、コードで新しい C++ 標準が必要になると問題になります。Go と Rust では、ツールチェーンがディストリビューションのパッケージとは独立してコンテナ内にインストールされるため、この問題をほぼ回避できます。C と C++ では、ディストリビューション独自のツールチェーンチャネルから新しいコンパイラを追加できます。または、古い glibc と最新の GCC を組み合わせるために存在する manylinux イメージを使用できます。Zig に付属する C コンパイラも、zig cc -target x86_64-linux-gnu.2.28 のように指定した glibc を直接ターゲットにできます。これにより、古いイメージを維持せずに同じ下限を実現できます。

musl に静的リンクする

musl は、静的リンクを前提に設計された小さな C ライブラリです。静的 musl バイナリには独自の libc が含まれ、インタープリターを指定せず、対象アーキテクチャに対応する任意の Linux カーネルで実行できます。多くの単一ファイル形式のツールは、この方法でビルドされています。

sudo apt install -y musl-tools
musl-gcc -static -O2 hello.c -o hello
file ./hello

file はインタープリターのフィールドがなく、statically linked になっているはずです。Rust ではターゲットを追加し、そのターゲット向けにビルドします。

rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl

Go では、これらの設定は不要です。CGO_ENABLED=0 go build を指定すると、結果はすでに静的になり、libc にも一切リンクしません。

NSS の名前解決は動作が変わります。 glibc は NSS(name service switch)を通じてユーザー名とホスト名を解決します。実行中に dlopen を使って libnss_* モジュールを読み込みます。静的リンクされた glibc ではこの処理ができないため、リンカーは次のメッセージを表示します。

warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking

musl は NSS 自体を実装しないことで、この警告を回避します。独自のリゾルバーを使用し、/etc/resolv.conf/etc/hosts を直接読み取ります。通常の VPS では、この構成で問題なく、より単純です。アカウントや名前を LDAP または SSSD から取得するホストでは、他のすべてのプログラムからは見えていても、対象のバイナリからは見えません。musl のリゾルバーは glibc より新しいものではありません。512 バイトを超える DNS 応答に対する TCP フォールバックは musl 1.2.4 で 2023 年に追加されたため、それより古い musl に対してビルドすると、大きな応答が切り詰められます。

dlopen は動作しません。 静的 musl バイナリでは、dlopen は常に失敗するスタブです。実行時にコードを読み込む機能は使用できません。対象には、プラグインシステム、PAM モジュール、GPU ドライバー、glibc 自身の iconv 文字セットモジュールなどがあります。プログラムが dlopen を必要とする場合、静的リンクは選択できません。他の 3 つの方法のいずれかを使う必要があります。

セキュリティ更新は自分で管理する必要があります。 動的リンクされたバイナリは、サーバーでパッケージを更新すると、その時点で libc の修正を取り込みます。静的バイナリは取り込みません。libc、または同梱した静的 OpenSSL に対して CVE(common vulnerabilities and exposures)の項目が登録された場合は、再ビルドして再配布する必要があります。すでに配置した各コピーは、誰かがファイルを置き換えるまで脆弱なままです。何をリンクしたかを記録してください。対象ホスト上の情報だけでは、管理者は単一ファイルのバイナリに 2 年前のライブラリが含まれていることを確認できません。

ライセンスが変わります。 glibc は LGPL であり、静的リンクを行うと LGPL の再リンク義務が発生します。受領者がプログラムを別の glibc に対して再リンクできるよう、必要なものを提供しなければなりません。musl は MIT で、このような条件はありません。単一ファイルのバイナリを配布するプロジェクトが、静的 glibc ではなく musl を選ぶ主な理由はこれです。

2 つのランタイム差異により、分かりにくいクラッシュが発生します。 musl のデフォルトのスレッドスタックは 128 KiB です。glibc では 8 MiB です。そのため、スレッドスタック上に大きなバッファーを置くコードは、メッセージもログ行も出さずにセグメンテーションフォールトを起こします。pthread_attr_setstacksize でサイズを明示するか、バッファーをヒープに移してください。musl のアロケーターも、多数のスレッドが同時に割り当てを行う場合より、小さなサイズと予測可能な動作を重視して設計されています。そのため、割り当ての多いマルチスレッドプログラムでは、実行速度が明確に低下することがあります。どちらのライブラリの評判も鵜呑みにせず、自分のワークロードでベンチマークしてください。

オプション 3: loader とライブラリを同梱する

ライブラリをバイナリの隣に配置し、対応する loader も同梱して、その loader 経由でプログラムを起動します。

./ld-linux-x86-64.so.2 --library-path ./lib ./mytool

この設定を永続化するには、patchelf を使用してパスをファイルに書き込みます。

patchelf --set-interpreter "$PWD/lib/ld-linux-x86-64.so.2" --set-rpath '$ORIGIN/lib' ./mytool

これが機能するかどうかを決めるルールは1つです。loader と libc.so.6 は、同じ glibc ビルドから提供されたものでなければなりません。これらは対応する組み合わせです。ホストの loader と同梱した libc を混在させると、読み取り可能なエラーではなく、起動中のクラッシュが発生します。両方を同梱するか、どちらも同梱しないでください。

AppImage はこの方式をパッケージ化したものです。ペイロードを squashfs イメージに格納し、小さな runtime でマウントします。見落とされやすい特性があります。AppImage は glibc を同梱しないため、最新のディストリビューションでビルドした AppImage でも、古いサーバーでは同じバージョンエラーで失敗します。AppImage の公式ガイダンスでは、サポート対象の中で最も古いベース上でビルドすることを推奨しています。そのため AppImage は option 1 の代替ではなく、その上に構築する配布形式です。

ヘッドレスサーバーでは、AppImage がペイロードをマウントするために FUSE (filesystem in userspace) が必要です。最小構成の VPS イメージには、FUSE が含まれていないことがよくあります。エラーには libfuse.so.2 と表示されます。./App.AppImage --appimage-extract-and-run を使用すればマウント自体を省略できます。このオプションは一時ディレクトリに展開し、そこから実行します。

オプション 4: コンテナイメージをリリースする

プログラムとともにユーザーランド全体を移行します。イメージには独自の libc が含まれるため、ホストの glibc に依存しなくなり、カーネルとアーキテクチャだけを一致させれば済みます。これは最も単純な選択肢であり、この問題群全体をなくせるため、多くのサーバーソフトウェアがこの形で配布されています。VPS で Docker を実行することが、通常の利用方法です。

ホストのカーネルには、引き続き制約があります。新しい glibc は新しいシステムコールを使用するため、古いコンテナホストではそれらがブロックされることがあります。glibc 2.34 以降は clone3 を使用しますが、古いデフォルトの seccomp プロファイルでは拒否されます。症状は、Operation not permitted が表示されて即座に失敗し、glbic への言及がどこにもないことです。ホスト上のコンテナランタイムを更新すれば解決します。ブロックはカーネルではなく、ランタイムのシステムコールフィルターに存在するためです。

コストは一般的なものです。対象にはコンテナランタイムと、その使用権限が必要です。ダウンロードサイズは 1 ファイルから数十または数百メガバイトに増えます。ベースイメージとそのパッチ適用スケジュールも管理する必要があるため、オプション 2 で回避したセキュリティ作業が、イメージの再ビルドという形で戻ってきます。長時間稼働するサービスであれば、妥当なトレードオフです。誰かが 1 回だけ実行するコマンドラインツールには適していません。

どの選択肢を選ぶべきか

  • 自分が管理するサーバーで、すべて同じディストリビューションを実行する内部ツールの場合は、そのディストリビューション上で動的にビルドして完了とします。
  • 見知らぬ利用者がダウンロードして実行する単一ファイルの場合は、プログラムが dlopen を必要とせず、NSS に依存する名前解決も行わないなら、static musl を使用します。
  • プラグインまたは GPU にアクセスするプログラムの場合は、動的リンクのまま、古いベース上でビルドし、option 3 を使って配布します。
  • ランタイムがすでに存在するマシンで長時間実行するサービスの場合は、image を配布します。

どの選択肢を選んだ場合でも、記録して確認してください。ビルドホストの glibc は、リリースプロセスの一部になります。ビルドマシンをある LTS リリースから次の LTS リリースへアップグレードすると、必要な最低バージョンが気付かないうちに引き上げられ、先月までは問題なく使えていた利用者の環境で動かなくなることがあります。ビルド用 image は tag で固定し、出力に含まれる最も高い GLIBC_ tag をビルドステップで検証してください。これにより、誰かの端末で失敗する前に、パイプラインで検出できます。

FAQ

サーバーで「version GLIBC_2.38 not found」と表示されるのはなぜですか?

そのバイナリは、サーバーにインストールされているものより新しい glibc を持つマシンでコンパイルされています。glibc のシンボルバージョン管理による前方互換性には限りがあります。古いバイナリは新しい glibc で動作しますが、新しいバイナリは古い glibc では動作しません。ローダーはファイルに記録された正確なバージョンタグを必要とし、古い libc にはそのタグが存在しないためです。サーバーに何かをインストールして安全に解決できる問題ではありません。古いベースイメージで再ビルドする、static な musl ビルドを提供する、対応するローダーとライブラリをまとめて同梱する、またはコンテナイメージを提供してください。

バイナリが必要とする glibc のバージョンを確認するにはどうすればよいですか?

objdump -T ./mytool | grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail -5 でファイルからバージョンタグを読み取ります。最後の行に、そのバイナリをロードできる最低限の glibc バージョンが表示されます。readelf -V ./mytool では .gnu.version_r セクションから同じ要件を表示し、必要なライブラリごとにまとめます。objdump -T が何も出力しない場合、そのバイナリは static で、glibc の要件はありません。結果を ldd --version で確認したサーバー自身のバージョンと比較してください。

musl バイナリは glibc バイナリより遅いですか?

ワークロードによって異なるため、正確な答えは計測することです。static な musl バイナリは、実行するローダーがなく、exec 時の再配置処理もないため、より速く起動します。一方、musl のアロケーターは、多数のスレッドが同時にメモリを割り当てる処理よりも、小さなサイズと予測可能な動作を重視して設計されています。また、musl は glibc より手動最適化された文字列処理やメモリ処理のルーチンが少ないため、メモリ割り当てや文字列処理が多いマルチスレッドプログラムでは、明らかに遅くなることがあります。どちらの主張を受け入れる場合も、使用するプログラムを使用するサーバー上でベンチマークしてください。

存在するファイルに対して bash が「No such file or directory」と表示するのはなぜですか?

不足しているのはバイナリではなく、dynamic loader です。kernel は ELF ヘッダーから interpreter のパスを読み取り、そのパスが存在しない場合に ENOENT を返します。shell は利用できるメッセージがそれしかないため、その内容を表示します。file ./mytool を実行し、interpreter フィールドを確認してください。Debian または Ubuntu サーバーで /lib/ld-musl-x86_64.so.1 と表示される場合、そのバイナリは musl にリンクされており、システムは glibc を使用しています。そのため、musl 対応版のダウンロードまたは static ビルドが必要です。

新しいサーバーから libc.so.6 をコピーすれば解決できますか?

いいえ。libc.so.6ld-linux-x86-64.so.2 は、同じ glibc ビルドに由来する対応する組み合わせです。また、マシン上のすべてのプロセスがこれらを使用します。そのため、システムのコピーを上書きすると、元に戻すために必要なツールを含め、サーバー上のあらゆるプログラムを実行できなくなるおそれがあります。古いホストで1つの新しいバイナリを実行する必要がある場合は、新しい glibc を private directory に展開し、--library-path を使用して専用のローダー経由でそのプログラムを起動してください。この方法の影響はそのプロセスだけに限定されます。6か月後にも予期しない問題を起こさない方法は、古い glibc に対して再ビルドすることです。

#glibc#musl#static-linking#portability#binaries