更新日:2025年3月17日
3分で読めます
環境変数“no proxy”が原因で問題発生したことは?お客様事例を取り上げ、標準化の方法を考えてみました。

ウェブプロキシサーバーを使用した経験がある方なら、環境変数http_proxyやHTTP_PROXYをよくご存知でしょう。しかし、no_proxy(ノープロキシ)に関しては、どうもわかりにくい、と感じていらっしゃる方も多いのではないでしょうか。
no proxyとは、あるホスト宛のトラフィックでプロキシを経由させないようにする環境変数です。世界基準が存在するHTTPと違い、ウェブクライアントがno proxyを処理する方法に「標準」は存在しません。その結果、ウェブクライアントは場合により異なる方法で処理を行います。
その違いが原因でサービスが通信を停止し、その原因を突き止めるために週末返上で作業する羽目になったGitLabのお客様もいらっしゃいます。
そこで、この記事ではGitLabのお客様が直面した問題について、具体例を挙げながら根本原因を探り、「no proxyを標準化する方法」というテーマを掘り下げてみます。
no proxyがなぜわかりにくいのか、具体例を挙げて説明します。
現在、ほとんどのウェブクライアントは環境変数を介してプロキシサーバーへの接続をサポートしています。環境変数には大文字表記と小文字表記があります。
http_proxy / HTTP_PROXYhttps_proxy / HTTPS_PROXYno_proxy / NO_PROXYこれらの変数は、プロキシサーバーにアクセスするのにどのURLを使用するか、またどういった例外を作っているか、クライアントに指示するものです。
たとえば、ある企業で田中さんがhttp://tanaka.example.com:8080 でリッスンしているプロキシサーバーの場合、次のようになります。
export http_proxy=http://tanaka.example.com:8080
一方、同僚の斎藤さんも、次のように大文字バージョンのHTTP_PROXY で定義していたとします。
export HTTP_PROXY=http://saito.example.com:8080
この場合、どちらのプロキシサーバーが使用されることになるのでしょうか?答えは「状況によって異なる」です。ある場合は田中さんのプロキシサーバーが有効になる場合もあれば、ある場合は斎藤さんのプロキシサーバーが有効になる場合があります。
この場合、どちらのプロキシサーバーが使用されることになるのでしょうか?答えは「状況によって異なる」です。ある場合は田中さんのプロキシサーバーが有効になる場合もあれば、ある場合は斎藤さんのプロキシサーバーが有効になる場合があります。
では、例外を設定したい場合はどうなるでしょうか。たとえば、internal.example.comとinternal2.example.com以外のすべてで、プロキシサーバーを経由したい場合です。このような場合がno_proxy変数の出番です。no_proxyを次のように定義します。
export no_proxy=internal.example.com,internal2.example.com
では、IPアドレスを除外したい場合はどうすればよいでしょうか?アスタリスクやワイルドカードは使用できるのでしょうか?CIDRブロック(例:192.168.1.1/32)は?
これらの答えも、「状況によって異なる」です。つまり「使用言語やツールという”PC環境”によって、proxy変数の処理方法が異なる」のが、no proxyがわかりにくいとされている理由です。次の項では、proxy変数の処理方法の違いについてさらに掘り下げます。
この問題の理解を深めるため、no proxyを巡るこれまでの経緯を説明しておきます。
1994年においてほとんどのウェブクライアントは、http_proxyとno_proxy環境変数をサポートするCERNのlibwwwを使用していました。libwwwは、http_proxyの小文字形式のみを使用し、no_proxy構文は以下のようにシンプルでした。
no_proxy is a comma- or space-separated list of machine
or domain names, with optional :port part. If no :port
part is present, it applies to all ports on that domain.
Example:
no_proxy="cern.ch,some.domain:8001"
つまり、元々「小文字表記のみ」で始まったのですが、その後新しいクライアントであるwgetやcurlの登場により、no proxyの大文字が使用可になったり、不可とされたりと変遷しているのです。
1996年1月にHrvoje Niksicが、libwwwをリンクせずに独自のHTTP実装を追加する新しいクライアント、geturl(現在のwgetの前身)をリリースしました。翌月にはgeturlがバージョン1.1でhttp_proxyのサポートを追加され、同年5月にはgeturlバージョン1.3でno_proxyのサポートが追加されました。ここではlibwwwと同様に、geturlでは小文字形式no_proxyのみのサポートでした。
1998年1月には、Daniel Stenbergがcurlv5.1をリリースし、http_proxyおよびno_proxy変数をサポート。また、大文字の形式のHTTP_PROXYおよびNO_PROXYも許可されました。
2009年3月にはcurl v7.19.4がセキュリティ上の懸念から、大文字HTTP_PROXYのサポートを廃止します。curlではHTTP_PROXYは無視されますが、HTTPS_PROXYは現在でも動作します。
GitLabのNourdinel Bachaが調査したところ、これらのプロキシサーバー変数の処理方法は、使用言語やツールによって異なることがわかりました。
各行はサポートされている動作を表し、各列にはそれが適用されるツール(例:curl)または言語(例:Ruby)を表しています。
| curl | wget | Ruby | Python | Go | |
|---|---|---|---|---|---|
http_proxy |
はい | はい | はい | はい | はい |
HTTP_PROXY |
いいえ | いいえ | はい (警告) | はい (REQUEST_METHOD が環境にない場合) |
はい |
https_proxy |
はい | はい | はい | はい | はい |
HTTPS_PROXY |
はい | いいえ | はい | はい | はい |
| 大文字と小文字の優先順位 | 小文字 | 小文字のみ | 小文字 | 小文字 | 大文字 |
| 参照 | 出所 | 出所 | 出所 | 出所 | 出所 |
この表から以下のことがわかります。
環境変数はすべて大文字だと思われがちですが、実は最初に登場したhttp_proxyに倣い、小文字表記が事実上のスタンダードとなっています。よくわからない場合は、普遍的にサポートされている小文字形式の使用をおすすめします。
さて、次はno_pproxyについて説明します。次の表は、さまざまな実装の状態を示しています。こちらの表はhttp_proxyの場合に比べてもっと複雑です。例えば、no_proxy設定が次の様に定義されているとします。
export no_proxy=example.com
これはドメインが完全一致である必要があるのか、それともsubdomain.example.comのようなサブドメインも含まれるのでしょうか。次の表は様々な実装状況を示しています。「サフィックス(接尾辞)と一致?」の行を見ると分かるように、実際にはすべての実装がサフィックス(ドメイン末尾)を適切に一致させることができます。
| curl | wget | Ruby | Python | Go | |
|---|---|---|---|---|---|
no_proxy |
はい | はい | はい | はい | はい |
NO_PROXY |
はい | いいえ | はい | いいえ | はい |
| 大文字と小文字の優先順位 | 小文字 | 小文字のみ | 小文字 | 小文字のみ | 大文字 |
| サフィックス(接尾辞)と一致? | はい | はい | はい | はい | はい |
.でリーディング停止? |
はい | いいえ | はい | はい | いいえ |
* はすべてのホストに一致? |
はい | いいえ | いいえ | はい | はい |
| 正規表現をサポート? | いいえ | いいえ | いいえ | いいえ | いいえ |
| CIDRブロックをサポート? | いいえ | いいえ | はい | いいえ | はい |
| ループバックIPを検出する? | いいえ | いいえ | いいえ | いいえ | はい |
| 参考 | 出所 | 出所 | 出所 | 出所 | 出所 |
ただし、no_proxy設定の先頭に「.」がある場合、動作が異なります。
たとえば、curlとwgetは動作が異なります。curlは常に先頭の「.」を削除し、ドメインサフィックスと照合します。次の呼び出しはプロキシをバイパスします。
$ env https_proxy=http://non.existent/ no_proxy=.gitlab.com curl https://gitlab.com
<html><body>You are being <a href="https://about.gitlab.com/">redirected</a>.</body></html>
ただし、wgetは先頭の「.」を削除せず、ホスト名に対して正確な文字列一致を実行します。その結果、wgetはトップレベルドメインが使用されている場合にプロキシの使用を試みます。
$ env https_proxy=http://non.existent/ no_proxy=.gitlab.com wget https://gitlab.com
Resolving non.existent (non.existent)... failed: Name or service not known.
wget: unable to resolve host address 'non.existent'
すべての実装において、正規表現はサポートされません。
正規表現には独自の特徴(PCRE、POSIXなど)があるため、正規表現を使用すると問題がさらに複雑になると思われます。また、正規表現を使用すると、パフォーマンスとセキュリティの問題が発生する可能性があります。
no_proxyを*に設定するとプロキシが完全に無効になる場合もあるが、これはすべてに共通するルールではない。
プロキシを使用するかどうかを決定する際に、ホスト名をIPアドレスに解決するためのDNSルックアップを実行する実装はない。
クライアントによってIPアドレスが明示的に使用されることが予想される場合を除き、no_proxy変数にIPアドレスを指定しないようにしましょう。
18.240.0.1/24などのCIDRブロックは、リクエストが直接IPアドレスに対して行われた場合にのみ機能します。CIDRブロックが許可されるのはGoとRubyのみです。他の実装とは異なり、GoではループバックIPアドレスが検出されると、プロキシの使用が自動的に無効になります。
大文字小文字表記、言語とツールによるリアクションの違いに注意を払う必要があるのは、複数の言語で記述されたアプリケーションを、プロキシサーバーを備えた企業のファイアウォールの背後で動作させる場合です。GitLabもそのひとつであり、RubyとGoで記述された複数のサービスで構成されています。
ここでGitLabのあるお客様の例を挙げましょう。お客様はプロキシ構文を次のように設定しました。
HTTP_PROXY: http://proxy.company.com
HTTPS_PROXY: http://proxy.company.com
NO_PROXY: .correct-company.com
このお客様からGitLabに以下の問題の報告がありました。
git pushが起動した連絡を受けたサポートエンジニアは、Kubernetesの構文の問題により、古い値が残っていることを発見しました。ポッドの環境は実際には次のようになっていました。
HTTP_PROXY: http://proxy.company.com
HTTPS_PROXY: http://proxy.company.com
NO_PROXY: .correct-company.com
no_proxy: .wrong-company.com
no_proxyとNO_PROXY、両者の定義が一致していないため警告が出ました。定義を一致させるか/誤ったエントリを削除することで、この問題を解決できます。
この古いエントリの何が原因で問題が起きたのか、もう少し詳しく見てみることにします。先ほど「no proxyの場合」で述べたことをここで思い出してみましょう。
その結果、GitLab WorkhorseなどのGoで記述されたサービスには正しいプロキシ構文となりました。Goサービスが主にこのアクティビティを処理したため、コマンドラインからのgit pushは正常に機能しました。
gRPC呼び出しでは、no_proxyがGitalyに直接接続するように適切に構成されていたため、プロキシの使用が試行されませんでした。
ただし、ユーザーがUIを変更すると、GitalyはリクエストをRubyで記述されたgitaly-rubyサービスに転送します。gitaly-rubyはリポジトリに変更を加え、gRPCコールバックを介して親プロセスにレポートを返します(英語)。ただし、以下の手順4に示すように、レポート手順は実行されませんでした。
gRPCは基盤となるトランスポートとしてHTTP/2を使用するため、gitaly-rubyは間違ったno_proxy設定で構成されたプロキシへのCONNECTを試行しました。プロキシはこのHTTP要求を即座に拒否し、ウェブUIプッシュケースで失敗を引き起こしました。
環境から小文字のno_proxyを削除すると、UIからのプッシュが期待どおりに機能し、gitaly-rubyが親のGitalyプロセスに直接接続されました。以下の図のステップ4は適切に機能しました。
https://ではなくhttp://が使用されています。セキュリティの観点からは理想的ではありませんが、TLS証明書の検証の問題によりクライアントが失敗するという面倒を避けるために行う場合もあります。
しかしこの場合、HTTPSプロキシが指定されていれば、この問題は発生しなかったでしょう。HTTPSプロキシが使用されている場合、gRPCはHTTPSプロキシをサポートしていないため、この設定を無視するからです。
小文字と大文字のプロキシ設定で矛盾した値を定義すべきではないことは、誰もが同意すると思います。ただし、複数の言語で記述されたスタックを管理する必要がある場合は、HTTPプロキシ構文を最も共通する設定で行うよう検討することをおすすめします。
http_proxy と https_proxyHTTP_PROXY は常にサポートまたは推奨されるわけではない。
no_proxyhostname:port値を使用する。example.comはtest.example.comと一致)。.)を使用しない。no_proxyの標準化チェックリスト最小公分母を知っておくと、定義が異なるウェブクライアントにコピーされた場合に、問題を回避する上で役立ちます。しかし、no_proxyやその他のプロキシ設定には、間に合わせの標準よりも、文書化された標準が必要かもしれません。以下のリストを出発点としてお役立てください。
http_proxy はHTTP_PROXYの前に検索すべき)。hostname:port 値を使用する。
*を使用する。.) を削除し、ドメインサフィックスに対してマッチングさせる。no_proxyのループバックアドレス)。最初のウェブプロキシがリリースされてから25年以上経ちました。環境変数を介してウェブクライアントを構成する基本的な仕組みはあまり変わっていませんが、さまざまな実装で微妙な違いが生じています。
今回、GitLabのあるお客様の具体的な事例をご紹介しました。このお客様の状況は以下のとおりでした。
no_proxy変数とNO_PROXY変数を誤って定義このブログではこの2つの違いに焦点を当て、解説しました。皆様の本番スタックで将来の問題発生回避にお役立ていただけると幸いです。また、設定標準チェックリストを参照して、ウェブクライアントの保守担当者様が動作を標準化し、このような問題を根本的に回避することを願っています。
Gitの利便性を生かしつつ、一元化されたプラットフォームでデベロッパー、セキュリティ担当者、運用チームをサポートするGitLabでは、AIによるコード提案機能があるため、効率性を高められます。導入検討中の方は、ぜひ無料でのトライアルをお試しください。
画像出展: PixaBay
監修:小松原 つかさ @tkomatsubara
(GitLab合同会社 ソリューションアーキテクト本部 シニアパートナーソリューションアーキテクト)