← ブログに戻る

Linux、Docker、CIにおけるHTTP_PROXY、HTTPS_PROXY、NO_PROXYの変数:7つの一般的な間違い

なぜプロキシ環境変数は1つのコンテナで機能し、別のコンテナで壊れるのか—7つの実際のケースと解決策、コードを解説します。

📅2026年11月7日

環境変数 HTTP_PROXY、HTTPS_PROXY、および NO_PROXY は非常にシンプルに見えます: 変数を設定すれば、全てのトラフィックがプロキシを通過します。しかし、実際にはここで開発者がデバッグに何時間も費やすことになります: プロキシはターミナルでは動作するが、Dockerコンテナ内では動作しない; curlはプロキシを通過するが、Pythonアプリケーションは通過しない; CIランナーが突然内部のレジストリをブロックするのは、NO_PROXYが誤った構文で指定されているからです。7つの実際の罠を解説し、具体的なコードでそれらを回避する方法を示します。

アプリケーションがプロキシ変数を読み取る方法

重要なのは、HTTP_PROXY、HTTPS_PROXY、NO_PROXYはHTTPプロトコルの標準ではなく、各ライブラリが独自に実装する慣習であるということです。Linuxカーネルはこれらの変数について何も知りません—それらは特定のアプリケーションまたはライブラリによってネットワーククライアントの初期化時に読み取られます。

例えば、curlやlibcurlベースのユーティリティは、プロセスの各実行時に変数を読み取ります。Pythonライブラリrequestsは、セッションの作成時にプロセスの環境をスキャンするurllib.request.getproxies()モジュールを介してそれらを読み取ります。しかし、Node.jsの組み込みfetch()は、バージョン18以前では追加のパッケージなしではこれらの変数を考慮しません。

ここから最初のルールが導き出されます: プロキシ変数は、特定のライブラリが明示的にサポートしている場所でのみ機能します。curlを介してのチェックは、Go、Java、Node.jsのアプリケーションが同様に動作することを保証しません。

export HTTP_PROXY="http://user:[email protected]:8080"
export HTTPS_PROXY="http://user:[email protected]:8080"
export NO_PROXY="localhost,127.0.0.1,.internal.corp"

curl -v https://api.example.com/ping
# curlはverboseログに「Trying proxy.example.com...」と表示します。

罠1: 変数の大文字小文字とhttpoxyの脆弱性

Linuxのほとんどのコンソールユーティリティは、変数を小文字で読み取ります: http_proxy、https_proxy、no_proxy。多くの現代のSDKは大文字を基にしています。この違いは小さなことのように思えますが、まさにこれが「ローカルでは動作するが、サーバーでは動作しない」というスタイルのバグの半分が設定の初日に発生する原因です。

別の話として、httpoxyの脆弱性(CVE-2016-5385および関連するもの)があります。CGI環境(PHP、Python WSGI、一部のRubyフレームワーク)は、受信リクエストのHTTPヘッダーProxyを自動的に環境変数HTTP_PROXYに変換します。攻撃者がProxy: http://attacker.comというヘッダーを送信すると、あなたのサーバーアプリケーションは他のプロキシサーバーを通じて外部リクエストを送信し、MITM攻撃やデータ漏洩の道を開くことになります。

解決策は、常に両方のケースで変数を設定し、受信リクエストのウェブサーバーレベルでHTTP_PROXYを明示的に無効にすることです:

# 互換性のために両方のケースで設定
export http_proxy="http://proxy.example.com:8080"
export HTTP_PROXY="http://proxy.example.com:8080"
export https_proxy="http://proxy.example.com:8080"
export HTTPS_PROXY="http://proxy.example.com:8080"

# nginxでは、クライアントからのProxyヘッダーをブロックします
proxy_set_header Proxy "";

罠2: NO_PROXYの構文はツールによって異なる

NO_PROXYは例外のリストですが、ドメインとマスクの記述形式は標準化されていません。あるツールは.example.comを「任意のサブドメイン」として理解し、他のツールは*.example.comを要求し、他のツールはワイルドカードなしでホストの正確な一致のみを理解します。

例えば、Goライブラリnet/httpはNO_PROXYでCIDR表記(10.0.0.0/8)をサポートしていますが、古典的なcurlは特定のバージョンまでサポートしていません。これにより、同じNO_PROXYが1つのCIステップでは内部レジストリを正しく除外し、別の言語で書かれた別のステップでは除外しないという状況が生じます。

ツール サブドメインのワイルドカード CIDRマスク
curl / libcurl .example.comを通じて なし
Go net/http .example.comを通じて あり
Python requests .example.comを通じて なし
npm / Node.js *.example.comが必要 なし

実用的なアドバイス: NO_PROXYにはドメインバージョンとワイルドカードバージョンの両方を同時に列挙して、異なるパーサーをカバーしてください:

export NO_PROXY="localhost,127.0.0.1,.internal.corp,*.internal.corp,10.0.0.0/8"

罠3: Docker build vs Docker run — 異なる環境変数

これはおそらくDockerで最も一般的な罠です。docker run -e HTTP_PROXY=...で設定された変数は、コンテナが実行されている間のみ有効です。これらは自動的にイメージのビルドステージには渡されません。もしあなたのDockerfileが内部でapt-get installやpip installを呼び出し、ビルドが企業のプロキシの背後で行われている場合、ビルドはタイムアウトでハングします。

ビルドには、Dockerがバージョン1.9以降「箱から出して」理解するbuild-argsを渡す必要があります:

docker build \
  --build-arg HTTP_PROXY=http://proxy.example.com:8080 \
  --build-arg HTTPS_PROXY=http://proxy.example.com:8080 \
  --build-arg NO_PROXY=localhost,127.0.0.1 \
  -t my-app:latest .

変数を起動したコンテナ内でも利用可能にしたい場合は、Dockerfile内でENVを介して明示的に複製する必要があります—ARGとENVはDockerにおいて根本的に異なるメカニズムであり、ARGはレイヤーのビルドが完了するとすぐに消えます:

ARG HTTP_PROXY
ARG HTTPS_PROXY
ARG NO_PROXY

ENV HTTP_PROXY=${HTTP_PROXY}
ENV HTTPS_PROXY=${HTTPS_PROXY}
ENV NO_PROXY=${NO_PROXY}

RUN apt-get update && apt-get install -y curl

もう一つの注意点: Dockerデーモン自体は、レジストリからイメージをダウンロードするためにプロキシを使用することができます—これはDockerfileではなく、/etc/systemd/system/docker.service.d/http-proxy.confのsystemdユニットの設定で行います。これはしばしばコンテナ内の変数と混同されます。

罠4: docker-composeとサービス間の変数の継承

docker-composeでは、ホストの環境変数は明示的にenvironmentセクションに記述するか、.envファイルを使用しない限り、自動的にコンテナに渡されません。よくある間違いは、docker-compose upを実行する前にターミナルでプロキシを設定し、すべてのサービスがそれを受け取ることを期待することです。

version: "3.9"
services:
  scraper:
    build:
      context: .
      args:
        HTTP_PROXY: "${HTTP_PROXY}"
        HTTPS_PROXY: "${HTTPS_PROXY}"
    environment:
      - HTTP_PROXY=${HTTP_PROXY}
      - HTTPS_PROXY=${HTTPS_PROXY}
      - NO_PROXY=localhost,127.0.0.1,db,redis
    depends_on:
      - db
      - redis

NO_PROXYに注意してください: docker-composeネットワーク内のコンテナは、localhostではなくサービス名(db、redis)で相互にアクセスします。サービス名を除外リストに追加するのを忘れると、コンテナ間のすべての内部トラフィックも外部プロキシを通過することになり、遅延が増加し、プロキシがPostgreSQLやRedisのような内部プロトコルを通過させない場合は接続が壊れる可能性があります。

罠5: CI/CDランナーとプロキシの秘密の漏洩

GitHub Actions、GitLab CI、Jenkinsでは、プロキシ変数はしばしばランナーまたは組織全体のレベルで設定され、これにより2つの問題が発生します。まず、プロキシのURLにログインとパスワードがそのまま含まれている場合、環境を出力する任意のコマンド(env、printenv、スクリプトのデバッグ出力)でビルドログに表示されます。第二に、自己ホスト型ランナーは、CIジョブのトラフィックに適していないホストのシステムプロキシを継承することがあります。

# .github/workflows/build.yml
jobs:
  build:
    runs-on: ubuntu-latest
    env:
      HTTP_PROXY: ${{ secrets.PROXY_URL }}
      HTTPS_PROXY: ${{ secrets.PROXY_URL }}
      NO_PROXY: "localhost,127.0.0.1,github.com"
    steps:
      - uses: actions/checkout@v4
      - name: 接続テスト
        run: curl -sS https://api.example.com/health

セキュリティルール: プロキシのURLはCIの秘密にのみ保存し(GitHub Secrets、GitLab CI/CD Variablesに「masked」フラグを付ける)、デバッグログに環境変数を全て出力しないようにし、NO_PROXYを制限してCIプラットフォーム自体(例えば、github.com、gitlab.com)へのリクエストがプロキシを通過しないようにします—そうしないと、プロキシがピーク時に利用できない場合、ビルドがハングする可能性があります。

罠6: プロキシのログインとパスワードにおける特殊文字

プロキシのパスワードに@、:、/、または#が含まれている場合、URLパーサーは文字列を不適切な場所で分割します。典型的なエラーは、パスワードP@ssw0rdがhttp://user:P@[email protected]:8080の文字列内にある場合、パーサーはホストを「[email protected]」として読み取り、「proxy.com」ではなくなります。

解決策は、変数に代入する前に常にログインとパスワードをURLエンコードすることです:

# Python: プロキシ文字列を構築する前にパスワードをエンコードします
import urllib.parse

user = "login"
password = "P@ss:w0rd/1"
encoded_password = urllib.parse.quote(password, safe="")
proxy_url = f"http://{user}:{encoded_password}@proxy.example.com:8080"

import os
os.environ["HTTPS_PROXY"] = proxy_url
# Node.js: encodeURIComponentを使った同様の構築
const user = "login";
const password = "P@ss:w0rd/1";
const encoded = encodeURIComponent(password);
const proxyUrl = `http://${user}:${encoded}@proxy.example.com:8080`;

process.env.HTTPS_PROXY = proxyUrl;

罠7: 環境変数を無視するアプリケーション

すべてのHTTPクライアントがシステムプロキシ変数を尊重するわけではありません。Pythonのライブラリrequestsはrequests.Session()を介して自動的にそれらを読み取りますが、一度proxies={}という辞書を明示的に渡すと、環境変数が上書きされ、HTTP_PROXYがシステムで設定されていてもトラフィックが直接流れます。

import requests

# 空の辞書proxiesは環境変数を上書きします
response = requests.get("https://api.example.com", proxies={})
# 正しくは、プロキシを明示的に渡すか、proxiesを全く渡さないことです
proxies = {
    "http": "http://user:[email protected]:8080",
    "https": "http://user:[email protected]:8080",
}
response = requests.get("https://api.example.com", proxies=proxies)

Node.jsの組み込みfetchは、追加のライブラリなしではHTTP_PROXYを知りません。undiciのProxyAgentや古典的なhttps-proxy-agentを明示的にインポートする必要があります:

const { ProxyAgent, setGlobalDispatcher } = require("undici");

const proxyUrl = process.env.HTTPS_PROXY;
if (proxyUrl) {
  setGlobalDispatcher(new ProxyAgent(proxyUrl));
}

fetch("https://api.example.com/data")
  .then(res => res.json())
  .then(console.log);

Javaも環境変数の代わりにシステムプロパティを必要とします: -Dhttp.proxyHost=proxy.example.com -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy.example.com -Dhttps.proxyPort=8080。JVMではHTTP_PROXYの環境変数はデフォルトでは読み取られません。

プロキシ変数を正しくテストする方法

アプリケーションのデバッグに時間を費やす前に、変数自体とプロキシをコードから孤立させて確認してください。これにより、何時間も節約できます。

# 1. 変数が現在のセッションに実際に設定されていることを確認します
env | grep -i proxy

# 2. curlを介してプロキシの動作を直接確認します
curl -x "$HTTP_PROXY" -v https://api.ipify.org

# 3. コンテナ内から、変数が届いていることを確認します
docker exec -it my_container env | grep -i proxy

# 4. NO_PROXYが実際に必要なホストを除外していることを確認します
curl -v --noproxy "internal.corp" https://service.internal.corp/health

もし目的が外部APIへの大量リクエスト、マーケットプレイスのパース、またはCIパイプラインからの広告の地理ターゲティングテストであれば、負荷に適したプロキシの種類を選ぶことが重要です。CIにおける安定した高負荷シナリオには、通常、データセンタープロキシで十分です—これらはビルドとテストの大量トラフィックに対して、レジデンシャルプロキシよりも速く、安価です。もしあなたのパイプラインが特定の地域の実際のユーザーIPからのサイトの動作を確認する場合は、レジデンシャルプロキシを接続する方が良いでしょう—これにより「データセンター」のIPによるブロックのリスクが低減します。

デプロイ前のチェックリスト

  • 変数は大文字と小文字の両方で同時に設定されています
  • プロキシのURLのログインとパスワードはurllib.parse.quoteまたはencodeURIComponentを介してURLエンコードされています
  • NO_PROXYには、ツール間の互換性のためにドメイン形式とワイルドカード形式の両方が含まれています
  • docker-composeのNO_PROXYには、localhostだけでなくComposeネットワーク内のサービス名も含まれています
  • Docker buildには、Dockerfile内のENVを介してruntime envとは別にbuild-argsが渡されています
  • CIのプロキシURLが含まれるSecretsはマスクされ、デバッグログに表示されません
  • Javaアプリケーションのプロキシは、envだけでなくシステムプロパティ-Dを介して設定されます
  • httpoxyからの保護が確認されており、クライアントからのProxyヘッダーはウェブサーバーレベルで無効にされています

結論

HTTP_PROXY、HTTPS_PROXY、NO_PROXY変数は、一見シンプルな慣習ですが、実際には文字の大文字小文字、例外の構文、Docker buildとDocker runの間の境界、docker-composeでの環境の継承、CIログでの秘密の漏洩、そして最後に、すべてのライブラリがこれらの変数を読み取るわけではないという問題に直面します。curlとdocker execを介して各変数を孤立して確認してから、アプリケーションのビジネスロジックのバグを探してください。

もしあなたのインフラが外部サイトのパース、広告の地理ターゲティングテスト、またはCIパイプラインからのIPによるブロックの回避に依存している場合、環境変数を正しく設定するだけでなく、タスクに適したプロキシの種類を選ぶことが重要です。モバイルトラフィックのテストや、モバイルオペレーターからの動作を確認するアプリケーションには、モバイルプロキシが適しています—これにより、実際の携帯ネットワークのIPが提供され、アンチフロードシステムのフィルターに引っかかることが少なくなります。