HTTPXのプロキシ設定: 認証、環境、タイムアウト

9分

HTTPXのプロキシを proxy= で設定し、資格情報を渡し、HTTPSはCONNECTのまま保ち、環境の既定と明示的なmounts、タイムアウトを分けます。

Pythonの開発者が、HTTPX 0.28.1をHTTPまたはSOCKS5プロキシ経由で出す必要があるときの案内です。私的な編集確認は、供給元の口座ではなく、ループバックオリジンとダミーのCONNECTプロキシを使いました。無料の鍵はありません。Top ProxyはIPを売りません。それらの確認は、実験室の経路だけを証明します。公開のダウンロードではありません。

01proxy= でリクエストを通す

導入したクライアントは、httpx.get と httpx.Client で proxy= を受けます。古いHTTPXは、URLパターンからプロキシURLへの proxies= マップも受け付けました。それはHTTPX自身であり、Requests専用のdictではありません。HTTPX 0.28.0(2024年11月28日)は、非推奨の proxies 引数を外しました。この手順は0.28.1の proxy= と mounts= に留まります。一つのプロキシの形は、URL文字列か httpx.Proxy オブジェクトです。

HTTPプロキシ(2026年10月14日に参照)は、そのURLをクライアント初期化またはトップレベルのヘルパーへ渡すと書いています。単一の proxy="http://127.0.0.1:PORT" は、HTTPとHTTPSの両方をそのホップへ送ります。HTTPSに https:// のプロキシURLは要りません。対象のスキームとプロキシのスキームは、別のつまみです。

  1. 実際にimportするクライアントを固定してください。この手順はHTTPX 0.28.1、httpcore 1.0.9、Python 3.14.6を使いました。
  2. HTTPプロキシURLを proxy= に入れた Client を作ってください。明示的な経路を証明するあいだは trust_env=False のままにし、残ったシェル変数が経路を奪えないようにします。
  3. 自分が制御するHTTPオリジンを取ってください。ステータスコードだけでなく、プロキシのログを読んでください。実験のダミー転送器は GET http://httpx-lab.origin:<port>/ を authorized: true で記録しました。
  4. コンテキストマネージャでクライアントを閉じてください。それがトランスポートのプールを閉じます。忘れられたクライアントは、漏れたソケットです。速いスクリプトではありません。

直接の基準が重要です。子プロセスで HTTP_PROXY、HTTPS_PROXY、ALL_PROXY、NO_PROXY を消したあと、proxy= なしのクライアントはHTTPオリジンへ当たり、ダミーのプロキシはアイドルのままでした。それがプロキシなしの対照です。それらの変数が残っていたときにHTTPXが何をするかは、教えません。それは後で来ます。

出口が二つ要るなら、proxy= に二つのURLを詰め込まないでください。パターンごとに HTTPTransport(proxy=...) の mounts を使い、verify、trust_env、retries は各トランスポートへ置いてください。このビルドでは、クライアント級の verify= 引数は付いていきませんでした。Client(verify=ctx) だけを継承したマウントは、ConnectError / SSLCertVerificationError: unable to get local issuer certificate でTLSに失敗しました。同じリクエストは、トランスポート自身が verify=ctx を受けたときに成功しました。

02HTTPS対象にHTTPプロキシを選ぶ

人がこれを「HTTPSプロキシ」と呼ぶのは、サイトが https:// だからです。前のホップは、いまも CONNECT を話すHTTPプロキシです。文書はその落とし穴を指摘しています。mountsのdictの https:// キーは対象のスキームであり、その前のプロキシURLはたいてい http:// です。

転送とトンネリングは、同じ仕組みではありません。http:// では、プロキシがオリジン要求を作り、オリジンのバイトを返します。https:// では、クライアントが CONNECT host:443 を送り、プロキシがTCPを開き、そのトンネル越しにクライアントとオリジンのあいだでTLSが走ります。バイトのトンネルが動いていないのにCONNECTが返す偽の200は、HTTPSプロキシではありません。実験は、HTTPでは本物のオリジン本文を、CONNECTのあとは本物のTLSバイトを転送しました。

  1. 対象が https://httpx-lab.origin:<port>/ でも、プロキシURLは http://127.0.0.1:<port> のままにしてください。
  2. オリジンは ssl.create_default_context(cafile=ca.pem) で信頼し、そのコンテキストを verify= として渡してください。検証は切らないでください。
  3. 200を祝う前に、プロキシのログが CONNECT httpx-lab.origin:<https-port> を示すことを確認してください。
  4. 既定のCAストアと実験CAなしでもう一度繰り返してください。欲しいのは、CONNECTは記録しつつTLSが失敗することです。

最後の手順が、運用者が飛ばすものです。この実行では、CONNECTは成功し、そのあとHTTPXは ConnectError を上げ、中身は SSLCertVerificationError: unable to get local issuer certificate でした。トンネルは問題ありませんでした。証明書が問題でした。ブラウザの自分のIPは、このクライアントではなく、ブラウザの出口を示したはずです。HTTPXを向ける前に、ホストがHTTPまたはSOCKS5に応答するかをプロキシチェッカーで見てください。

SOCKS5は三番目の経路です。pip install httpx[socks] は socksio 1.0.0 を引きました。0.28.1の httpx.Proxy は socks5 と socks5h を受けます。制御したSOCKS5フィクスチャに対し、socks5://127.0.0.1:<port> から http://127.0.0.1:<origin>/ は ATYP ipv4 を送りました。同じクライアントで socks5h:// とホスト名の対象は、ATYP domain httpx-lab.origin を送りました。そのホスト名で socks5:// を使っても、ATYP domain を送りました。このビルドは、一部のcurl socks5 構成のようにホスト名をローカルで解決してからIPv4アドレスを送ることはしませんでした。Requestsのsocks5hの言い伝えをHTTPXへ写さないでください。ハンドシェイクを測ってください。

03プロキシ認証を設定する

プロキシの資格情報は auth= ではありません。auth= はオリジン認証です。混ぜると、間違ったホップを一時間デバッグすることになります。

プロキシのページは、プロキシURLへuserinfoを置きます。APIは httpx.Proxy(url, auth=(user, password)) も文書化しています。実験では両方動きました。ダミーのパスワードには @、:、/、#、% が含まれていました。それらの文字は、コンポーネントごとのURLエンコードのあとにだけuserinfoへ入るか、URL解析を通らない Proxy の auth= タプルへ入ります。ログと図は値を伏せています。デバッグのときに Proxy-Authorization を印刷しないでください。

無い資格情報と間違った資格情報は、一つの動きではありません。

HTTP転送では、プロキシユーザーなしのリクエストは、フィクスチャからHTTP 407を返しました。HTTPXはそれを status_code == 407 として出しました。間違ったパスワードも同じでした。フィクスチャは authReason: missing と authReason: wrong を記録しました。例外はありません。ラッパーがあとで投げるなら、407を残してください。

HTTPS CONNECTでは、同じ無いまたは間違ったダミー資格情報は、メッセージ 407 Proxy Authentication Required の httpx.ProxyError を上げました。読む応答オブジェクトはありませんでした。CONNECTはトンネルの準備です。そこでの407は転送の失敗であり、オリジンのページではありません。

正しい資格情報は、両方の経路でオリジン200を返しました。オリジンは Proxy-Authorization を見ませんでした。そのヘッダーはプロキシホップに留まります。オリジンのBasicも要るなら、クライアントへ auth=(origin_user, origin_pass) を送ってください。実験の /origin-auth 経路は、無しでは401、両方のホップが認証されると200を返しました。

  1. パスワードに予約文字があるときは、プロキシのユーザーとパスワードを httpx.Proxy(..., auth=(user, password)) へ入れてください。
  2. オリジンの auth= はウェブサイト用に残し、プロキシ用にはしないでください。
  3. HTTP 407とCONNECTの ProxyError は、別の結果として扱ってください。実際に捕捉したクラスを記録してください。
  4. 「curlでは動く」が、HTTPXが同じヘッダーを送った証明だという考えは捨ててください。このクライアントで証明してください。

レジデンシャル製品も同じ分け方を書いています。Oxylabs Residential Proxies(2026年10月14日に参照)は、HTTP、HTTPS、HTTP/3、SOCKS5を列挙し、ユーザー名とパスワードまたはIP許可リスト、入口 pr.oxylabs.io:7777 で customer-USERNAME:PASSWORD を使います。それは文書化された端点の形であり、プールの試験ではありません。Decodoのレジデンシャルクイックスタート(同日)は gate.decodo.com:7000 と、一つのHTTPプロキシURLで http と https の両方を送るPythonのマップを示します。英語カタログは、その製品をまだ smartproxy-com として載せています。文書上の公開名はDecodoです。経路は変わっていません。

04環境の既定とmountsを理解する

HTTPXは、既定で環境を読みます。環境変数(2026年10月14日に参照)は HTTP_PROXY、HTTPS_PROXY、ALL_PROXY、NO_PROXY に加え、SSL_CERT_FILE と SSL_CERT_DIR を列挙しています。trust_env=False はその一式をオフにします。「証明書の確認を飛ばす」ではありません。

子の実験プロセスは、それらの変数を外し、CGIの残り、REQUESTS_CA_BUNDLE、CURL_CA_BUNDLE、SSLKEYLOGFILE も外して始まりました。空の私的 NETRC ファイルを使いました。ワークステーションの HOME はそのままです。各環境実験は、その基準から始まり、名前の付いたダミー値だけを注入しました。

ループバックの迂回は嘘を付きます。HTTPクライアントは、しばしば 127.0.0.1 を飛ばします。環境プロキシのケースはホスト名 httpx-lab.origin と NO_PROXY="" を使い、その名前をループバックへ写す範囲付きの getaddrinfo パッチを付けました。そのパッチは注入したリゾルバの動きです。公開DNSの測定ではありません。

  1. HTTP_PROXY と HTTPS_PROXY を、エンコードしたプロキシURLへエクスポートしてください。対象がプロキシを使わなければならないときは、NO_PROXY を空文字にしてください。
  2. proxy= 引数なしで Client(trust_env=True) を作ってください。実験のリクエストは200を返し、プロキシ alpha だけがGETを記録しました。プロキシ bravo はアイドルのままでした。
  3. NO_PROXY=httpx-lab.origin でもう一度繰り返してください。オリジンはまだ200を返しました。alphaはそのGETを見ませんでした。それが迂回であり、専用のケースです。副作用として仮定するものではありません。
  4. 同じ環境変数と trust_env=False でもう一度繰り返してください。リクエストは直接行きました。alphaは何も見ませんでした。

明示的な proxy= は、衝突する環境に勝ちました。HTTP_PROXY にalphaがあり、proxy= がbravoを指すと、bravoがGETを受け、alphaは受けませんでした。ステータス200だけでは、どのホップが使われたかは分かりません。二つの観測用の識別が分かりました。

trust_env=False は、SSL_CERT_FILE がクライアントのCAストアになるのも止めました。実験CAを SSL_CERT_FILE に入れ trust_env=True だと、実験オリジンへのHTTPSは200を返しました。trust_env=False と既定の verify=True だと、同じURLは ConnectError / SSLCertVerificationError を上げました。その trust_env=False クライアントへ verify=ssl.create_default_context(cafile=ca.pem) を渡すと、200が戻りました。検証はオンのままです。環境の検索だけが消えました。

transport= と mounts= は、別のコンストラクタ引数です。HTTPX 0.28.1は allow_env_proxies = trust_env and transport is None を設定します。独自の transport= オブジェクトは、環境のプロキシマウントを飛ばします。mounts= のdictは飛ばしません。クライアントは先に環境マウントを作り、そのマップを自分のmountsで更新します。より具体的なURLパターンが勝ちます。HTTP_PROXY からの http:// は all:// に勝つので、Client(mounts={"all://": some_transport}, trust_env=True) でも、HTTP対象を継承したプロキシへ送ることがあります。環境の経路を除外しなければならないときは、trust_env=False にしてください。マウントした各 HTTPTransport へ proxy= と verify= を置いてください。クライアント級の verify= は、まだそれらのトランスポートへコピーされません。最後のその見逃しが、信頼しないCAの失敗を戻してきます。

05同期と非同期のライフサイクルを試す

同期と非同期は、二つのクライアントです。proxy= の形は共有します。接続プールは共有しません。

with httpx.Client(proxy=..., verify=ctx) as client: が同期の形です。閉じるのはコンテキストの終了です。async with httpx.AsyncClient(proxy=..., verify=ctx) as client: が非同期の形です。await client.get(...) します。非同期クライアントの中で await なしの get を混ぜるのはバグであり、タイムアウトではありません。

この実験の非同期ケースは、同じHTTPプロキシURLと実験のSSLコンテキストで AsyncClient を使いました。await client.get(https_url) は、HTTPSオリジンの印付きで200を返しました。プロキシはCONNECTを記録しました。それが成功した一つの非同期トンネルです。並行のベンチマークではなく、負荷下のプール再利用についても何も言いません。

マウントしたトランスポートも、同じ閉じ方を必要とします。手で作った HTTPTransport は、クライアントが mounts 経由で所有するなら、クライアントが閉じます。トランスポート変数をクライアントをまたいで生かすなら、finally で transport.close() を呼んでください。実験は、再試行とマウントのケースでそれをしました。

再試行はトランスポートに属します。Transports(2026年10月14日に参照)は、HTTPTransport(retries=...) が ConnectError と ConnectTimeout を再試行すると書いています。429や503は再試行しません。実験は retries=2 を設定し、/status/503 を取りました。オリジンは1回のヒットを数えました。HTTPXは503を返しました。それはurllib3の Retry ではありません。allowed_methods の言い伝えをHTTPXへ貼らないでください。

06接続、本文読み、TLSの失敗を診断する

捕捉した例外に名前を付けてください。HTTPXには本物の階層があります。すべてを「プロキシエラー」へ潰すと、失敗したホップが隠れます。

閉じたループバックリスナーは、ConnectError を上げ、中身は ConnectionRefusedError: [Errno 61] Connection refused でした。それが拒否です。接続タイムアウトではありません。このDarwinカーネルでは、bindして保持しただけでlistenしなかったソケットは拒否しませんでした。HTTPXは、設定した接続期限のあと、TimeoutError を包む ConnectTimeout を上げました。その待ちは、bindしてlistenしない注入フィクスチャの動きとして印を付けています。公開インターネットの測定ではありません。

readタイムアウトは、応答が始まったあとのチャンクについてのものです。オリジンの /slow-body は完全なヘッダーをすぐに送り、1バイトの本文を書いてフラッシュし、それから Timeout(read=0.45) より長く待ちました。本文をストリームすると、HTTPXはステータス200を返し、first-byteイベントが発火し、1バイトが届き、それから TimeoutError を包む ReadTimeout でした。ヘッダーはすでに着いていました。ConnectError は間違ったクラスだったはずです。ConnectTimeout も間違ったクラスだったはずです。ストリームせず response.text だけを呼んでもタイムアウトしますが、ヘッダーが着いた証明は失います。

CONNECTのあとのTLS失敗は、SSLCertVerificationError 付きの ConnectError のままでした。プロキシはすでにトンネルを受けていました。verify=False にすると、それが隠れます。しないでください。

仕事がISP割り当ての出口なら、モバイルプロキシまたはレジデンシャルプロキシを使ってください。プロトコル対応とIPの出自は、別の次元です。この実験は、国、成功率、口座を主張していません。

失敗0.28.1の実験が上げたものそうではないもの
HTTPプロキシ、無いまたは間違ったBasic応答ステータス407CONNECTの例外
HTTPS CONNECT、無いまたは間違ったBasicProxyError("407 Proxy Authentication Required")オリジンの407ページ
実験CAなしの実験HTTPSCONNECTのあとの ConnectError / SSLCertVerificationErrorCONNECTが起きなかった証明
閉じたループバックポートConnectError / ConnectionRefusedErrorConnectTimeout
DarwinでbindしてlistenしないConnectTimeout(フィクスチャ固有の待ち)拒否、またはWANのRTT
最初のバイトのあとの遅延本文iter_bytes() を回しているあいだの ReadTimeout接続失敗
retries=2 付きのオリジン /status/503ステータス503、オリジンへのヒットは1回アプリケーションの再試行
2026年10月14日のHTTPX 0.28.1ループバック実験からの検証表

図はこの実行の検証表です。カバー画像ではなく、チェッカーUIのスクリーンショットでもありません。

次のブロックは、模式的なAPIのスケッチです。ポート、CAパス、パスワードは、自分が制御する値へ置き換えるプレースホルダです。公開のフィクスチャ一式ではなく、空のディレクトリからは動きません。

import httpx
import ssl

# PLACEHOLDERS. Do not treat these literals as lab values.
ctx = ssl.create_default_context(cafile="PATH/TO/LAB_CA.pem")
proxy = httpx.Proxy(
    "http://127.0.0.1:PROXY_PORT",
    auth=("PROXY_USER", "PROXY_PASSWORD"),
)
timeout = httpx.Timeout(5.0, connect=2.0, read=0.45)

with httpx.Client(proxy=proxy, verify=ctx, trust_env=False, timeout=timeout) as client:
    http_response = client.get("http://TARGET_HOST:HTTP_PORT/")
    https_response = client.get("https://TARGET_HOST:HTTPS_PORT/")

この記事の0.28.1の例外クラスと、CONNECT対407の結果は、私的な編集フィクスチャから来ています。そのフィクスチャはここには公開していません。python -m pip install "httpx[socks]==0.28.1" でimportできるクライアントを入れてください。httpcore 1.* に依存し、SOCKSならsocksioにも依存します。HTTPプロキシのホスト、ポート、資格情報をプレースホルダへ入れ、パスワードに予約文字があるときは httpx.Proxy(..., auth=(user, password)) を使ってください。転送には自分が制御するオリジンのHTTP URLを、CONNECTのあとのTLSには自分が制御するオリジンのHTTPS URLを向けてください。HTTPSでは、そのオリジンですでに信頼しているCAファイルで verify= を ssl.create_default_context(cafile=...) にしてください。明示的な proxy= の経路を証明するあいだは trust_env=False を保ってください。verify=False は設定しないでください。

07文書化された端点を選ぶ

クライアントの経路が正直になったら、HTTP転送とCONNECTを文書化している入口ホストを選んでください。OxylabsとDecodo(まだ smartproxy-com として載っている)の英語カタログページは、2026年10月14日に生きた自己カノニカルURLを返しました。公式文書は、ユーザー名とパスワード、または許可したIPのHTTPゲートウェイを説明しています。それは紙の上の端点適合です。この記事はどちらの製品にもログインしておらず、順位も付けません。

Oxylabs — レジデンシャルとデータセンタープロキシ
Oxylabs — レジデンシャルとデータセンタープロキシ4.0771

Oxylabsはレジデンシャル、モバイル、ISP、データセンタープロキシを、月$30からのひとつの月次契約で売っています。支払いはカード、電信送金、AliPay、PayPalです。

10か国
$0.45
1GBあたり
Decodo(Smartproxy) — レジデンシャルとデータセンタープロキシ
Decodo(Smartproxy) — レジデンシャルとデータセンタープロキシ4.22118

Decodo(旧Smartproxy)はレジデンシャルとデータセンタープロキシを売っています。レジデンシャルは月次プランなしで1GBあたり$4、または3 GBから1GBあたり$3.75の月次パックです。支払いはカード、PayPal、Alipay、Google/Apple Pay、暗号資産(制限あり)です。

83か国
$0.38
1GBあたり

ブラウザの確認だけが要るなら、自分のIPに留まってください。HTTPXが要るなら、このライブラリのプロキシログと例外クラスを信じてください。ブログ記事が認めるより、二つはよく食い違います。

HTTPXのプロキシはどう設定しますか?

httpx.Client または httpx.get へ proxy= を渡してください。URL文字列か httpx.Proxy を使います。HTTPX 0.28.1 に proxies= 引数はありません。HTTPXは0.28.0でその非推奨マップを外しました。スキームごとの経路には、それぞれが自分の proxy と verify 引数を受ける HTTPTransport オブジェクトをマウントしてください。trust_env=True のとき、mounts= はまだ環境のパターンをマージします。継承した HTTP_PROXY を適用してはいけないときは、transport= または trust_env=False を使ってください。

HTTPSを扱うプロキシに http:// を使うのはなぜですか?

プロキシホップは、たいてい素のHTTPです。HTTPS対象はそのホップ越しにCONNECTを使い、それからオリジンへTLSします。mountsの https:// キーは対象のスキームです。その前のプロキシURLは、製品がプロキシ自身へTLSを話すとき以外、http:// のままです。

プロキシの資格情報は auth= とどう違いますか?

プロキシのuserinfo、または httpx.Proxy(auth=(user, password)) は、プロキシへ Proxy-Authorization を送ります。auth= はオリジン認証です。実験は、プロキシ資格情報が間違っているとプロキシ407または ProxyError を返し、オリジン資格情報だけが無いとオリジン401を返しました。

trust_env=False は証明書の検証を無効にしますか?

しません。trust_env=False は、HTTPXが HTTP_PROXY、NO_PROXY、SSL_CERT_FILE、SSL_CERT_DIR を読むのを止めます。TLS検証はオンのままです。HTTPXは trust_env が真だからといって、netrcからオリジン資格情報を読みません。自分で httpx.NetRCAuth を渡すなら、その認証は trust_env=False でも適用されます。私的CAが要るときは、verify= として ssl.create_default_context(cafile=...) を渡してください。verify=False は、環境処理のスイッチではありません。

ヘッダーが着いたあとにクライアントが ReadTimeout を上げるのはなぜですか?

readタイムアウトは、チャンクごとの本文期限です。実験オリジンはヘッダーと1バイトの本文を送り、それから止まりました。ストリームはステータス200を返し、そのバイトを届け、それから ReadTimeout を上げました。それは ConnectTimeout ではなく、プロキシと話せなかった失敗でもありません。

HTTPTransport は 429 や 503 を再試行しますか?

しません。HTTPTransport の retries は ConnectError と ConnectTimeout だけを再試行します。retries=2 でも、実験オリジンの /status/503 は1回当たり、HTTPXは503を返しました。ステータスの再試行が要るなら、別のライブラリを使ってください。