コマンドを紹介する前に、ひとつだけお断りしておきます。私たちは Geonode というプロキシ販売業者です。そのため、率直に申し上げて、curl のタイムアウトがプロキシの問題であることはほぼありません。 単に遅いサーバー、ダウンしているホスト、あるいはパケットを黙って破棄するファイアウォールに対してリクエストがタイムアウトする場合、当社を経由しても請求額以外は何も変わりません。 プロキシが役立つのは、リクエストの「発信元」が問題となる場合です。例えば、地域制限のあるコンテンツ、IPアドレスに基づくレート制限、ブロックされたIPアドレスなどが該当します。プロキシは、遅いサーバーを速くするものではありません。まずはタイムアウトを適切に設定してください。そうすれば、他に何も必要なかったことに気づくかもしれません。
その通りです。ここで、多くの人が痛い目に遭って初めて気づく事実があります。curlには、デフォルトの全体的なタイムアウト設定がありません。デフォルトの接続タイムアウトは300秒ですが、一度接続が確立されると、curlは完全に到着することのないレスポンスを、喜んで無期限に待ち続けてしまいます。 cronで実行されるシェルスクリプトの場合、これは決して完了しないジョブとなり、誰もクリアしないロックファイルが残ることになります。
重要な2つのタイムアウト
それ以外は、これら2つの詳細な設定に過ぎません。
--max-time
(短縮形:-m
)は、操作全体の上限時間を指定します。curlマニュアルには次のように記載されています。「転送操作に許容される最大時間(秒単位)」。接続、ハンドシェイク、リクエスト、レスポンス、そのすべてが含まれます。 制限時間に達すると、curl は処理を中止し、コード 28 を返して終了します。
curl -m 10 https://example.com
合計 10 秒経過すると、curl はどの段階に達していても処理を中止します。
--connect-timeout
はセットアップフェーズのみを制限します。マニュアルには、その範囲について次のように明確に記述されています: 「接続フェーズは、DNS 検索および要求された TCP、TLS、または QUIC ハンドシェイクが完了した時点で完了したとみなされます。」接続が確立されると、このオプションは適用されなくなります。
curl --connect-timeout 3 https://example.com
ホスト名の解決とハンドシェイクの完了に 3 秒かかります。その後、curl は転送が完了するまで待機します。
実際には、両方のオプションが必要であり、それぞれが異なる問題を解決します:
| オプション | 対象範囲 | 典型的な値 | 防止対象 |
|---|
--connect-timeout | | | |
| DNS および TCP/TLS/QUIC ハンドシェイク | 3~10秒 | ホストのダウン、パケットの損失、DNS 障害 |
| --max-time
| 操作全体 | 10~60秒(ワークロードに依存) | 応答の遅延、転送の停滞、無限のストリーム |
まとめ:
curl --connect-timeout 5 -m 30 https://example.com
接続確立に5秒、全体で30秒かかります。ホストに到達できない場合、30秒ではなく5秒で失敗します。これは、1,000件のURLリストを順次処理する際には非常に重要な点です。
当て推量ではない値の選び方
一般的なアプローチは、丸い数字を選んで、何かが失敗するたびにその値を上方に調整することです。そうしていると、タイムアウトがもはや何の役にも立たなくなるほど大きな値に収束してしまいます。
より良い方法には、コマンドを1つ追加する必要があります。curl を使えば、実際に時間がどこに費やされたかを報告できます:
curl -o /dev/null -s -w "dns: %{time_namelookup}\nconnect: %{time_connect}\ntls: %{time_appconnect}\nttfb: %{time_starttransfer}\ntotal: %{time_total}\n" https://example.com
実際のターゲットに対してこれを20~30回実行すれば、推測ではなく分布が得られます。その後:
time_appconnect から --connect-timeout を設定します。 p95 の値をとり、それを大まかに2倍にします。接続時間はネットワークの往復時間に大きく左右され、比較的安定しています。通常より2倍の時間がかかっている場合は、単に遅いのではなく、何かが実際に問題を起こしていることになります。
time_total から --max-time を設定します。 ここでは、乗数をより大きめに設定する必要があります(p95の3~5倍)。これは、合計時間がレスポンスサイズとサーバーの負荷に依存しており、どちらも当然変動するためです。--max-time を厳しく設定しすぎると、普段通りの「調子の悪い日」でも失敗してしまう不安定なスクリプトになってしまいます。
ワークロードに応じた2つの調整点があります。大容量のファイルをダウンロードする場合、--max-timeは全く不適切なツールです。正当なダウンロードでは、いかなる妥当な固定制限値をも超える可能性があるため、代わりに以下の速度ベースのオプションを使用してください。 また、サーバー側で実際の処理を行うAPIを呼び出す場合は、そのAPI自体のタイムアウト時間を確認し、自身のタイムアウトをそれよりわずかに長く設定してください。12秒で応答が返ってくるサービスに対して10秒でタイムアウトすると、処理コストを支払ったにもかかわらず結果を破棄することになってしまいます。
秒未満の単位とミリ秒単位の精度
どちらのオプションも小数を入力でき、ロケールに関係なく小数点としてドットが使用されます。これは curl 7.32.0 以降でサポートされています。
curl --connect-timeout 0.5 -m 2.5 https://example.com
接続に0.5秒、合計で2.5秒かかります。ヘルスチェックや、障害ごとに1秒ずつ待機時間が積み重なるようなループ処理に役立ちます。
知っておくべき注意点として、値が大きくなるにつれて精度が低下します。curlのドキュメントには、指定されたタイムアウトの小数精度が高くなるほど、実際のタイムアウトの精度は低下すると記載されています。--max-time 30.001と記述しても、--max-time 30と記述しても実質的な違いはありません。小数は1秒未満の値に用いてください。数秒を超える場合は、整数を使用してください。
関連する --expect100-timeout も小数を受け付けます。これは、curl がリクエスト本体を送信する前に 100 Continue を待機する時間を制御するもので、デフォルトは 1 秒です。100 Continue を送信しないサーバーに大きなリクエスト本体を POST する場合、リクエストごとにその 1 秒を無駄にしています。この時間を短縮するか、-H "Expect:" を使用してこの期待を無効にしてください。
タイムアウトしない停滞した転送を捕捉する
ここに問題があります。数秒ごとに1バイトずつ送信される転送は、アイドル状態になることがなく、そのため接続レベルのタイムアウトは発生しません。また、--max-time
が正当な大容量ダウンロードに対応できるほど十分に高い値に設定されている場合、こちらも発生しません。 リクエストはただじわじわと進むだけです。
``--speed-limit`
および ``--speed-time
オプションがこの問題に対処します。マニュアルには次のように記載されています。「--speed-time`
の期間中、ダウンロード速度が--speed-limit
バイト/秒を下回った場合、転送は中止されます。」
curl --speed-limit 1000 --speed-time 30 -O https://example.com/large-file.zip
スループットが 30 秒間連続して 1 秒あたり 1000 バイトを下回ると、curl は転送を中止します(この場合も終了コード 28 となります)。正常な速度で 6 時間続くダウンロードには影響がありません。一方、停滞しているダウンロードは 30 秒で強制終了されます。
これは、サイズが予測できないあらゆるケースに対して適切なタイムアウト設定であり、以下の設定とよく組み合わされます:
curl --connect-timeout 5 --speed-limit 1000 --speed-time 30 -O https://example.com/large-file.zip
応答のないホストでは早期に失敗し、全体的な上限は設けられていませんが、全体を通じて停滞を検知する仕組みが備わっています。特にファイルのダウンロードに関しては、このパターンをすべてのスクリプトに組み込むべきです。関連するオプションについては、curl によるファイルのダウンロードに関するガイドを参照してください。
デフォルトでは、タイムアウトと再試行の相互作用がうまく機能しない
これが多くの人が見落としがちな点です。
--retry N
を指定すると、curlは一時的なエラーに対して最大N回まで再試行を行います。見落としやすいのは、--max-time
がコマンド全体ではなく各試行に適用されること、そしてcurlが試行の合間に、1秒から始まり倍増していく指数関数的なバックオフで待機することです。
したがって、次のような場合:
curl -m 10 --retry 5 https://example.com
は、実際には1分以上かかる可能性があります。最大10秒の試行が5回行われ、さらに1秒、2秒、4秒、8秒、16秒のバックオフ待ち時間が加わるためです。最大10秒と想定してこのコードを書いた場合、実際には6倍の時間がかかってしまうことになります。
--retry-max-time
が解決策です。これにより、再試行に費やす合計時間が制限されます:
curl -m 10 --retry 5 --retry-max-time 40 https://example.com
これで、40秒が経過するとcurlは新しい試行を開始しなくなります。表現に注意してください――制限時間を過ぎても新しい再試行は開始されませんが、すでに実行中の試行は、その試行固有の--max-time
まで実行されます。真の最悪ケースは、--retry-max-time
に、--max-time
を1つ加えたものです。
--retry-delay
は、指数関数的なバックオフを固定の待機時間に置き換えるため、総実行時間が予測可能になります:
curl -m 10 --retry 3 --retry-delay 2 --retry-max-time 40 https://example.com
知っておくべきもう1つのフラグがあります。デフォルトでは、--retry
はごく限られた一時的な状況でのみ発生します。--retry-all-errors
を指定すると、その範囲が大幅に広がります。マニュアルでは、これを「FTPの4xxおよび5xxレスポンスコードを含む、すべての一時的なエラー」の再試行と説明しています。 これは、ネットワークが不安定なスクリプトでは非常に有用ですが、非冪等な POST の前では非常に危険です。追加する前によく考えてください。
プロキシを経由する場合のタイムアウト
-x
を追加すると、タイミングの挙動が変わります。これは、接続が 1 つではなく 2 つ(ユーザーからプロキシへ、そしてプロキシからターゲットへ)になるためです。
curl -x http://user:pass@proxy.example.com:9000 --connect-timeout 10 -m 45 https://example.com
直接接続の場合とは、3 つの点で挙動が異なります。
**--connect-timeout
は、プロキシからターゲットへのホップではなく、ユーザーからプロキシへのホップを測定します。** HTTPSの場合、curlはCONNECT
を発行し、プロキシがそれ以降の接続を確立します。その所要時間は--max-time
にカウントされ、--connect-timeout
にはカウントされません。したがって、接続タイムアウトを短く設定しても、接続を即座に受け付けたものの、ターゲットに到達するまでに20秒かかるようなプロキシに対しては、十分な保護にはなりません。
一般家庭向けプロキシは、当然ながら速度が遅くなります。 トラフィックは実際の一般ユーザー接続を経由して送信されるため、数百ミリ秒程度の遅延は不具合ではなく正常な動作です。直接接続用に調整されたタイムアウト値では、プロキシが故障しているように見えるエラーが発生しますが、実際には物理的な制約によるものです。上記の-w
コマンドを使用してプロキシ経由で測定を行い、その測定結果に基づいて値を設定してください。直接接続時の数値に基づいて設定してはいけません。
失敗の原因は一概には言えません。 プロキシ経由で終了コード 28 が発生した場合、プロキシが遅い、ターゲットが遅い、あるいはターゲットが意図的にリクエストを遅延させている、のいずれかの可能性があります。これらを区別するには、各コンポーネントを個別にテストする必要がありますが、これは非常に広範なテーマであるため、プロキシのテストに関する別のガイドを作成しました。
プロキシ経由のリクエストにおける実用的なパターン:--connect-timeout
を十分に長く設定(10秒)、--max-time
は「期待値」ではなく「測定結果」に基づいて設定し、--retry-all-errors
は使用しない。プロキシ経由の場合、5xxエラーは「一時的な不具合」ではなく「ターゲットが接続を拒否している」ことを意味することが多く、再試行すると事態を悪化させるためです。
終了コードの読み方
curl の終了コードからは、どの段階で失敗したかがわかります。これは、多くのスクリプトがわざわざ利用しようとはしないほど詳細な情報です。
| コード | 名称 | 意味 |
|---|
| 6 | CURLE_COULDNT_RESOLVE_HOST | DNS の失敗 — タイムアウトは発生していない |
| 7 | CURLE_COULDNT_CONNECT | 「ホストまたはプロキシへの connect() に失敗しました」 |
| 28 | CURLE_OPERATION_TIMEDOUT | 「指定されたタイムアウト期間に達しました」 |
| 56 | CURLE_RECV_ERROR | ネットワークデータの受信に失敗 — 転送中に接続が切断されました |
説明は libcurl エラーリファレンス からのものです。
7と28の違いは実用上重要です。コード7は、接続が能動的に拒否されたことを意味します — 何かが応答し、即座に「ノー」と返答した状態です。コード28は、時間内に応答がなかったことを意味します。前者は通常、ポートの誤りやサービスが終了していることを示し、後者はパケットの損失、サイレントフィルタリングを行うファイアウォール、あるいは実際に過負荷状態にあるホストを示します。 コード28の場合は再試行が妥当ですが、コード7の場合は通常、再試行しても意味がありません。
スクリプトでの例:
curl --connect-timeout 5 -m 30 -sS https://example.com > out.txt
case $? in
0) echo "ok" ;;
6) echo "dns failure" ;;
7) echo "connection refused" ;;
28) echo "timed out" ;;
*) echo "other failure" ;;
esac
なお、3つのタイムアウトメカニズム(--max-time、--connect-timeout、および速度制限のペア)はすべてコード28を返します。終了コードは制限に達したことを示すだけで、どの制限に達したかは示しません。詳細を知る必要がある場合は、-w "%{time_total}" を使用し、設定値と比較してください。
タイムアウトを設定すべきでない場合
一般的なアドバイスとは裏腹に、タイムアウトが適切な手段ではないケースもあります。
対話型のダウンロード。 ターミナルで curl コマンドを入力して大容量のファイルを取得する場合、タイムアウトとなるのはユーザー自身です。進行状況バーを確認でき、Ctrl-C を押すこともできます。 ここで -m を追加しても、ダウンロードが 90% のところで中断されるという煩わしさを生むだけです。
長時間のストリーム。 サーバー送信イベント、ログのテール表示、設計上接続が維持されるチャンク化されたレスポンスなど――--max-time は、まさに最悪のタイミングでこれらを終了させてしまいます。停止検知が必要な場合は、--speed-limit や --speed-time を使用するか、あるいは何も設定しないようにしてください。
すでに外部の制限で囲まれているもの。 curl が timeout(1) や、RuntimeMaxSec を設定した systemd ユニット、あるいは独自の予算を持つ CI ステップの下で実行されている場合、2 層目の制限を追加しても、同期を取るための数値がもう 1 つ増えるだけになります。確認したいエラーメッセージを生成する層を選び、そこで設定を行ってください。
応答の遅いターゲットへの対処法として。 タイムアウトを設定しても、遅いリクエストが早く失敗するようになるだけで、成功するわけではありません。もし本当の問題が「サーバーの応答に40秒かかる」ことであるなら、解決策はキャッシュ、 ページネーション、別のエンドポイント、あるいはサーバー管理担当者との協議です。ここで冒頭の免責事項を繰り返しておきます。これは、プロキシを購入して解決しようとする最も一般的な問題であり、プロキシでは全く解決できない数少ない問題の一つだからです。 当社の料金体系は、2026年9月時点で、一般家庭向けトラフィックが1GBあたり0.79ドル、データセンター向けが1GBあたり0.14ドルからとなっていますが、いずれの場合も、応答が遅いオリジンサーバーの速度を向上させることはできません。
よくある質問
curl のデフォルトのタイムアウトはどれくらいですか?
全体的なデフォルトのタイムアウトはありません。curl は接続が確立されると、応答が無限に待ち続けます。ただし、接続フェーズには 300 秒というデフォルト値があります。これが、スクリプトにおいて -m が重要となる理由です。これを指定しないと、リクエストがハングアップした際にスクリプトもハングアップしてしまいます。
--max-time と --connect-timeout の違いは何ですか?
--connect-timeout は DNS 解決と TCP/TLS/QUIC ハンドシェイクのみを対象とし、接続が確立されると適用されなくなります。--max-time は、レスポンスの転送を含め、開始から終了までの操作全体を対象とします。 両方を併用してください。接続タイムアウトを短く設定して応答のないホストを素早く見切り、最大時間を長く設定して全体の上限を確保します。
curl コマンドの実行時間が、設定したタイムアウトよりも長くなるのはなぜですか?
ほとんどの場合、再試行が原因です。--max-timeは1回の試行ごとに適用され、--retryは追加の試行回数と、試行間の指数関数的バックオフを追加します。--retry-max-timeを追加して合計回数を制限し、最悪の場合、その値にさらに1回分の--max-timeが加算されることを覚えておいてください。
curlのタイムアウトをミリ秒単位で設定するにはどうすればよいですか?
小数点付きの値を使用します。--connect-timeout 0.25 は 250 ミリ秒です。--max-time と --connect-timeout の両方で、curl 7.32.0 以降、ドット区切りの小数点がサポートされています。精度は数秒未満の場合に最適です。値が大きくなるほど、小数部分の精度は低下します。
タイムアウトが発生した場合、curl はどの終了コードを返しますか?
28(CURLE_OPERATION_TIMEDOUT)。3つのタイムアウトメカニズムすべてがこのコードを返すため、このコードは制限に達したことを示しますが、どの制限に達したかは特定できません。7(接続拒否)や6(DNS 失敗)と比較してください。これらは異なる意味を持ち、通常は再試行すべきではありません。
大容量ファイルを中断せずに、遅いダウンロードをタイムアウトさせるにはどうすればよいですか?
--max-time の代わりに、--speed-limit および --speed-time を使用してください。これらは、スループットが一定期間しきい値を下回り続けた場合にのみ中止されるため、正常な数時間にわたるダウンロードには影響を与えず、停滞しているダウンロードは速やかに終了します。
プロキシ経由の場合、タイムアウトの動作は異なりますか?
はい。--connect-timeoutはプロキシへの接続のみを測定します。プロキシからターゲットへの接続は、--max-timeの対象となります。また、一般家庭向けプロキシは実際の遅延も生じさせるため、直接接続用に調整された値では誤った失敗が発生します。プロキシ経由で測定し、その結果に基づいて値を設定してください。
DNS解決のみにタイムアウトを設定できますか?
個別のオプションとしては設定できません。DNSは--connect-timeoutに含まれています。DNSのみを個別に制限する必要がある場合は、別途解決を行い、その結果を--resolveで渡してください。これにより、curl自身のルックアップが完全にスキップされます。
まとめ
ほぼすべてのケースに対応できる2つのオプションがあります。ホストに到達できない場合に素早く失敗を返す「--connect-timeout」と、全体的な上限を設定する「--max-time」です。すべてのスクリプトで、常に両方を設定してください。デフォルトの「無期限に待機」は、対話型ツールでは妥当な選択ですが、自動化においては最悪の選択です。
多くの人がつまずく2つの点については、改めて強調しておく価値があります。--max-timeは1回の試行ごとに適用されるため、--retryを使用する場合は、必ず--retry-max-timeも併せて設定する必要があります。そうしないと、10秒のコマンドが1分間のコマンドになってしまいます。また、予測不可能なサイズの転送に対して固定の時間制限を設定するのは不適切です。--speed-limitを--speed-timeと併用すれば、正当な長時間ダウンロードを妨げることなく、処理が停滞していることを検出する仕組みが得られます。
値は、なんとなく妥当に思える丸数字ではなく、測定結果に基づいて設定してください。実際のターゲットに対して curl -w を実行すれば分布が得られ、その分布から導き出されたタイムアウト設定であれば、ネットワークが単に調子の悪い午後を過ごしているだけの場合ではなく、真に問題が発生したときにのみ失敗します。