curlのエラーコードは、curl: (7) Failed to connect ... のように括弧の中に出る数字で、curlコマンドがシェルに返す終了コードと同じ値です。公式マニュアルのExit codes節には0〜100の範囲で87件が載っており、このほかECH関連の失敗で101が返ることが同じマニュアルの--echの項に書かれています。3桁のHTTPステータスコード(404や500)とは別物で、404が返ってもcurlの終了コードは0のままです。この記事では、公式一覧の87件を表にしたうえで、よく出る6・7・28・35・52・56・60について、curl 8.23.0(2026年10月14日リリース)とmacOS同梱の8.7.1で実際に表示されたメッセージと対処を並べます。
まとめ
- 括弧の数字が終了コード。
$?または-w '%{exitcode}'で取得できます。公式一覧は87件・欠番14件で、既存の番号の意味は変更しないと明記されています。 - まず疑う番号は6・7・28・35・60。順に、名前解決、接続、タイムアウト、TLSハンドシェイク、証明書の検証で止まっています。6・7・35・60はHTTPの応答を受け取る前の失敗で、28は接続前と応答待ちのどちらでも出ます。
- 404や500ではcurlは失敗しない。終了コードに反映させるには
--failか--fail-with-bodyを付けます。返るのは22ですが、macOS同梱の8.7.1はHTTP/2接続で56を返しました。 --retryは--failが無くても503を再試行する。再試行しても503のままなら、--failなしの終了コードは0です。再試行の有無と成否の判定は別物として扱ってください。- CIや監視で判定するなら、終了コードの数値を直書きせず
-w '%{http_code}'の3桁と終了コードを両方見ます。000はHTTPの応答コードを受け取れていないことを示します。
curlのエラーコードとHTTPステータスコードの違い
同じ「エラーコード」と呼ばれますが、返す主体が違います。HTTPステータスコードはサーバーが応答に含める3桁で、curlの終了コードはcurlというプログラムが処理を終えたときにシェルへ返す整数です。
| 観点 | curlの終了コード | HTTPステータスコード |
|---|---|---|
| 返す主体 | curlコマンド | Webサーバー・プロキシ |
| 値の範囲 | 一覧は0〜100(ECHで101) | 100〜599 |
| 表示のされ方 | curl: (6) ... |
HTTP/2 404 |
| 取得方法 | $?・%{exitcode} |
%{http_code}・-I |
| サーバーに届かない場合 | 6・7・28・35・60など | 返らない(000と表示) |
切り分けは終了コードから始めます。0以外なら、curlの処理が途中で止まっています。6・7・28・35・60なら通信、3・23・26なら引数や入出力、22なら --fail が拾ったHTTPエラーです。0だった場合は、サーバーが返した3桁を読みます。「500が出ているのにcurlの一覧に500が無い」という行き止まりは、この2つを混同したときに起きます。
よく出るcurlエラーコードの原因と対処(8.23.0と8.7.1の実測)
以下は2026年10月17日に、Homebrewのcurl 8.23.0(OpenSSL 4.0.3)とmacOS 26同梱のcurl 8.7.1(SecureTransport、LibreSSL 3.3.6)で同じコマンドを実行した結果です。この節で扱う番号は両者で一致しましたが、メッセージの文言はビルドで変わります(番号まで割れた例は、後述の --fail と --cacert です)。ログをメッセージの全文でgrepする監視は、curlの入れ替えで外れることがあります。番号で拾ってください。
6 Could not resolve host:名前解決の失敗
$ curl -sS -o /dev/null https://no-such-host-3038.invalid/
curl: (6) Could not resolve host: no-such-host-3038.invalid
DNSでホスト名が引けていません。ホスト名のtypo、社内DNSにしか無い名前を外から引いている、コンテナ内の /etc/resolv.conf が想定と違う、DNSサーバー自体の障害などが典型例です。dig や nslookup で同じ名前を引き、引けるのにcurlだけ失敗するなら、プロキシ設定(環境変数 https_proxy)を疑います。プロキシ名そのものが引けない場合は6ではなく5になります。
7 Failed to connect:TCP接続の失敗
# 8.23.0
curl: (7) Failed to connect to 127.0.0.1:1 after 0 ms: Could not connect to server
# 8.7.1
curl: (7) Failed to connect to 127.0.0.1 port 1 after 0 ms: Couldn't connect to server
名前は引けたが、TCP接続が成立していない状態です。「after 0 ms」のように即座に失敗するなら、相手が接続を拒否しています(サービス停止、ポート番号の誤り、待ち受けアドレスが127.0.0.1だけ)。時間がかかってから失敗するなら、ファイアウォールで落とされている可能性が高くなります。--retry は既定では接続拒否を再試行しません。起動直後のサービスを待つ用途では --retry-connrefused を併用します。
28 Operation timed out:タイムアウト
# 接続段階で時間切れ(--connect-timeout 2)
8.23.0: curl: (28) Connection timed out after 2001 milliseconds
8.7.1 : curl: (28) Failed to connect to 10.255.255.1 port 80 after 2147 ms: Timeout was reached
# 接続後、応答待ちで時間切れ(-m 2)
8.23.0: curl: (28) Operation timed out after 2016 milliseconds with 0 bytes received
同じ28でも、メッセージが示す段階が違います。8.23.0の「Connection timed out」は接続の段階で、「Operation timed out … with 0 bytes received」は接続後に1バイトも受け取れないまま時間切れになったことを表します。後者でもサーバー側では処理が済んでいる可能性があるため、28だけでリクエストが届いたかどうかは判断できません。--connect-timeout は接続までの上限、-m(--max-time)は転送全体の上限なので、監視スクリプトでは両方を指定しておくと、どちらで止まったかがメッセージで分かります。
35 TLS connect error:TLSハンドシェイクの失敗
$ curl -sS -o /dev/null https://example.com:80/
8.23.0: curl: (35) TLS connect error: error:0A00010B:SSL routines::wrong version number
8.7.1 : curl: (35) LibreSSL/3.3.6: error:1404B42E:SSL routines:ST_CONNECT:tlsv1 alert protocol version
HTTPSで話しかけた相手がTLSで応答しなかった例です。代表的なのは、平文HTTPのポートに https:// で接続しているケースで、OpenSSL系では「wrong version number」が出ます。ほかに、サーバーが古いTLS 1.0・1.1しか受け付けない、途中のプロキシやファイアウォールがTLSを遮断している、といった原因があります。-v を付けて、どのTLSバージョンで交渉が止まったかを確認してください。
60 証明書検証の失敗:期限切れ・自己署名・ホスト名不一致
# 期限切れ(expired.badssl.com)
8.23.0: curl: (60) SSL certificate OpenSSL verify result: certificate has expired (10)
8.7.1 : curl: (60) SSL certificate problem: certificate has expired
# 自己署名(self-signed.badssl.com)
8.23.0: curl: (60) SSL certificate OpenSSL verify result: self-signed certificate (18)
# ホスト名不一致(wrong.host.badssl.com)
8.23.0: curl: (60) SSL: no alternative certificate subject name matches target hostname 'wrong.host.badssl.com'
60には複数の原因が同じ番号で集まり、上はその代表例の3つです。信頼するCAの一覧に発行元が無い場合も60になります。メッセージ後半で区別してください。社内の検証環境で自己署名証明書を使っているなら、-k で検証を外すのではなく、その証明書を --cacert で渡します。-k は中間者攻撃を検知できなくなるので、スクリプトに残すと本番でも検証が切れたままになります。社内プロキシがTLSを中継して証明書を差し替えている環境では、プロキシのルート証明書を、curlが参照するCAストアに追加するか --cacert で指定します。curlのビルドによってはOSの信頼ストアではなくファイルのCAバンドルを読むため、OSに登録しただけでは直らないことがあります。
52・56・18:応答が空・受信の失敗・途中で切れた転送
curl: (52) Empty reply from server
curl: (18) end of response with 997 bytes missing # 8.23.0
curl: (18) transfer closed with 997 bytes remaining to read # 8.7.1
3つとも「接続はできたが、応答が正しく届かなかった」系統です。52はヘッダーも含めて何も返らずに接続が閉じられた状態で、上の例は接続を受け付けた直後に閉じるサーバーで再現しました。18はContent-Lengthで予告された長さの途中で切れた状態で、上の例はContent-Length 1000に対して3バイトだけ送って切断させたものです。56は受信中に接続がリセットされた場合のほか、後述のとおり8.7.1では404にも使われます。いずれもサーバー側のログと突き合わせ、同じ時刻にプロセスの再起動やタイムアウトが無いかを確認します。
3・23・26・47・1:curlの引数と入出力が原因のコード
| コード | 8.23.0で出たメッセージ | 原因 |
|---|---|---|
| 3 | URL rejected: No host present | URLの書式誤り |
| 23 | client returned ERROR on write of 2 bytes | 保存先に書けない |
| 26 | Failed to open/read local data from file/application | 送るファイルが読めない |
| 47 | Maximum (3) redirects followed | リダイレクトのループ |
| 1 | Received HTTP/0.9 when not allowed | HTTPでない応答 |
23は -o の保存先ディレクトリが無い、権限が無い、ディスクが満杯のときに出ます。シェル変数の展開漏れでURLが空になると3になるので、CIでは変数を引用符で囲んでください。47は -L 使用時に既定の上限(50回)か --max-redirs の値に達した状態で、転送元と転送先が互いを指していないかを curl -sSIL で確かめます。1は「未対応のプロトコル」ですが、実際にはHTTPのヘッダーとして読めない応答を受けたときにも返ります。
もう1つ版差がありました。存在しないファイルを --cacert に渡すと、8.7.1は77(CA証明書を読めない)を返しましたが、8.23.0は転送の前にオプションの誤りとして2で止まりました。証明書ファイルの欠落を77で検知している監視は、curlを更新すると判定が変わります。
curlの終了コード一覧(curl 8.23.0公式マニュアルの87件)
curl公式マニュアルのExit codesに載っている全件です。0〜100の範囲に87件が並び、20・24・29・32・40・44・46・50・51・57・62・75・76・81の14件は欠番です。マニュアルは「今後エラーコードが増えることはあっても、既存のものは変更しない」と明記しています。ネット上の一覧には51を証明書エラーとして載せているものがありますが、現行の51は欠番で、証明書の検証失敗は60です。
| コード | 意味 | コード | 意味 |
|---|---|---|---|
| 0 | 成功 | 55 | データ送信に失敗 |
| 1 | 未対応のプロトコル | 56 | データ受信に失敗 |
| 2 | 初期化に失敗 | 58 | ローカル証明書の問題 |
| 3 | URLの書式が不正 | 59 | 指定した暗号方式を使えない |
| 4 | ビルドに機能が含まれていない | 60 | サーバー証明書を検証できない |
| 5 | プロキシ名を解決できない | 61 | 未知の転送エンコーディング |
| 6 | ホスト名を解決できない | 63 | 最大ファイルサイズ超過 |
| 7 | ホストへ接続できない | 64 | 要求したFTPのSSLレベルに失敗 |
| 8 | サーバー応答を解析できない | 65 | 再送のための巻き戻しに失敗 |
| 9 | FTPのアクセス拒否 | 66 | SSLエンジンの初期化に失敗 |
| 10 | FTPのaccept失敗 | 67 | ログインを拒否された |
| 11 | FTPのPASS応答が不正 | 68 | TFTPにファイルがない |
| 12 | FTPのaccept待ちでタイムアウト | 69 | TFTPの権限エラー |
| 13 | FTPのPASV応答が不正 | 70 | TFTPサーバーのディスク不足 |
| 14 | FTPの227行の書式が不正 | 71 | TFTPの不正な操作 |
| 15 | FTPの227行のホストを解決できない | 72 | TFTPの不明な転送ID |
| 16 | HTTP/2フレーミング層のエラー | 73 | TFTPでファイルが既に存在 |
| 17 | FTPで転送モードをバイナリにできない | 74 | TFTPに該当ユーザーがない |
| 18 | 転送が途中で終わった | 77 | CA証明書ファイルを読めない |
| 19 | FTPのRETRに失敗 | 78 | URLの参照先が存在しない |
| 21 | FTPのquoteコマンドがエラー | 79 | SSHセッションのエラー |
| 22 | HTTPが400以上を返した | 80 | SSL接続の終了処理に失敗 |
| 23 | ローカルへの書き込みエラー | 82 | CRLファイルを読み込めない |
| 25 | アップロードの開始に失敗 | 83 | 発行者チェックに失敗 |
| 26 | 読み込みエラー | 84 | FTPのPRETコマンドが失敗 |
| 27 | メモリ確保に失敗 | 85 | RTSPのCSeq番号が不一致 |
| 28 | タイムアウト | 86 | RTSPのセッションIDが不一致 |
| 30 | FTPのPORTが失敗 | 87 | FTPのファイル一覧を解析できない |
| 31 | FTPのRESTが失敗 | 88 | FTPのチャンクコールバックがエラー |
| 33 | レンジ指定が使えない | 89 | 利用できる接続がない |
| 34 | HTTP POSTの内部生成エラー | 90 | ピン留めした公開鍵と不一致 |
| 35 | TLSハンドシェイクに失敗 | 91 | 証明書ステータスが不正 |
| 36 | 中断したダウンロードを再開できない | 92 | HTTP/2ストリームのエラー |
| 37 | fileスキームのファイルを読めない | 93 | コールバック内からAPIを呼んだ |
| 38 | LDAPのbindに失敗 | 94 | 認証関数がエラーを返した |
| 39 | LDAPの検索に失敗 | 95 | HTTP/3層のエラー |
| 41 | 必要なLDAP関数が見つからない | 96 | QUIC接続エラー |
| 42 | コールバックによる中断 | 97 | プロキシのハンドシェイクエラー |
| 43 | 内部エラー(引数が不正) | 98 | クライアント証明書が必要 |
| 45 | 送信元インターフェースを使えない | 99 | pollまたはselectの致命的エラー |
| 47 | リダイレクト回数の上限超過 | 100 | 値またはデータ長が上限超過 |
| 48 | libcurlが未知のオプションを拒否 | ||
| 49 | telnetオプションの書式が不正 | ||
| 52 | サーバーが何も返さなかった | ||
| 53 | SSLエンジンが見つからない | ||
| 54 | SSLエンジンを既定に設定できない |
libcurl(CURLE_)とPHPのcurl_errnoで番号がずれる箇所
プログラムからlibcurlを使うと、同じ番号が CURLE_COULDNT_CONNECT(7)のような定数名で返ります。PHPの curl_errno() が返すのもこの番号で、curl_error() で文言を取れます。ただしPHPで curl_multi_exec() を使った場合、curl_errno() はメッセージが読まれるまで0を返すので、curl_multi_info_read() の result を見てください。
コマンドの一覧とlibcurl-errorsは、4つの番号で食い違います。81 CURLE_AGAIN(ソケットの準備待ち)はlibcurlにだけあり、コマンドの一覧では欠番です。101 CURLE_ECH_REQUIRED はExit codes節には載っていませんが、マニュアルの --ech の項に「ECH関連のエラーの多くは101になる」とあり、コマンドでも返ります。逆に34と41はコマンドの一覧に残っていますが、libcurl-errorsでは「Obsolete error」として廃止扱いです。シェルの終了コードはコマンドのマニュアル、プログラムの戻り値はlibcurl-errorsで引き分けてください。
404でもcurlの終了コードが0になる理由とfailオプションの版差
curlは既定で、HTTPのエラー応答を「転送としては成功」と扱います。404の本文を受け取り終えた時点で、curlの仕事は完了しているためです。curl 8.23.0で404を返すURLを叩いた結果は次のとおりです。
$ curl -sS -o /dev/null -w 'http_code=%{http_code} exitcode=%{exitcode}\n' https://curl.se/nonexistent-page-xyz
http_code=404 exitcode=0
$ curl -sS --fail -o /dev/null https://curl.se/nonexistent-page-xyz; echo "exit=$?"
curl: (22) The requested URL returned error: 404
exit=22
--fail は400以上の応答で22を返し、本文を出力しません。エラー内容のJSONも捨てられるので、本文が欲しいときは7.76.0で追加された --fail-with-body を使います。公式マニュアルは --fail について「確実ではなく、特に認証が絡む401と407ではすり抜けることがある」と留保を付けています。認証付きのAPIを監視するなら、ステータスコードを直接比べるほうが確実です。
macOS同梱8.7.1でfail使用時に返る56(HTTP/2接続)
$ /usr/bin/curl -sS --fail -o /dev/null https://curl.se/nonexistent-page-xyz; echo "exit=$?"
curl: (56) The requested URL returned error: 404
exit=56
$ /usr/bin/curl -sS --fail --http1.1 -o /dev/null https://curl.se/nonexistent-page-xyz; echo "exit=$?"
curl: (22) The requested URL returned error: 404
exit=22
メッセージは同じ「The requested URL returned error: 404」のまま、番号だけが56になります。--http1.1 を付けると22に戻り、同じ8.7.1でも --fail-with-body では22でした。8.23.0ではHTTP/2でも22です。if [ $? -eq 22 ] と書いたスクリプトは、Macの開発機ではHTTPエラーを見逃します。
複数URL指定時の終了コードとfail-earlyの使い分け
1つのcurlコマンドに複数のURLを渡すと、終了コードは最後のURLの結果で決まります。接続できないURLの次に正常なURLを並べると、0が返りました(8.23.0・8.7.1とも)。--fail を付けて409を返すURLの後に200のURLを並べても0です。最初の失敗で止めたいときは --fail-early を付けます。同じ並びで --fail-early を付けると7が返りました。
retryの再試行条件と終了コードの関係
--retry が再試行する「一時的なエラー」は、タイムアウトと、HTTPの408・429・500・502・503・504・522・524です(–retryの公式説明)。522と524は2025年11月5日の8.17.0で追加されました(curlの変更履歴)。待ち時間は1秒から倍々に増え、上限は10分です。
見落としやすいのは、再試行と成否の判定が独立している点です。--retry は --fail が無くても503を再試行します。毎回503を返すローカルサーバーに --retry 2 で投げると、サーバー側では3回の受信を数えたうえで、終了コードは0でした。--fail を併用すると同じ3回の後に22を返します。また接続拒否(7)は既定では再試行の対象外で、--retry-connrefused を付けてはじめて3回試行しました。--retry-all-errors は、データの二重送信や二重受信といった意図しない結果を招くと公式が警告しています。使うなら冪等なGETに限ってください。
CIで使う判定スクリプト:終了コードと3桁の併用
#!/bin/sh
# HTTPの3桁とcurlの終了コードを両方見る
code=$(curl -sS -o /dev/null --connect-timeout 5 -m 30 --retry 2 -w '%{http_code}' "$1"); rc=$?
if [ "$rc" -ne 0 ]; then
echo "NG: curl終了コード $rc(HTTP $code)"; exit 1
fi
case "$code" in
2*) echo "OK: $code" ;;
*) echo "NG: HTTP $code"; exit 1 ;;
esac
成功の条件は「終了コードが0」かつ「3桁が2xx」の両方です。--fail を付けていないので、HTTPのエラーは終了コードではなく3桁で拾い、終了コードは通信と入出力の失敗だけを表します。終了コードを見ずに3桁だけで判定すると、ヘッダーの後で切れた転送を見逃します。Content-Length 1000に対して3バイトで切断するローカルサーバーでは http_code=200 exitcode=18 となり、3桁だけの判定では成功扱いでした。上のスクリプトはこれを「NG: curl終了コード 18(HTTP 200)」として止めます。なお、3桁はプロキシやロードバランサーが返したものの場合もあるので、502・504ならアプリケーションより手前を先に調べます。監視やデプロイ後のヘルスチェックをCI/CDに組み込む段階で判定の設計に迷うなら、DevOps・CI/CD導入支援で相談を受けています。
curlでHTTPステータスコードを確認する方法
# ステータスコードだけを出力(本文は捨てる)
$ curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/
# ステータス・終了コード・エラー文をまとめて出力(7.75.0以降)
$ curl -sS -o /dev/null -w 'http_code=%{http_code} exitcode=%{exitcode} errormsg=%{errormsg}\n' https://example.com/
# リダイレクトを追い、各ホップのステータス行を確認
$ curl -sSIL https://example.com/ | grep -i '^HTTP/'
-I はHEADリクエストなので、HEADを受け付けないサーバーでは405が返り、GETの結果と食い違います。判定には -o /dev/null とGETの組み合わせを使ってください。名前解決に失敗した場合は http_code=000 exitcode=6 errormsg=Could not resolve host: ... のように出力され、1行で原因の層が分かります。ブラウザで起きた通信をcurlで再現する手順はCopy as cURLの使い方|Chrome DevToolsでリクエストを再現する手順とcmd・bash・fetchの違いで扱っています。
curlで見かける主なHTTPステータスとcurl側の扱い
| コード | 意味 | failで22になるか | retryの対象か |
|---|---|---|---|
| 200・201・204 | 成功 | ならない | 対象外 |
| 301・302・307・308 | リダイレクト | ならない | 対象外(-Lで追跡) |
| 400・404・409 | リクエスト側の誤り | なる | 対象外 |
| 401・407 | 認証が必要 | すり抜けることがある | 対象外 |
| 408・429 | タイムアウト・回数超過 | なる | 対象 |
| 500・502・503・504 | サーバー側の失敗 | なる | 対象 |
| 522・524 | CDN(Cloudflare)の接続・応答タイムアウト | なる | 8.17.0以降は対象 |
429と503は Retry-After ヘッダーで待ち時間を指示されることがあり、curlは7.66.0以降その値で次の再試行の時期を決めます。サーバーが長い待ち時間を返すと処理が止まって見えるので、--retry-max-time で新しい再試行を始める期限を切っておきます。開始済みの転送は期限を越えても続くため、1回ごとの上限は -m で指定します。409(Conflict)はAPIで重複登録や更新の競合を表すことが多く、競合の原因を解消しないまま同じリクエストを再送しても、同じ409が返ります。--fail-with-body で本文の理由を読んでください。再試行の間隔とリトライ予算の設計はリトライパターンとは?指数バックオフ・ジッター・リトライ予算の実装判断を解説、503そのものの原因と復旧は503エラーとは?原因の切り分けと復旧・再発防止をサーバー実装目線で解説【2026年版】にまとめています。API側でエラー応答の本文をどう設計するかはAPIエラーレスポンス設計とは?コード体系・リトライ判断・漏洩防止の実装手順が詳しいです。
よくある質問
curlの終了コードに欠番があるのはなぜですか?
過去に定義され、使われなくなった番号を空けたまま残しているためです。curl 8.23.0では20・24・29・32・40・44・46・50・51・57・62・75・76・81の14件が欠番です。公式マニュアルは既存の番号の意味を変えないと明記しているので、古いスクリプトに書かれた番号が、curlの更新で別の意味に変わることはありません。
curlのステータスコードが000になるのはなぜですか?
%{http_code} の000は、有効なHTTPの応答コードを受け取れなかったことを示します。HTTPに000というコードがあるわけではなく、libcurlが応答コードを受信していないときの値0を3桁で表示したものです。実測では、名前解決の失敗で http_code=000 exitcode=6、接続拒否で exitcode=7、接続後に空のまま切られた場合で exitcode=52 になりました。000を見たら終了コードを確認して原因を切り分けてください。
「curl: (56) The requested URL returned error: 404」は何が起きていますか?
サーバーが404を返し、--fail がそれをエラーにした状態です。本来の番号は22ですが、macOS同梱のcurl 8.7.1でHTTP/2接続を使うと56と表示されます。ネットワークの受信エラーではないので、URLのパスや権限を確認してください。番号を22に揃えたい場合は --http1.1 を付けるか、Homebrewなどで新しいcurlを入れます。
HTTPステータスコードは全部で何種類ありますか?
IANAのHTTP Status Code Registryに登録されているのは64コードです(2025年9月15日更新時点)。このうち306と418は「(Unused)」の予約で使えず、104 Upload Resumption Supportedは2026年11月13日に失効予定の暫定登録です。一覧サイトによって件数が違うのは、これらを数えるかどうかの差です。
証明書エラー60は -k で回避してよいですか?
手元で一度だけ原因を確かめる目的なら使えますが、スクリプトや設定ファイルには残さないでください。-k はサーバー証明書の検証を丸ごと外すため、なりすましたサーバーにも接続します。自己署名証明書なら --cacert でその証明書を渡し、期限切れならサーバー側で証明書を更新するのが解決策です。