Short answers
How do I configure an HTTPX proxy?
Pass proxy= to Client or AsyncClient. Use an httpx.Proxy object with an auth tuple to keep the gateway address separate from its username and password.
Why does an HTTPS request use an http:// proxy?
The proxy URL describes the connection to the gateway. An HTTP gateway can open a CONNECT tunnel for an HTTPS destination; the client then verifies that destination’s TLS certificate.
Why does HTTPX reject the proxies argument?
HTTPX 0.28 removed the deprecated proxies= argument. For one gateway use proxy=. Older examples using Requests-style dictionaries are not interchangeable with this interface.
Already using aiohttp? Its session and proxy arguments differ: use the aiohttp proxy setup guide for that client.
Use the right interpreter and credentials
These examples use Python 3.11 or newer and HTTPX 0.28.1. Install the tested version in your project environment. The release notes explain the removed argument; check your dependency lock before adapting an older application.
python -m pip install "httpx==0.28.1"
python -c "import httpx; print(httpx.__version__)"Copy the complete generated username from the connection builder into your runtime’s PROXY_USER variable. Choose a pool and country currently available. Use the proxy password when prompted, not your website password or API key. Save the following three files together; run either check, not both for every job.
Keep gateway authentication separate
import getpass
import os
import ssl
from urllib.parse import urlsplit
import certifi
import httpx
def connection_settings():
server = os.environ.get("PROXY_SERVER", "http://gw.portproof.org:7000")
address = urlsplit(server)
if (address.scheme != "http" or not address.hostname
or address.port is None or address.username is not None
or address.password is not None or address.path not in ("", "/")
or address.query or address.fragment):
raise SystemExit("Use an HTTP gateway URL without credentials or a path")
proxy = httpx.Proxy(
server,
auth=(os.environ["PROXY_USER"], getpass.getpass("Proxy password: ")),
)
tls = ssl.create_default_context(
cafile=os.environ.get("PROXY_CA_BUNDLE") or certifi.where()
)
return {
"proxy": proxy,
"verify": tls,
"trust_env": False,
"follow_redirects": False,
"timeout": httpx.Timeout(10.0, connect=5.0, pool=2.0),
}The Proxy configuration accepts raw credentials separately, so password punctuation does not become URL syntax. Do not put proxy credentials in destination auth= or log this configuration. For unattended jobs, replace the prompt with your existing secret-manager lookup; keep passwords out of command arguments and source files.
trust_env=False makes routing explicit. HTTPX will not choose a different gateway from HTTP_PROXY, HTTPS_PROXY or NO_PROXY. Certificate trust is explicit too: this code uses certifi, or a trusted bundle you supply as PROXY_CA_BUNDLE. It does not adopt HTTPX’s SSL_CERT_FILE setting. Preserve your organisation’s approved network and certificate configuration. See the environment reference.
Check one request with Client
import json
import httpx
from proxy_settings import connection_settings
settings = connection_settings()
try:
with httpx.Client(**settings) as client:
with client.stream("GET", "https://api.portproof.org/v1/echo-ip") as response:
response.raise_for_status()
body = bytearray()
for chunk in response.iter_bytes(chunk_size=8192):
if len(body) + len(chunk) > 65536:
raise ValueError("Echo response exceeds 64 KiB")
body.extend(chunk)
payload = json.loads(body)
if not isinstance(payload, dict) or not isinstance(payload.get("ip"), str):
raise ValueError("Expected an IP address in the echo response")
print("HTTP", response.status_code, "exit", payload["ip"])
except (httpx.HTTPError, ValueError) as error:
raise SystemExit(f"Check failed: {type(error).__name__}") from NoneRun python httpx_check.py. The status and exit address describe this echo request only. Redirects are disabled, there are no automatic retries, and the code stops collecting decoded response content above 64 KiB. That response limit is not a proxy billing measurement. The managed response and client close even when parsing fails.
Use AsyncClient in an async application
import asyncio
import json
import httpx
from proxy_settings import connection_settings
settings = connection_settings()
async def main():
try:
async with asyncio.timeout(20):
async with httpx.AsyncClient(**settings) as client:
async with client.stream("GET", "https://api.portproof.org/v1/echo-ip") as response:
response.raise_for_status()
body = bytearray()
async for chunk in response.aiter_bytes(chunk_size=8192):
if len(body) + len(chunk) > 65536:
raise ValueError("Echo response exceeds 64 KiB")
body.extend(chunk)
payload = json.loads(body)
if not isinstance(payload, dict) or not isinstance(payload.get("ip"), str):
raise ValueError("Expected an IP address in the echo response")
print("HTTP", response.status_code, "exit", payload["ip"])
except (httpx.HTTPError, ValueError, TimeoutError) as error:
raise SystemExit(f"Check failed: {type(error).__name__}") from None
if __name__ == "__main__":
asyncio.run(main())Run python httpx_async_check.py. Inside an existing event loop, await main() instead of calling asyncio.run. The password prompt happens before the timed async work; it is not subject to that deadline. An unattended application should obtain its secret before starting the job.
HTTPX’s timeouts limit connection, read, write and pool waits. A ten-second read timeout is a wait for data, not a whole-download deadline. The async example also requests cancellation after twenty seconds using asyncio.timeout; cleanup can take additional time. Give the synchronous job an overall deadline in its worker or scheduler.
What the local checks establish
We ran these snippets with Python 3.12.14, HTTPX 0.28.1 and httpcore 1.0.9 on 30 September 2026. A local authenticated HTTP proxy carried CONNECT tunnels to a local HTTPS origin. Credentials and certificates were synthetic; no live proxy pool was involved.
| Check | Result |
|---|---|
| Client and AsyncClient with password punctuation | The proxy accepted authentication; the origin received neither proxy credentials nor a destination Authorization header. |
| Conflicting environment proxy and NO_PROXY | Both snippets still used the explicitly configured proxy. |
| Wrong password during CONNECT | ProxyError; the fixture returned 407 before the origin received a request. |
| Untrusted certificate | ConnectError; verification remained enabled. |
| Stalled response and async deadline | ReadTimeout and TimeoutError respectively; response and client closure were checked. |
Separate connection reuse from rotation
Reuse a scoped client for related requests rather than creating one inside every iteration; the async guide explains its lifetime. A client can reuse an existing connection, so one HTTPX call does not mean one new exit address. Gateway rotation and sticky settings belong in the generated username. See rotation and sessions.
For a ProxyError, check gateway authentication and the CONNECT failure guide. A destination status rejected by raise_for_status() becomes HTTPStatusError; it is a different stage. Keep TLS verification enabled when diagnosing ConnectError. Respect access rules and rate limits, and bound any later retries to permitted reads. If your project uses Requests instead, use the separate Requests setup guide.
After the echo works, try one authorised application request with the same settings. Measure a representative run before buying traffic; the bandwidth guide and current pricing help turn that measurement into an order.
What is not allowed
Use these examples for authorised QA, monitoring and research that respects the destination’s terms. See the acceptable-use policy.
Read next
Python Requests proxy setup
A complete Python check with HTTP and SOCKS5, predictable settings and a practical failure checklist.
Read the guide
Fix CONNECT tunnel failed: 403, 502 and 503
Read the proxy and destination statuses separately, then choose the next check for 403, 407, 502 or 503.
Read the guide