私たちの見解を明言します。私たちはGeonodeであり、プロキシを販売しています。したがって、プロキシを介してファイルをダウンロードする際に正直に指摘すべき点は、コストはすべて帯域幅に依存しており、ファイルサイズが大きいということです。 1GBあたり0.79ドルの従量制の家庭用回線を通じて4GBのファイルをダウンロードする場合、1ファイルあたり約3.16ドルのコストがかかりますが、1GBあたり0.14ドルのデータセンター回線を利用すれば0.56ドルで済みます。 ダウンロードが一般家庭の接続から行われたように見える必要がない場合(ほとんどのダウンロードはそうではありません)、一般家庭向け帯域幅を利用するのはお金を無駄にするようなものです。価格は当社の料金ページに基づくもので、2026年9月時点で確認済みです。さらに良いのは、直接ダウンロードできる場合はそうして、費用を一切かけないことです。
その点を踏まえた上で、正しい方法をご紹介します。
出力ファイルに名前を付ける3つの方法
デフォルトでは、curlは標準出力に書き出します。そのため、単純に「curl https://example.com/file.zip
」と実行すると、ターミナルがバイナリデータで埋め尽くされてしまいます。これを変更するには、3つのオプションがあります。
**-o filename
** は、指定した名前でファイルを出力します:
curl -o archive.zip https://example.com/download?id=1234
ファイル名を指定したい場合、特にURLに適切なファイル名が含まれていない場合は、このオプションを使用してください。
**-O
** はリモート名をそのまま使用します。curl マニュアル では次のように説明されています。「リモートファイルと同じ名前のファイルに出力を書き込みます。リモートファイルのうちファイル名部分のみが使用され、パスは切り捨てられます。」
curl -O https://example.com/files/report.pdf
# saves as report.pdf
この説明文にある注意点に留意してください。パスは切り捨てられるため、異なるディレクトリにある同じベース名を持つ2つのファイルは互いに上書きされてしまいます。また、URLがスラッシュで終わっている場合やファイル名部分がない場合、-O
は失敗します。
**-J
** は、代わりにサーバーの Content-Disposition
ヘッダーからファイル名を取得します。これは、-O
に対して「URL からファイル名を抽出するのではなく、サーバーが指定した Content-Disposition ファイル名を使用する」よう指示するものとして文書化されています。URL が不透明な識別子であるダウンロードエンドポイントで有用です。
また、このオプションにはマニュアルの中で最も強い警告が記載されており、ここに引用しておく価値があります:
警告:Content-Dispositionのファイル名は検証もサニタイズもされていません。パストラバーサルシーケンス(..)が含まれている可能性があります。信頼できない場所に、あるいはこのオプションが有効になっている状態で、何の前触れもなく出力を保存しないでください。
つまり、-OJ
を使用すると、リモートサーバーがあなたのファイルシステム上のパスを選択できるようになります。自分が管理しているソースであれば問題ありません。しかし、任意の URL からデータを取得するスクリプトでは、これは脆弱性となります。信頼できないソースからサーバーが提供する名前が必要な場合は、ヘッダーを取得し、名前を自分でサニタイズした上で、-o
を使用してください。
リダイレクト:-Lがほぼ常に必要な理由
curlはデフォルトではリダイレクトを追跡しません。-Lについては、「HTTPリダイレクトを追跡し、最初に指定されたメソッドでリクエストを繰り返す」と記載されています。
これが、ダウンロードしたファイルが、期待したコンテンツではなくHTMLを含む小さなファイルになってしまう最も一般的な原因です。 ダウンロード用URLは、CDN、署名付きURL、ミラーサイト、地域ごとのエンドポイントなどへ絶えずリダイレクトされます。-Lを指定しないと、リダイレクト先のページが保存されてしまいます。
curl -L -O https://example.com/latest.tar.gz
これに関連する2つのポイントがあります。
-oでは順序が重要です。 複数のURLを含むリダイレクトを追跡する場合、curlは-o引数をURLの位置に基づいて照合します。 URLが1つの場合は問題になりませんが、複数のURLを含むコマンドを書く人にとっては予想外の結果となることがあります。
リダイレクトの回数を制限してください。 --max-redirs は、curlが追跡するリダイレクトの回数を制限します。デフォルトの制限は緩く設定されていますが、制限がない状態でのリダイレクトループは、cronジョブにおいて望ましくない障害モードとなります。
気づかれない失敗:エラーページをファイルとして保存する
これが、この記事で最も重要なポイントです。
デフォルトでは、curl は HTTP エラー応答を転送成功として扱います。404 エラーには本文があり、curl はその本文をダウンロードし、終了値 0 を返します。 これで、「Not Found」と表示されたHTMLページを含むinstaller.dmg
という名前のファイルが作成され、スクリプトはすべてが正常に動作したかのように処理を進めてしまいます。
--fail
を指定することでこれを修正できます。マニュアルには、「レスポンス本文が出力されない、ステータスコード400以上のHTTPレスポンスに対しては、エラーコード22で失敗を返す」と記載されています。
curl --fail -L -O https://example.com/installer.dmg
これで、404 エラーが発生すると終了コード 22 が返され、ファイルは生成されなくなります。スクリプトでこれを確認できます。
2 つの改良点:
**--fail-with-body
** も同じ動作をしますが、レスポンス本文を保持します。これは、API がログに記録したい有用な JSON エラーメッセージを返す場合に役立ちます。
**--fail
は完璧ではありません。** エラーページを表示しながら 200 を返すサーバー(一部に存在します)を捕捉できません。重要な処理を行う場合は、結果を検証してください。ファイルサイズが妥当かどうかを確認したり、公開されている場合はチェックサムを確認したり、マジックバイトを確認したりしてください:
curl --fail -L -o pkg.tar.gz "$URL" || exit 1
file pkg.tar.gz | grep -q gzip || { echo "not a gzip archive"; exit 1; }
したがって、スクリプト用の標準的なダウンロード行は次のようになります:
curl --fail --silent --show-error --location -o output.bin "$URL"
-sS
は、エラーメッセージを保持しつつ進行状況メーターを非表示にします。これは自動化において望ましい動作です。対話的な使用の場合は、-#
を使用すると、デフォルトのメーターではなくシンプルな進行状況バーが表示されます。
中断されたダウンロードの再開
大容量のファイルや不安定な接続環境では、この機能は不可欠です。
-C - については、次のように説明されています。「指定されたバイトオフセットから、以前の転送を再開します。'-C -' を使用すると、curl に転送の再開場所や方法を自動的に判断させることができます。」
curl -C - -L -O https://example.com/large-file.iso
curlはローカルファイルのサイズを確認し、Rangeヘッダーを使用して残りの部分のみをリクエストします。再試行と組み合わせることで、長時間のダウンロードも問題なく完了させることができます:
curl --fail -L -C - --retry 5 --retry-delay 5 -O https://example.com/large-file.iso
3つの注意点があります。
サーバーが範囲指定リクエストに対応している必要があります。 対応していない場合、curlは再開できず、最初からやり直すか、失敗します。レスポンスヘッダーにAccept-Ranges: bytesが含まれているか確認してください。
破損したファイルの再開を行うと、さらに長い破損ファイルができてしまいます。 -C - はローカルのバイトデータを信頼します。部分的なファイルが書き込み途中で切り捨てられたり、ソースが変更されたりした場合、結果は黙って間違ったものになります。ソース側がチェックサムを公開している場合は、それに対して検証を行ってください。
ソースが変更されると、再開は無効になります。 再開の試行の間にファイルが置き換えられた場合、エラーは発生しませんが、2つのバージョンが混在した状態のファイルが得られます。
多数のファイルを並列でダウンロードする
curl 7.66 以降、-Z
は「順次ではなく並列で転送」を行うようになり、--parallel-max
で「並列で実行する転送の最大数」を設定できます。
curl -Z --parallel-max 8 --fail -L \
-O https://example.com/a.zip \
-O https://example.com/b.zip \
-O https://example.com/c.zip
URL が記載されたファイル(
xargs -a urls.txt curl -Z --parallel-max 8 --fail -L --remote-name-all
)から
--remote-name-all
を実行すると、各 URL に -O
の動作が適用されるため、フラグを繰り返し指定する手間が省けます。
並列数は慎重に選択してください。ある点を超えると、多ければ多いほど良いというわけではありません。サーバーのレート制限や自身の接続の飽和が生じ、プラトーを超えるとスループットが向上するどころかエラーが発生するようになります。十分にリソースが確保されたサーバーであれば、8 が妥当な初期値です。リソースの少ないホストに対しては、4 以下に抑える方がホストへの負荷が少なく、全体として高速になる場合が多いです。 ここでの挙動は、並行処理と並列処理の比較で説明したスループット曲線と同じです。つまり、上昇し、頭打ちになり、その後下降します。
ディレクトリ、タイムスタンプ、レート制限
ユーザーが一般的に作成するラッパースクリプトを不要にする3つのオプション。
**--output-dir
** — 「ファイルを保存するディレクトリを指定します。このオプションは、--remote-name または --output オプションと併用できます。」これにより、前後に cd
を記述する必要がなくなります。
**--create-dirs
** — -o
オプションを指定すると、「curl が必要なローカルディレクトリ階層を作成します。作成されたディレクトリは、Unix システム上でモード 0750 になります。」モードに注意してください:0750 であり、0755 ではありません。他のユーザーやサービスがこれらのディレクトリを読み取る必要がある場合、この設定は思わぬ結果をもたらす可能性があります。
curl --fail -L --create-dirs -o data/2026/09/report.pdf https://example.com/report.pdf
**--remote-time
** — 「システムのファイルの変更日時を、リモートファイルのタイムスタンプと一致するように設定します」。後続のツールが最新の状態を判断できるようになるため、ミラーリングには非常に有用です。
**--limit-rate
** — 「転送速度を指定されたレートに制限します」。k
、M
、G
の接尾辞を受け付けます:
curl --limit-rate 2M -L -O https://example.com/large.iso
もっと活用されるべき機能です。アップリンクを飽和させると、ネットワーク上の他のすべての通信が利用できなくなってしまいます。また、共有回線や通信量制限のある回線では、転送速度の上限設定は基本的なマナーです。さらに、サーバーから不正利用とみなされるリスクも低減できます。
実用的な設定をまとめてみると:
curl --fail --location --continue-at - --retry 5 \
--remote-time --create-dirs \
--output downloads/archive.tar.gz \
"$URL"
プロキシ経由でのダウンロード
-x
を追加すれば、上記の内容はすべてそのまま適用されますが、3つの追加事項があります。
curl -x http://user:pass@proxy.example.com:9000 \
--fail -L -O https://example.com/file.zip
タイムアウトの設定を見直す必要があります。 --max-time
は、サイズが予測できないダウンロードには不適切な手法です。なぜなら、正当な大容量転送の場合、どのような固定制限値も超えてしまうからです。 代わりに、速度ベースのストール検出機能を使用してください:
curl -x "$PROXY" --fail -L \
--connect-timeout 10 --speed-limit 1000 --speed-time 30 \
-O https://example.com/large.iso
これにより、スループットが 30 秒間 1 秒あたり 1000 バイトを下回り続けた場合にダウンロードが中止されますが、速度は遅くても進行している数時間にわたるダウンロードは中断されません。タイムアウトオプションの全容については、curl でのタイムアウト設定 で解説しています。
再開時の挙動はローテーションと相互に影響します。 プロキシが接続ごとに出口アドレスをローテーションする場合、再開された転送は元のアドレスとは異なるアドレスから届きます。これを受け入れるサーバーもあれば、別のミラーサイトを提供するサーバー、あるいは範囲指定リクエストを拒否するサーバーもあります。長時間のダウンロードでは、同じ出口アドレスを維持するセッションを使用してください。
帯域幅の課金は、頭の中では双方向で計算されますが、実際には片方向のみです。 プロキシを通過するすべてのバイトに対して課金されます。これに--retry
を組み合わせ、大容量ファイルの90%の時点で転送が失敗した場合、計算結果はすぐに厄介なものになってしまいます。プロキシ経由のダウンロードでは常に-C -
を使用し、再試行時に最初からやり直すのではなく、中断した箇所から再開されるようにしてください。
実際にダウンロードした内容を確認する
終了コードが0の場合は、転送が完了したことを意味します。しかし、正しいデータが受信されたことを意味するわけではありません。実行、インストール、またはアーカイブを行う場合は、このギャップを埋める必要があります。
ファイルサイズが妥当かどうかを確認してください。 最も手軽な確認方法であり、データの切り捨て、エラーページ、空のレスポンスを検出できます:
SIZE=$(stat -c%s pkg.tar.gz 2>/dev/null || stat -f%z pkg.tar.gz)
[ "$SIZE" -gt 100000 ] || { echo "suspiciously small: $SIZE bytes"; exit 1; }
ファイルの種類を確認してください。 .tar.gz
という拡張子で保存された HTML エラーページは、ページが十分に大きい場合、サイズチェックでは検出されませんが、file
では明らかに検出されます:
file pkg.tar.gz | grep -q 'gzip compressed' || exit 1
公開されているチェックサムを確認してください。 ソース側がチェックサムを公開している場合、これは形式ではなく内容そのものを検証する唯一の手段です:
curl --fail -sL -O https://example.com/pkg.tar.gz
curl --fail -sL -O https://example.com/pkg.tar.gz.sha256
sha256sum -c pkg.tar.gz.sha256 || exit 1
よく誤解される点ですので、この制限に注意してください。同じ接続を介して同じサーバーからチェックサムを取得することは、データの破損や切り捨てからは保護してくれますが、ソースが侵害されていることからは保護しません。 サーバーが不正なファイルを提供している場合、それに対応する不正なチェックサムも提供されます。GPGによる署名検証は、この問題に対処するものであり、特権で実行されるものについては、この追加の手順を踏む価値があります。
**可能であれば、Content-Length
と比較してください。** 転送が指定された長さより前に終了した場合、curl はエラー 18 を返して非ゼロで終了します。これにより、ある種の切り捨ては自動的に検出されますが、これはサーバーが長さを通知した場合に限られ、チャンク化されたレスポンスでは長さが通知されないため、この方法は機能しません。
プロキシ経由のダウンロードでは、検証を「少なめ」ではなく「多め」に行うこと。 ホップが増えることは、転送が途中で途切れる可能性や、フィルタリングを行う中間サーバーによって改変される可能性が高まることを意味します。上記の検証には数ミリ秒しかかかりませんが、混乱を招くバグ報告のカテゴリー全体を排除することができます。
curl が不適切なツールである場合
curl は URL の取得には非常に優れています。しかし、それに付随するいくつかの作業には、より適したツールがあります。
再帰的なダウンロードとミラーリング。 curlは指定されたURLを取得するだけです。自動巡回(クロール)は行いません。ディレクトリやサイトのミラーリングには、wget -rや専用のミラーリングツールが適しています。この比較については、curl vs wgetで詳しく解説しています。
信頼性の低い接続での超大容量ファイル。 専用に設計されたダウンロードマネージャーは、curl よりもセグメント化された転送や積極的な再開処理をうまく処理します。-C - や再試行機能を組み合わせた curl でも十分ですが、専用ツールの方が優れています。
Torrent、rsync、S3 など。 これらはそれぞれ独自のプロトコルを持ち、整合性、重複排除、権限をネイティブに処理する専用クライアントが存在します。aws s3 cp は、単に追加の手順を加えた curl ではありません。
パッケージマネージャーが存在する場合。 ソフトウェアのインストールに curl | sh を使用するのは便利ですが、検証なしにリモートサーバーにローカルマシン上で任意の実行権限を与えてしまうことになります。 パッケージマネージャーが存在する場合は、それを利用してください。
同じリソースの繰り返しダウンロード。 変更の有無を確認するために定期的にファイルを取得する場合、-z や If-None-Match を使った条件付きリクエストにより、変更のないコンテンツの再ダウンロードを回避できます。高速なダウンロードよりもキャッシュの方が優れています。
よくある質問
curl でファイルをダウンロードするにはどうすればよいですか?
curl -O https://example.com/file.zip を実行するとリモート名で保存され、-o name を実行するとファイル名を指定できます。実際には、--fail および -L を追加してください。これらを指定しないと、curl はリダイレクトを追跡せず、エラーページをリクエストしたファイルであるかのように保存してしまいます。
なぜ curl は空のファイルや HTML ファイルをダウンロードしてしまうのですか?
ほとんどの場合、リダイレクトを追跡しなかったためです(-L を追加してください)。あるいは、HTTP エラーがファイルとして保存されてしまったためです。--fail を指定すればこれを防げます。file downloaded.zip を実行するか、テキストエディタで開いて内容を確認してください。HTML のエラーページは一目でわかります。
curl で中断したダウンロードを再開するにはどうすればよいですか?
curl -C - -O <url> を実行します。curl はローカルファイルのサイズを確認し、範囲指定リクエストを通じて残りの部分を要求します。サーバーは範囲指定をサポートしている必要があります(Accept-Ranges: bytes を参照)。また、破損した部分ファイルを再開すると、警告なしにさらに長い破損ファイルが生成されることに注意してください。
curlで複数のファイルをダウンロードするにはどうすればよいですか?
並列転送には -Z を使用し、--parallel-max で同時実行数を制限し、--remote-name-all を指定してすべてのURLでリモート名による処理が行われるようにします。ファイル内のリストについては、xargs を通じてパイプ処理します。並列処理は控えめにしてください。サーバーがレート制限を開始すると、スループットは頭打ちになり、その後低下します。
curl でダウンロード速度を制限するにはどうすればよいですか?
--limit-rate 2M は転送速度を 2 メガバイト/秒に制限し、このオプションでは k、M、G のサフィックスが使用可能です。共有接続や小規模なサーバーに対しては、ネットワークの他の部分を正常に利用できるようにし、不正利用とみなされるのを防ぐためにも、このオプションを使用することをお勧めします。
curl の -O と -o の違いは何ですか?
-O は、URL からファイル名を取得し、パスは無視します。-o は、指定した名前で書き込みを行います。URL に使用可能なファイル名がない場合、特定の名前が必要な場合、または異なるパスにある同名のファイル間の競合を避けたい場合は、-o を使用してください。
curl -OJ は安全ですか?
信頼できないソースの場合は安全ではありません。マニュアルでは、Content-Disposition のファイル名が「検証もサニタイズもされていない」こと、また「パストラバーサルシーケンスを含む可能性がある」ことが明示的に警告されています。つまり、ファイルが保存される場所はリモートサーバーによって決定されるということです。ヘッダーを取得し、ファイル名を自分でサニタイズした上で、-o を使用してください。
curlによるダウンロードが成功したかどうかを確認するには?
--fail を使用すると、HTTPエラーが発生した際にエラーページが表示される代わりに終了コード22が返されるため、その終了コードを確認してください。重要なデータの場合は、さらに検証を行ってください。ファイルサイズが妥当かどうかを確認し、file を実行してファイルの種類を確認し、ソースがチェックサムを公開している場合はそれを比較してください。
まとめ
多くの例で見られるような、単純な curl -O は、ある時点までは正常に動作しますが、一度失敗すると最悪の事態を招きます。つまり、終了コードが 0 となり、要求した内容とは異なるものがファイルに書き込まれてしまうのです。
この問題を解決するには、2つのフラグを使用します。-L を指定すると、実質的にすべての実際のダウンロードURLで使用されているリダイレクトを追跡し、--fail を指定すると、curlがHTTPエラー応答をコンテンツであるかのように保存するのを防ぎます。大容量のファイルの場合は、-C - を追加してください。ダウンロードの再開が可能かどうかが、一時的なネットワークの問題と最初からやり直すこととの分かれ目になるからです。
スクリプトの場合、curl --fail --silent --show-error --locationという行は覚えておく価値があります。ファイルが大きい場合は、--continue-at -や--retryを追加してください。また、どのようなオプションを使用する場合でも、終了コードだけを鵜呑みにせず、結果を必ず確認してください。サイズや種類を確認し、チェックサムが存在する場合はそれを確認してください。ダウンロードの失敗は、エラーメッセージを表示して失敗するよりも、何事もなかったかのように静かに失敗する方がはるかに多いのです。