Skip to content

Guides · Connect and debug

HTTPX proxy setup for sync and async

Connect Python HTTPX through an authenticated HTTP proxy. Start with one HTTPS request, then use the same configuration in AsyncClient with explicit timeouts and response cleanup.

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.

Install the version used belowsh
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

proxy_settings.pypython
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

httpx_check.pypython
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 None

Run 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

httpx_async_check.pypython
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.

Results from the controlled HTTPX fixture
CheckResult
Client and AsyncClient with password punctuationThe proxy accepted authentication; the origin received neither proxy credentials nor a destination Authorization header.
Conflicting environment proxy and NO_PROXYBoth snippets still used the explicitly configured proxy.
Wrong password during CONNECTProxyError; the fixture returned 407 before the origin received a request.
Untrusted certificateConnectError; verification remained enabled.
Stalled response and async deadlineReadTimeout and TimeoutError respectively; response and client closure were checked.
These results validate the named client versions and local fixture. They do not measure live gateway reliability, exit-country accuracy, performance, SOCKS support or TLS connections to the proxy itself.

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.

HTTPX proxy setup for sync and async · Portproof