社内プロキシとWindowsアプリ ── WinINET・WinHTTP・.NETのプロキシ解決を整理する

· 更新日: · · Windows, プロキシ, WinHTTP, WinINET, .NET, HttpClient, PAC, WPAD, ネットワーク

「ブラウザーでは外部サイトを開けるのに、業務アプリだけが外部APIへつながらない」「開発機では動くのに、客先のネットワークではタイムアウトする」「手動で実行すれば通信できるのに、Windowsサービスにした途端に失敗する」── 社内プロキシのある環境で業務アプリを動かすとき、この種の相談は定番中の定番です。

原因の大半は、プロキシサーバー側の障害でも、アプリのバグでもありません。Windowsには「プロキシ設定」と呼ばれるものが複数の系統に分かれて存在し、どの設定を誰が読むかがアプリ(が使うHTTPスタック)と実行アカウントごとに違う── この食い違いです。ブラウザーが読む設定と、サービスが読む設定と、.NETのHttpClientが読む設定は、それぞれ別物になり得ます。この構造さえ頭に入れば、「ブラウザーではつながるのに」の切り分けは驚くほど速くなります。

この記事では、中小企業の情シス担当者とWindowsアプリ開発者を対象に、WinINET・WinHTTP・環境変数という3系統のプロキシ設定、PACとWPADによる自動構成、.NET Frameworkと.NET(Core以降)のプロキシ解決の違い、認証プロキシ(407)、TLSインスペクション、そして実務の切り分け手順までを一枚につなぎます。HttpClientの生成パターンやタイムアウト設計そのものは「HttpClientをusingで囲んではいけない」で扱っているので、本記事はプロキシの解決に集中します。

1. まず結論

  • Windowsのプロキシ設定は1つではなく、少なくとも3系統あります。①WinINETのユーザー別設定(設定アプリの「プロキシ」=旧インターネットオプション)、②WinHTTPのマシン設定(netsh winhttp)、③環境変数HTTP_PROXY/HTTPS_PROXYです。どれを読むかはアプリ側で決まっています。12
  • 設定アプリで見えている「プロキシ」はWinINETのユーザー別設定です。ブラウザーや対話型アプリはこれを読みますが、Windowsサービスは読みません。WinINETはサービスでの利用がサポートされておらず、サービス用途はWinHTTPが担当します。13
  • 「手動なら動くのにサービスだと動かない」の最頻出原因は、実行アカウントの違いです。LocalSystemやサービスアカウントには、管理者が自分の画面で設定したユーザー別プロキシは見えていません。34
  • netsh winhttp set proxyは静的設定であり、PAC・自動検出・プロキシ認証を扱いません。PACやWPADをマシン単位で構成したい場合はnetsh winhttp set advproxy側の設定が必要です。42
  • PACの結果はURLごとに変わります。PACファイルのFindProxyForURL関数はURLとホストを引数に取り、プロキシのリストまたは直結(DIRECT)を返します。「あのサイトは通るのにこのAPIだけ通らない」はPACの分岐が原因のことがあります。56
  • .NET(Core以降)のHttpClientは、環境変数→Windowsのユーザープロキシ設定の順で既定プロキシを初期化します。HTTP_PROXYHTTPS_PROXYALL_PROXYのいずれかが定義されているとOS設定より優先されるため、「誰かが環境変数を残していた」事故が起こり得ます。7
  • .NET Frameworkの既定は実行中アカウントのインターネットオプションで、app.configのdefaultProxyで上書きできます。構成ファイルの設定はシステム設定より優先されます。89
  • 407はプロキシ認証のエラーで、401(サーバー認証)とは別物です。方式はNegotiate・NTLM・Basicなどがあり、.NETではDefaultProxyCredentialsWebProxy.UseDefaultCredentialsで資格情報を渡します。サービスアカウントでは「既定の資格情報」の中身が変わることに注意してください。101112
  • TLSインスペクション型プロキシは、社内CA証明書の配布とセットで初めて成立します。配布されていないマシン・ランタイムでは証明書検証エラーになります。アプリ側で検証を無効化するのではなく、証明書ストアへの配布で解決します。134

一文でまとめるなら、「プロキシ設定を確認した」と言うとき、3系統のどれを・どのアカウントから見て確認したのかを常に言えるようにする── これが本記事の主題です。

2. Windowsには「プロキシ設定」が3系統ある

まず全体地図です。Windows上のアプリが社内プロキシを見つける経路は、大きく次の3系統に分かれます。

設定系統 設定場所・コマンド スコープ 主に読むもの
① WinINET(インターネットオプション) 設定アプリ→ネットワークとインターネット→プロキシ、inetcpl.cpl ユーザー別(既定) ブラウザー、対話型デスクトップアプリ、.NET Frameworkの既定
② WinHTTP(マシン設定) netsh winhttp set proxy / set advproxy マシン Windowsサービス、OSコンポーネントの一部
③ 環境変数 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY プロセス(定義元により継承) .NET(Core以降)のHttpClient、curl、Node.js・Python等のクロスプラットフォーム系ツール

①は「Windowsのプロキシ設定」として一般に認識されているもので、実体はWinINETの構成です。歴史的にはInternet Explorerのインターネットオプションであり、既定ではユーザーごとに保存されます。4

②はサービスなど「サインインしたユーザーがいない文脈」のための、マシン単位の既定です。③は主にクロスプラットフォーム由来のツールの流儀で、Windowsでも.NET(Core以降)やcurlなどがこれを読みます。7

重要なのは、どの系統を読むかを決めるのは設定側ではなくアプリ側だという点です。アプリが内部でWinINETを使っていれば①、WinHTTPなら②(またはアプリ独自の指定)、.NET(Core以降)なら③→①の順、と読む先が決まっています。だから「プロキシ設定は正しいのにつながらない」のではなく、「アプリが読んでいる系統は、あなたが確認した系統と別だった」が実態であることが多いのです。

なお、グループポリシー「プロキシの設定をユーザーごとではなくコンピューターごとに設定する」を有効にすると、①をマシン単位に切り替えて全ユーザーへ同じ設定を適用できます。MDM(Intune等)ならNetworkProxy CSPでデバイス単位に構成できます。4

3. WinINETとWinHTTP ── 対話型アプリ用とサービス用

3.1. 役割の違い

WinINETとWinHTTPはどちらもWindows標準のHTTPクライアントスタックですが、想定する使われ方が異なります。

  • WinINET: 対話型デスクトップアプリ向け。ユーザーのインターネットオプション(プロキシ・Cookie・資格情報キャッシュ)を自動的に引き継ぎ、必要なら資格情報の入力UIも出せます。ただしサービスやサービス的なプロセスでの利用はサポートされません1
  • WinHTTP: サービス・サーバーサイド用。サービスアカウントでの実行、スレッドの偽装(impersonation)、セッション分離をサポートする代わりに、ユーザーのブラウザー設定・Cookie・資格情報は共有しません。UIも出しません。3

Microsoft自身の指針も明快で、「サービス内、またはセッション分離と偽装を必要とするサービス的プロセスで動かすのでなければWinINETを使う」、逆に言えばサービスならWinHTTPです。1

3.2. netsh winhttpの基本操作

WinHTTPのマシン既定プロキシはnetshで操作します。2

:: 現在のWinHTTPプロキシ設定を表示
netsh winhttp show proxy

:: 静的プロキシを設定(バイパスリスト付き)
netsh winhttp set proxy proxy-server="proxy.example.co.jp:8080" bypass-list="*.example.co.jp;<local>"

:: インターネットオプション(WinINET)の設定を取り込む
netsh winhttp import proxy source=ie

:: 既定(DIRECT)へ戻す
netsh winhttp reset proxy

ここで押さえるべき制約が2つあります。

  1. netsh winhttp set proxyは静的設定です。プロキシの自動検出も、PAC URLの指定も、プロキシ認証も扱いません。4
  2. import proxy source=ieは実行時点の静的設定を写すだけで、その後インターネットオプション側を変えても追従しません。PACや自動検出を含むマシン単位の構成が必要な場合は、netsh winhttp set advproxyでJSON形式の詳細設定(ProxyProxyBypassAutoconfigUrlAutoDetect)を構成します。2

3.3. 最頻出の落とし穴: サービスはユーザーのIE設定を読まない

現場で最も多いパターンを時系列にすると、こうなります。

  1. 開発者が自分のPCでツールを実行 → 自分のユーザー別プロキシ設定(①)が効いて動く
  2. 本番ではWindowsサービス(Windowsサービスの作り方と運用)としてLocalSystemで常駐させる
  3. LocalSystemから見える設定は別物(ユーザー別設定は見えず、WinHTTPマシン設定は未構成=DIRECT) → 外部APIへ直結を試みてタイムアウト

「同じマシンなのに動かない」のではなく、同じマシンでも実行アカウントが違えば見えているプロキシ設定が違うのです。ユーザーがサインインしていない状態でも通信するプロセスには、そのプロセスのHTTPスタックが実際に読む形でマシン単位の設定を用意するのが正攻法です。WinHTTPを使うネイティブアプリやWindowsコンポーネントならnetshのWinHTTP設定が効きます。4 一方、.NET(Core以降)のHttpClientはWinHTTPのマシン設定を読まない(5章参照)ため、.NET製サービスにはシステム環境変数(HTTPS_PROXY等)を設定するか、アプリ設定からHttpClientHandler.Proxyで明示指定します。

逆方向の事故もあります。ラップトップのように社内と社外を行き来する端末にnetsh winhttp set proxyで静的プロキシを焼き込むと、社外ではそのプロキシに到達できず通信が全滅します。マシン静的設定は、ネットワーク構成が変わらないサーバー向きの手段と考えてください。4

4. PACとWPAD ── 「自動構成」の中身

4.1. PACファイルとFindProxyForURL

PAC(Proxy Auto-Configuration)ファイルは、「このURLへはどのプロキシを使うか」を計算するJavaScript(ECMAScript)で、FindProxyForURL(url, host)という関数を必ず含みます。この関数は使うべきプロキシのリストを返すか、プロキシを使わず直接接続してよいことを特別な返り値(DIRECT)で示します。5

function FindProxyForURL(url, host) {
    // 社内ドメインとプライベートアドレスは直結
    if (dnsDomainIs(host, ".example.co.jp") ||
        isInNet(host, "10.0.0.0", "255.0.0.0")) {
        return "DIRECT";
    }
    // それ以外はプロキシ経由。先頭が使えなければ次へフォールバック
    return "PROXY proxy1.example.co.jp:8080; PROXY proxy2.example.co.jp:8080; DIRECT";
}

ここから2つの実務的な帰結が出ます。

  • プロキシ解決はURLごとに行う必要があります。PACはURL(ホスト)によって別のプロキシや直結を返し得るため、WinHTTPの自動プロキシ機能もリクエスト対象のURLを渡して都度問い合わせる設計になっています。6「ブラウザーで別のサイトは見える」ことは、問題のAPIが同じ経路を通る証明にはなりません。
  • DIRECTは「プロキシなしで行け」という指示です。社内宛のはずの通信がプロキシログに現れない場合、PACがDIRECTを返している(またはバイパスリストに一致している)可能性をまず疑います。

4.2. WPADによる自動検出

「設定を自動的に検出する」をオンにすると、WPAD(Web Proxy Auto-Discovery)プロトコルでPACファイルの場所を探します。一般的な構成では、DHCPからPAC URLを配るか、DNSでwpadという名前のホストを引き、http://wpad/wpad.datのようなURLからPACをダウンロードします。14

つまり「自動検出」は魔法ではなく、DHCP/DNSにWPADの仕掛けを用意してあるネットワークでだけ機能する仕組みです。仕掛けのないネットワークで自動検出だけをオンにしても、検出失敗の待ち時間が増えるだけです。

4.3. PACを読めないクライアントの挙動

すべてのクライアントがPACを評価できるわけではありません。

  • netsh winhttp set proxyの静的設定はPACを評価しません。4
  • 環境変数HTTP_PROXY方式のツールは、原則として固定のプロキシURLしか書けません(PAC URLを書く場所がありません)。7
  • WinHTTPを直接使うネイティブアプリの場合、セッションの開き方で変わります。Windows 8.1以降のWinHttpOpenWINHTTP_ACCESS_TYPE_AUTOMATIC_PROXYを指定して開いたアプリは、システム/ユーザーのプロキシ設定(WPAD/PACを含む)をWinHTTP側がリクエストごとに自動で解決します。15 一方、従来のWINHTTP_ACCESS_TYPE_DEFAULT_PROXY(8.1以降では非推奨)などで開いた場合、自動プロキシはHTTPスタックに統合されておらず、アプリが自分でWinHttpGetProxyForUrlを呼んで結果をリクエストに設定する必要があります。つまり古い実装のアプリでは、PACがあっても使われないことがあります5

「ブラウザーはPACで正しいプロキシへ行くが、業務アプリはPACを読まずに直結を試みて失敗する」── これも定番の食い違いです。PAC運用のネットワークでは、PACを読めないクライアントのために静的設定や環境変数でのフォールバック手段を決めておく必要があります。

5. .NETのプロキシ解決 ── FrameworkとCore以降で別物

.NETアプリの「どのプロキシ設定を読むか」は、.NET Frameworkと.NET(Core以降)で既定が異なります。ここを混同すると、Framework時代の知識で.NET 8のアプリを調査して外します。

5.1. .NET Framework ── 既定はインターネットオプション、defaultProxyで上書き

.NET Frameworkでは、HttpWebRequestや(その上に乗る)HttpClientが、Proxyを明示しない限り既定プロキシを使います。既定プロキシはシステムのインターネット設定(実行中アカウントのWinINET設定)と構成ファイルの組み合わせで決まり、構成ファイルの設定が優先されます。8

app.config(またはmachine.config)のsystem.net/defaultProxy要素で、この既定を制御できます。9

<configuration>
  <system.net>
    <!-- useDefaultCredentials: 認証プロキシへ既定資格情報を送るか -->
    <defaultProxy enabled="true" useDefaultCredentials="true">
      <proxy usesystemdefault="true"
             proxyaddress="http://proxy.example.co.jp:8080"
             bypassonlocal="true" />
      <bypasslist>
        <add address="[a-z]+\.example\.co\.jp$" />
      </bypasslist>
    </defaultProxy>
  </system.net>
</configuration>

defaultProxy要素を空のままにすればシステム(インターネットオプション)の設定が使われ、proxyaddress等を書けばそれが優先されます。プログラムからはWebRequest.DefaultWebProxyで同じ既定を差し替えられます。98

ここでも3.3節の落とし穴が効いてきます。「実行中アカウントのインターネットオプション」が既定なので、サービスアカウントで動く.NET Frameworkアプリは、管理者のデスクトップで見えている設定とは別の(たいてい空の)設定を読みます

5.2. .NET(Core以降) ── 環境変数が先、次にOSのユーザー設定

.NET(Core以降)のHttpClientには、静的プロパティHttpClient.DefaultProxyがあります。ハンドラーでプロキシを明示しない限り、すべてのHttpClientインスタンスがこれを使います。Windowsでの初期化規則は「環境変数を読み、定義されていなければユーザーのプロキシ設定を読む」です。7

使われる環境変数は次のとおりです。7

環境変数 意味
HTTP_PROXY HTTPリクエストに使うプロキシ
HTTPS_PROXY HTTPSリクエストに使うプロキシ
ALL_PROXY 上記が未定義のときのフォールバック
NO_PROXY プロキシを使わないホストのカンマ区切りリスト

注意点が3つあります。

  • HTTP_PROXYHTTPS_PROXYALL_PROXYのいずれかが定義されていると、OS側のプロキシ設定より優先されます。NO_PROXYだけを定義しても環境変数によるプロキシは構成されず、Windowsでは引き続きOSのユーザープロキシ設定が使われます。過去の検証でHTTPS_PROXYをシステム環境変数に設定したまま忘れる、CI/CDのテンプレートが注入している、といった「見えない設定」が事故の温床です。
  • NO_PROXYはワイルドカード(*)をサポートしません。サブドメインに一致させたいときは先頭にドットを付けます(.example.comwww.example.comに一致するがexample.com自体には一致しない)。7
  • Windows以外(Linuxコンテナー等)では、環境変数が未定義だとプロキシなしで初期化されます。同じアプリでもWindowsとLinuxで既定挙動が変わる点は、コンテナー移行時に確認が必要です。7

5.3. 明示指定 ── HttpClientHandler.ProxyとUseProxy

どちらのランタイムでも、最優先はハンドラーへの明示指定です。HttpClientHandler.Proxyを指定するとOS設定や構成ファイルより優先され、UseProxy = falseにすればプロキシを一切使いません。14

using System.Net;

// アプリ設定から読んだプロキシを明示的に使う
var handler = new HttpClientHandler
{
    Proxy = new WebProxy("http://proxy.example.co.jp:8080")
    {
        BypassProxyOnLocal = true,
        BypassList = new[] { @"^intra\.example\.co\.jp$" },
        UseDefaultCredentials = true // 認証プロキシなら実行アカウントの資格情報で応答
    },
    UseProxy = true
};
var client = new HttpClient(handler);

// プロキシを一切使わないクライアント(社内API直結用)
var directHandler = new HttpClientHandler { UseProxy = false };
var directClient = new HttpClient(directHandler);

なお、明示指定がなくOS設定に従う場合、ローカル宛の自動バイパスには規則があります。ドットを含まないフラット名、ループバックアドレス、自マシンのドメインサフィックスと一致する宛先などは「ローカル」とみなされ得ます。14「IPアドレス直指定だと挙動が変わる」「FQDNにしたら急にプロキシを通り出した」という現象は、この判定が原因のことがあります。

優先順位を整理すると次のとおりです。

優先順位(高→低) .NET Framework .NET(Core以降)
1 HttpClientHandler.Proxy等の明示指定 同左
2 app.configのdefaultProxy HttpClient.DefaultProxyへの代入
3 実行アカウントのインターネットオプション 環境変数(HTTP_PROXY等)
4 Windowsのユーザープロキシ設定

6. 認証プロキシ ── 407は「プロキシの」認証エラー

6.1. 407と401を混同しない

認証を要求するプロキシを通ろうとすると、プロキシはステータスコード407 (Proxy Authentication Required)と、利用可能な認証方式を列挙したProxy-Authenticateヘッダーを返します。接続先サーバーの認証要求(401とWWW-Authenticate)とは別物で、資格情報を渡す相手も設定する場所も違います。10

認証方式には、ユーザー名とパスワードをそのまま送るBasicと、Negotiate(Kerberos/NTLM)のようなチャレンジ/レスポンス方式があります。チャレンジ/レスポンス方式ではパスワード自体はネットワークを流れず、複数回のやり取りで認証が完了します。10どの方式に「落ちる」かの仕組みは「図解でわかるNTLMとKerberos」で詳しく扱っています。

6.2. .NETでの資格情報の渡し方

OS設定由来の既定プロキシを使いつつ認証だけ通したい場合は、HttpClientHandler.DefaultProxyCredentialsを使います。これはUseProxy = trueかつProxy = null(=システム既定のプロキシ)のときに、その既定プロキシへ送る資格情報です。11

using System.Net;

var handler = new HttpClientHandler
{
    UseProxy = true,   // 既定値。null Proxyと組み合わせるとシステム既定のプロキシを使う
    Proxy = null,
    // 実行アカウント(サインイン中ユーザーまたはサービスアカウント)の資格情報で407に応答
    DefaultProxyCredentials = CredentialCache.DefaultCredentials
};
var client = new HttpClient(handler);

プロキシを明示指定する場合は、WebProxy側に資格情報を持たせます。多くのクライアントシナリオでは、個別のユーザー名・パスワードではなくサインイン中ユーザーの既定資格情報を使うのが推奨で、WebProxy.UseDefaultCredentials = trueがそれに当たります。12

6.3. サービスアカウントの407問題

ここでも実行アカウントが効いてきます。「既定の資格情報」とはそのプロセスを動かしているアカウントの資格情報です。対話ユーザーで動かせばそのユーザーとして、LocalSystemのサービスで動かせばコンピューターアカウントとして、プロキシに対する認証が行われます。

  • プロキシがActive Directory連携でユーザー認証している場合、コンピューターアカウントやローカルアカウントを認証できず、サービス化した途端に407が続く
  • 逆に、プロキシ側でサービス用の認証除外(送信元IPやアカウント単位)が用意されている環境もある

つまり407の調査は「アプリの設定」だけでは閉じず、実行アカウントをプロキシが認証できるかというインフラ側の設計確認とセットになります。サービス化するアプリでは、ドメインのサービスアカウント(gMSA等)で動かす、プロキシ側で認証除外を設ける、認証不要の内部中継プロキシを立てる、のいずれかを設計段階で決めておくべきです。

なお、HTTP_PROXY=http://user:pass@proxy:8080のように環境変数へ資格情報を埋め込む方式もありますが7、平文のパスワードが環境変数(=プロセス情報)に露出するため、恒久運用には推奨しません。

7. HTTPSとプロキシ ── CONNECTトンネルとTLSインスペクション

7.1. HTTPSはプロキシを「トンネル」で通る

HTTPS通信でプロキシを使う場合、クライアントはまずプロキシへCONNECT 宛先ホスト:443というリクエストを送り、プロキシがTCPトンネルを開通させます。成功すればプロキシは200を返し、以後クライアントと宛先サーバーがそのトンネル内でTLSハンドシェイクを行います。トンネルが開通しない場合、プロキシは407(認証要求)や502などを返します。16

このモデルでは、プロキシはトンネルの中身(暗号化されたHTTPS)を読めません。プロキシログに残るのは宛先ホスト名と接続の成否までで、URLパスは見えない ── これが「素通し型」プロキシの挙動です。

7.2. TLSインスペクション型プロキシと証明書エラー

一方、セキュリティ製品系のプロキシには、TLSをいったん終端して中身を検査し、再暗号化して転送するTLSインスペクション(SSL復号、break and inspect)型があります。この方式では、クライアントに提示されるサーバー証明書が本物ではなく、プロキシ自身のCAが署名し直した証明書に置き換わります。13

したがって、この構成が成立する前提は「プロキシのCA証明書が、全クライアントの信頼されたルートに配布されていること」です。配布されていないマシン、またはWindowsの証明書ストアを見ないランタイム(独自の信頼ストアを持つツール類)では、証明書検証エラーになります。.NETでは典型的にHttpRequestExceptionとその内部のAuthenticationException(リモート証明書が無効、の類のメッセージ)として表面化します。

対処の原則は次のとおりです。

  • 社内CA証明書を、ローカルコンピューターの「信頼されたルート証明機関」ストアへ配布する。ユーザーストアとコンピューターストアの使い分けは「Windows証明書ストア実務ガイド」を参照してください。
  • コードで証明書検証を無効化しない。ServerCertificateCustomValidationCallbackで常にtrueを返すような回避は、社外ネットワークに出た瞬間、中間者攻撃を検出できない脆弱なアプリになります。
  • 証明書ピン留めを行う通信は、そもそもインスペクションできません。特定のMicrosoft証明書を検証するWindowsコンポーネントのように、ピン留めされた接続はプロキシが証明書を差し替えた時点で失敗し、回避策はなく除外設定が必要です。4Microsoft 365などのSaaS宛トラフィックについては、Microsoft自身がネットワーク層での復号・検査の対象から除外することを推奨しています。13

「社内の全サイトは見られるのに、特定のクラウドサービスだけアプリが証明書エラーを出す」という症状は、TLSインスペクションの除外リストとピン留めの組み合わせをまず疑ってください。

8. 切り分け手順 ── 5ステップで犯人を特定する

「つながらない」の調査は、次の順で機械的に進めます。

手順 やること 分かること
① 再現 問題のURLへcurl.exe -vInvoke-WebRequestでアクセス(できれば同じマシン・同じアカウントで) アプリ固有の問題か、環境の問題か
② 設定採取 netsh winhttp show proxy、ユーザー別設定、環境変数の3系統を採取 どの系統に何が入っているか
③ アカウント特定 対象アプリの実行アカウントを特定(サービスか、タスクスケジューラか、別ユーザーか) どの設定・どの資格情報で動いているか
④ エラー分類 407 / 403 / 名前解決失敗 / タイムアウト / 証明書エラーを区別 プロキシ認証・ポリシー拒否・経路・TLSインスペクションの切り分け
⑤ プロキシログ プロキシサーバーのアクセスログで該当時刻を確認 そもそもプロキシに到達したか、誰として認証されたか

②の採取はPowerShellでまとめて行えます。

# ① ユーザー別(WinINET)設定 ── 実行アカウントのHKCUを読む点に注意
Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' |
    Select-Object ProxyEnable, ProxyServer, ProxyOverride, AutoConfigURL

# ② マシン(WinHTTP)設定
netsh winhttp show proxy

# ③ 環境変数
Get-ChildItem env: | Where-Object Name -match 'proxy'

いくつか実務上のコツがあります。

  • ①の再現テストでは、ツールが読む設定系統を意識します。Windows同梱のcurl.exe-x http://proxy:8080でプロキシを明示でき、TLS検証には通常OSの証明書ストア(Schannel)を使います。Windows PowerShell 5.1のInvoke-WebRequestは.NET Framework側(既定でインターネットオプション)、PowerShell 7では.NET側(環境変数優先)の解決に従います。「curlは通るのにアプリは通らない」自体が、設定系統の食い違いを示すヒントです。
  • ③で対象がサービスなら、サービスと同じアカウントで①②を再確認します。管理者の自分のセッションでの確認は、LocalSystemの見え方の証明になりません。
  • ④のエラー分類では、407なら6章(認証)、証明書エラーなら7章(TLSインスペクション)、タイムアウトなら「プロキシに到達できていない」(経路・名前解決・ファイアウォール)をそれぞれ第一候補にします。プロキシではなくWindowsファイアウォールの受信規則が原因になるパターンは「Windowsファイアウォールと業務アプリ」で扱っています。
  • ⑤まで進んでもプロキシログに痕跡がない場合、通信はプロキシに到達していません。PACのDIRECT判定、バイパスリスト、環境変数の消し忘れを疑い、必要ならパケットキャプチャで実際の宛先を確認します(「Windowsのパケットキャプチャ実務 ── pktmon・netsh trace・Wiresharkの使い分け」)。

9. 設計の推奨 ── プロキシを「設定できるアプリ」にする

調査手順の話を裏返すと、アプリ側の設計指針になります。社内プロキシのある環境へ納めるWindowsアプリでは、次を推奨します。

  1. プロキシをアプリ設定で明示可能にする。既定は「OS設定に従う」。多くの環境では既定のままでよく、PACが読めない・サービスで動く・特殊なプロキシ構成という例外的な環境でだけ、設定ファイルでプロキシURL・バイパスリスト・「プロキシを使わない」を指定できるようにします。5.3節のHttpClientHandler.Proxy/UseProxyがその実装点です。14
  2. 社内宛(API・DB・ライセンスサーバー等)の通信は、プロキシ例外の扱いを明文化する。PACのDIRECT、バイパスリスト、NO_PROXYのどれで除外するのかを、導入手順書に書ける形にしておきます。NO_PROXYのマッチ規則(ワイルドカード不可・先頭ドットの意味)は誤解が多いため、例を添えます。7
  3. タイムアウトとリトライを、プロキシ経由を前提に設計する。プロキシが落ちている・認証で止まっている場合、既定の長いタイムアウトを待つ実装はUIも運用も固まります。接続タイムアウトを短めに分離し、リトライは冪等な要求に限定します(設計の詳細は「HttpClientをusingで囲んではいけない」)。
  4. 「どのプロキシを使ったか」をログに出す。障害調査の最初の質問に、アプリ自身が答えられるようにします。

4のログは、次のように解決結果を記録するだけでも効果があります。ポイントは、クライアントを実際に構成した設定(ハンドラー)から経路を導くことです。HttpClient.DefaultProxyを直接ログに出すと、ハンドラーでProxyを明示指定した場合やUseProxy = falseにした場合に、実際の経路と食い違う値を記録してしまいます。

using System.Net.Http;

// handler は HttpClient の生成に使ったものと同じインスタンス
// UseProxy=false なら常に直結。明示指定があればそれが、無ければ DefaultProxy が使われる
var effectiveProxy = handler.UseProxy
    ? handler.Proxy ?? HttpClient.DefaultProxy
    : null;
var target = new Uri("https://api.example.com/v1/orders");
var route = effectiveProxy is null || effectiveProxy.IsBypassed(target)
    ? "DIRECT"
    : effectiveProxy.GetProxy(target)?.ToString() ?? "DIRECT";
logger.LogInformation("HTTP送信 {Target} 経路 {Route} 実行アカウント {User}",
    target, route, Environment.UserName);

起動時に一度、主要な宛先について「経路」「実行アカウント」を記録しておくと、8章の切り分け①〜③がログを見るだけで終わります。「ブラウザーではつながるのに」と言われたとき、アプリ側から『自分はこの設定で、この経路を使った』と言えることが、プロキシトラブルに強いアプリの条件です。

10. まとめ

  • Windowsのプロキシ設定は、WinINETのユーザー別設定・WinHTTPのマシン設定・環境変数の3系統に分かれ、どれを読むかはアプリ(のHTTPスタック)と実行アカウントで決まります。
  • WinINETは対話型アプリ用でサービスでの利用はサポートされず、サービス用途はWinHTTP(netsh winhttp)が担います。「手動なら動くのにサービスだと動かない」は、まず実行アカウントの違いを疑ってください。
  • netsh winhttp set proxyは静的設定で、PAC・自動検出・認証を扱いません。PAC運用のネットワークでは、PACを読めないクライアントの扱いを決めておく必要があります。
  • PACのFindProxyForURLはURLごとにプロキシまたはDIRECTを返します。WPADはDHCP/DNSの仕掛けがあるネットワークでだけ機能します。
  • .NET Frameworkの既定は実行アカウントのインターネットオプション(defaultProxyで上書き)、.NET(Core以降)は環境変数→ユーザープロキシ設定の順です。明示指定(HttpClientHandler.Proxy)は常に最優先です。
  • 407はプロキシ認証のエラーで、サービスアカウントで動くアプリでは「既定の資格情報」が別人になることが典型的な原因です。
  • TLSインスペクション型プロキシは社内CA証明書の配布が前提で、証明書エラーの正解は検証の無効化ではなく証明書ストアへの配布です。ピン留めされた通信は除外が必要です。
  • 切り分けは「再現→3系統の設定採取→実行アカウント特定→エラー分類→プロキシログ」の順で機械的に。アプリ側は「プロキシを設定でき、使った経路をログに残す」設計にしておくことが最善の予防です。

次に「業務アプリだけつながらない」と相談されたら、最初にこう問い返してください。

そのアプリは、誰のアカウントで動いていて、3系統のどのプロキシ設定を読むものですか。

この一問だけで、調査の入口は大きく変わります。

関連記事

関連する相談領域

合同会社小村ソフトでは、「開発機では動くのに客先ネットワークでは通信できない」「サービス化したら外部APIにつながらなくなった」といった、社内プロキシ・認証プロキシ・TLSインスペクション環境でのWindowsアプリの通信トラブル調査と、プロキシ環境を前提にした業務アプリの通信設計(設定項目・タイムアウト・ログ設計)の相談を扱っています。現象の再現手順とログの採取方法を整理するところからで構いません。

参考リンク

  1. Microsoft Learn, WinINet vs. WinHTTP. サービスや偽装・セッション分離を必要とするプロセスでなければWinINETを使うという使い分けの指針、資格情報キャッシュ・資格情報プロンプト・サービスサポート・偽装・セッション分離などの機能比較表について。  2 3 4

  2. Microsoft Learn, netsh winhttp. netsh winhttp show/set/import/resetの構文、set proxyのproxy-serverとbypass-list、import proxy source=ie、set advproxyによるJSON形式(Proxy・ProxyBypass・AutoconfigUrl・AutoDetect)の詳細プロキシ設定について。  2 3 4

  3. Microsoft Learn, About WinHTTP. WinHTTPがサービス・サーバーサイド用途向けに設計されたHTTPスタックであり、サービスアカウントでの実行と偽装をサポートする一方、ブラウザーのCookie・キャッシュ・資格情報・ユーザーのインターネットオプションを共有しないことについて。  2 3

  4. Microsoft Learn, Using a proxy with Delivery Optimization. netsh winhttp set proxyが自動検出・PAC URL・プロキシ認証をサポートしない静的設定であること、サインインユーザーがいない文脈のためのデバイス単位プロキシ構成(NetworkProxy CSP、「プロキシの設定をコンピューターごとに設定する」ポリシー)、および証明書ピン留めを使う通信がTLSインスペクションで失敗し除外が必要になることについて。  2 3 4 5 6 7 8 9 10

  5. Microsoft Learn, WinHTTP AutoProxy Support. PACスクリプトがFindProxyForURL(url, host)関数を含み、リクエストごとにプロキシのリストを計算すること、直接接続でよい場合は特別な返り値で示すこと、従来のAutoProxy APIでは自動プロキシがHTTPスタックへ自動統合されておらずアプリ側でWinHttpGetProxyForUrlを呼ぶ必要があることについて。  2 3

  6. Microsoft Learn, WinHttpGetProxyForUrl function. WPADプロトコルの実装として、PACファイルがURLごとに異なるプロキシを返し得るためURL単位で呼び出す必要があること、PAC URLの明示指定とネットワークからの自動検出の両方をサポートすることについて。  2

  7. Microsoft Learn, HttpClient.DefaultProxy Property. WindowsではHTTP_PROXY・HTTPS_PROXY・ALL_PROXY・NO_PROXY環境変数を先に読み、未定義ならユーザーのプロキシ設定を読むこと、Linuxでは環境変数がなければプロキシなしで初期化されること、NO_PROXYがワイルドカード非対応で先頭ドットによるサブドメイン一致を使うこと、プロキシURLにユーザー名・パスワードを含められることについて。  2 3 4 5 6 7 8 9

  8. Microsoft Learn, Configuring Internet Applications. .NET FrameworkでdefaultProxy要素が既定プロキシを定義し、Proxyプロパティを持たないHttpWebRequestが既定プロキシを使うこと、システムのインターネット設定と構成ファイルの設定が組み合わされ構成ファイル側が優先されることについて。  2 3

  9. Microsoft Learn, defaultProxy element (network settings). system.net/defaultProxy要素のenabled・useDefaultCredentials属性、proxy・bypasslist・module子要素、要素が空の場合はシステムのプロキシ設定が使われること、.NET 6以降への移行時はHttpClient.DefaultProxyで構成することについて。  2 3

  10. Microsoft Learn, Authentication in WinHTTP. プロキシ認証が必要な場合にステータスコード407とProxy-Authenticateヘッダーが返ること(サーバー認証は401とWWW-Authenticate)、Basic認証とKerberosなどのチャレンジ/レスポンス方式の違い、チャレンジ/レスポンス方式ではユーザー名とパスワードがネットワークを流れないことについて。  2 3

  11. Microsoft Learn, HttpClientHandler.DefaultProxyCredentials Property. UseProxyがtrueかつProxyがnullでシステム既定のプロキシを使う場合に、その既定プロキシへの認証に使う資格情報を設定するプロパティであることについて。  2

  12. Microsoft Learn, WebProxy.Credentials Property. CredentialsプロパティがHTTP 407への応答としてプロキシへ送る資格情報であること、多くのクライアントシナリオではサインイン中ユーザーの既定資格情報を使うためUseDefaultCredentialsをtrueに設定するのが推奨であることについて。  2

  13. Microsoft Learn, Understanding implications when using network intermediation to decrypt or manipulate Microsoft 365 traffic at the network layer. TLSインスペクション(SSL復号)がプロキシやファイアウォールでTLSを復号・検査・再暗号化する構成であること、エンドツーエンドのTLSを前提とするサービスで機能不全や性能劣化を招き得ること、Microsoft 365宛トラフィックをネットワーク層の復号・検査から除外する推奨について。  2 3

  14. Microsoft Learn, Make HTTP requests with the HttpClient class. HttpClient.DefaultProxyとHttpClientHandler.Proxyの2つの構成方法、Proxy指定が構成ファイルやローカルコンピューターの設定より優先されること、WPADがDNSのwpad名やDHCPを通じてPACファイル(wpad.datなど)を取得する一般的な構成、フラット名・ループバック・ドメインサフィックス一致によるローカル宛バイパス判定について。  2 3 4

  15. Microsoft Learn, WinHttpOpen function. dwAccessTypeの各値の意味。WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY(Windows 8.1以降)がシステム/ユーザーのプロキシ設定を使ってプロキシを自動決定し、フェールオーバーや認証も自動処理すること、WINHTTP_ACCESS_TYPE_DEFAULT_PROXYが8.1以降では非推奨であることについて。 

  16. Microsoft Learn, Work with existing on-premises proxy servers. アウトバウンドHTTPS通信がプロキシへのCONNECTリクエストで確立されること、成功時はHTTP 200が返り、407(認証要求)や502などの応答はプロキシが通信を許可していないことを示すため、プロキシ側チームとの切り分けに進むべきことについて。 

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

ブラウザーではつながるのに、業務アプリだけ社内プロキシを通れないのはなぜですか?
ブラウザーはWinINETのユーザー別プロキシ設定を読みますが、業務アプリが同じ設定を読むとは限りません。Windowsサービスとして動くアプリや別アカウントで動くアプリは、そのアカウントから見える設定やWinHTTPのマシン設定、環境変数を参照します。まず実行アカウントを特定し、そのアカウントから見えるプロキシ設定をnetsh winhttp show proxyとユーザー設定の両方で確認してください。同じアカウント・同じマシンでcurl.exeなどを使って再現できれば、アプリ固有の問題ではなく設定系統の食い違いと判断できます。
netsh winhttp set proxyを設定したのに、アプリの通信が変わらないのはなぜですか?
netsh winhttpが設定するのはWinHTTPのマシン既定であり、WinINETを読むブラウザーや対話型アプリ、環境変数を優先する.NET(Core以降)のHttpClientには影響しません。また、netsh winhttp set proxyは静的な設定で、PACによる自動構成や自動検出、プロキシ認証を扱いません。対象アプリがどのHTTPスタックで、どの設定系統からプロキシを解決しているかを先に確認する必要があります。
.NETアプリはどのプロキシ設定を読みますか?
.NET Frameworkは既定で実行中アカウントのインターネットオプション(WinINET相当)の設定を使い、app.configのsystem.net/defaultProxy要素で上書きできます。.NET(Core以降)のHttpClientは、HTTP_PROXY・HTTPS_PROXY・NO_PROXYなどの環境変数を先に読み、定義されていなければWindowsのユーザープロキシ設定へフォールバックします。どちらの場合も、HttpClientHandler.Proxyで明示指定すればそれが最優先になります。つまりFrameworkとCore以降では既定の解決順序が違うため、移行時にはプロキシ挙動の再確認が必要です。
407 Proxy Authentication Requiredが返るときは何を確認すべきですか?
407はプロキシ自体が認証を要求しているサインで、接続先サーバーの認証エラー(401)とは別物です。まずプロキシが要求する認証方式(Negotiate・NTLM・Basic)をProxy-Authenticateヘッダーで確認し、.NETならHttpClientHandler.DefaultProxyCredentialsやWebProxy.UseDefaultCredentialsで資格情報を渡します。サービスアカウントで動くアプリでは「既定の資格情報」がそのサービスアカウントのものになるため、対話ユーザーでは通るのにサービス化すると407になる、という事象が典型的に起こります。プロキシ側のログで、誰として認証されたかも合わせて確認してください。
TLSインスペクション型プロキシで証明書エラーになります。証明書検証を無効化してよいですか?
無効化は推奨しません。TLSインスペクション型プロキシは通信をいったん復号し、自前のCAで再署名した証明書をクライアントへ提示するため、そのCA証明書が信頼されたルートに入っていないと検証エラーになります。正しい対処は、社内CA証明書をWindowsの証明書ストア(通常はローカルコンピューターの信頼されたルート証明機関)へ配布することです。コードで検証を無効化すると、社外ネットワークで使われたときに中間者攻撃を検出できなくなり、脆弱性として残り続けます。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る