cheat sheet
httpx
Package-level reference for httpx on PyPI — install variants, version policy, extras, transport options, and alternatives.
httpx
What it is
httpx is a full-featured HTTP client for Python created by Tom Christie (also the author of Starlette and Django REST framework) and maintained under the encode GitHub organization. It offers a requests-compatible synchronous API plus a true async/await client built on httpcore and anyio, with optional HTTP/2 support.
Reach for httpx when you need any combination of: async I/O (inside FastAPI, Litestar, or asyncio jobs), HTTP/2, fine-grained timeout control, or a single library that works in both sync and async code paths. The sync API is close enough to requests that most code ports with import-line changes only.
Install
pip install httpx
Output: (none — exits 0 on success)
uv add httpx
Output: dependency resolved + added to pyproject.toml
poetry add httpx
Output: updated lockfile + virtualenv install
pip install "httpx[http2]" # HTTP/2 transport via h2
pip install "httpx[socks]" # SOCKS proxy support
pip install "httpx[cli]" # bundled `httpx` CLI (rich-printed responses)
pip install "httpx[brotli,zstd]" # extra response decoders
Output: core httpx plus the named optional dependencies
Versioning & Python support
- Current line is the
0.xseries (as of mid-2026). The project has not declared a1.0despite production-grade stability — minor releases (0.27,0.28, …) can include small breaking changes, so pin a minor range in production. - Supports Python 3.9+ on recent releases.
- The
1.0milestone is a long-standing project goal; expect a stable API once it lands.
Package metadata
- Maintainer: Tom Christie /
encodeorg on GitHub - Project home: github.com/encode/httpx
- Docs: python-httpx.org
- PyPI: pypi.org/project/httpx
- License: BSD-3-Clause
- Governance: community-maintained, commercial sponsorship via Encode
- First released: 2019
- Downloads: tens of millions per month; standard async HTTP client across the FastAPI ecosystem
Optional dependencies & extras
httpx[http2]— installsh2for HTTP/2 transport. Required forhttpx.Client(http2=True).httpx[socks]— installssocksiofor SOCKS4/5 proxy URLs.httpx[cli]— installsclick+rich+pygmentsto enable the bundledhttpxCLI command.httpx[brotli]/httpx[zstd]— addsbrotli/zstandarddecoders forContent-Encoding: brandzstdresponses.
Core dependencies pulled in automatically:
httpcore— low-level transport (connection pooling, retries)anyio— async runtime adapter (asyncio / trio compatible)certifi— bundled CA certificate storeidna— internationalized domain namessniffio— detect the running async backend
Alternatives
| Package | Trade-off |
|---|---|
requests | Sync-only; broader ecosystem and SDK assumptions. Use for blocking scripts and integrations. |
aiohttp | Async client + server combined; older, more bespoke API. Heavier than httpx for client-only use. |
urllib3 | Lower-level — direct connection-pool control. Use when you need wire-level fine tuning. |
niquests | Drop-in requests fork with HTTP/2, HTTP/3, async. Use when you want the requests API with modern transport. |
urllib (stdlib) | Zero-dependency. Use only when you cannot add a third-party package. |
Common gotchas
- Pre-1.0 versioning means minor releases can break. Pin a tight range (
httpx>=0.27,<0.28) in libraries; let applications float more freely but watch the changelog. - HTTP/2 is opt-in.
Client(http2=True)needs theh2extra installed AND the server to ALPN-negotiate it. Without both, you silently get HTTP/1.1. - Default timeout is 5 s on all phases. Unlike
requests(no default), httpx returnsReadTimeoutquickly. Override withtimeout=httpx.Timeout(connect=5.0, read=30.0). ClientandAsyncClientare separate types. A syncClientcannot be awaited; anAsyncClientcannot be used from sync code withoutasyncio.run(). Be explicit about which mode each call site is in.- Connection pool is shared per
Client, not per host. Default pool is 10 keepalive + 100 max. Tune viahttpx.Limits(max_keepalive_connections=N, max_connections=M). certifiCA bundle, not system trust store. Same caveat asrequests— override withverify="/path/to/ca.pem"or setSSL_CERT_FILE.- Cookie persistence differs from requests. httpx Cookies are per-
Client; one-shothttpx.get()does not persist cookies.
Real-world recipes
Worked examples that target the specific value of httpx over requests — async concurrency, HTTP/2 multiplexing, and streaming. The API-style recipes (auth flow, JSON parsing) are in the companion language-section article; here we focus on transport, pool, and runtime concerns.
Recipe 1 — Reusable async client tuned for high concurrency.
import httpx
limits = httpx.Limits(max_keepalive_connections=20, max_connections=200, keepalive_expiry=30.0)
timeout = httpx.Timeout(connect=5.0, read=30.0, write=10.0, pool=5.0)
async def make_client() -> httpx.AsyncClient:
return httpx.AsyncClient(
http2=True,
limits=limits,
timeout=timeout,
headers={"User-Agent": "myapp/1.0"},
follow_redirects=True,
)
Output: client object; reuse across the application lifetime and close on shutdown.
Recipe 2 — Concurrent fan-out with bounded parallelism.
import asyncio, httpx
async def fetch_all(urls):
sem = asyncio.Semaphore(20)
async with httpx.AsyncClient(http2=True) as client:
async def one(u):
async with sem:
r = await client.get(u, timeout=10)
return r.status_code, r.text[:200]
return await asyncio.gather(*(one(u) for u in urls))
Output: at most 20 in-flight requests; gather returns the list of (status, body) tuples.
Recipe 3 — Streaming a large download to disk without buffering the whole body.
async def download(client: httpx.AsyncClient, url: str, path: str):
async with client.stream("GET", url, timeout=httpx.Timeout(60, read=None)) as r:
r.raise_for_status()
with open(path, "wb") as f:
async for chunk in r.aiter_bytes(chunk_size=64 * 1024):
f.write(chunk)
Output: file written incrementally; memory footprint stays at chunk size regardless of file size.
Recipe 4 — Custom transport for retry-on-connect.
transport = httpx.AsyncHTTPTransport(retries=3, http2=True)
client = httpx.AsyncClient(transport=transport)
Output: connect-level retries (NOT response-status retries) are wired in; for status-code retries, use tenacity or stamina around the call.
Performance tuning
The biggest perf knobs are pool sizing, HTTP/2 multiplexing, and transport reuse. Misconfigured, an async client will be slower than requests because it pays the asyncio overhead without amortizing it across concurrent calls.
- Reuse one
AsyncClientper process. Per-request clients pay full TLS handshake every time; in benchmarks this is a 5-10× slowdown. - HTTP/2 helps when fanning out to one host. Many concurrent requests to one origin multiplex over a single TCP stream; HTTP/1.1 pipelining is unreliable in practice, so HTTP/2 is the only way to avoid head-of-line blocking. To many different hosts, HTTP/2 makes no difference — each host still gets its own connection.
- Tune
httpx.Limitsto your workload. Defaultmax_connections=100, max_keepalive_connections=20is fine for a small worker; for an aggregator hitting many hosts, bump both. For a microservice serving a single backend, lowermax_connectionsto match the backend's worker count. keepalive_expirycontrols idle-connection close. Default 5 s is short — a 30-60 s value reduces handshake churn for low-RPS services.http1andhttp2toggles onClientdecide ALPN negotiation. Disable HTTP/1.1 (http1=False, http2=True) only when you control the upstream.- Buffer reads with
iter_bytesandchunk_size=64*1024— the default chunk size is small and wastes scheduler turns on large bodies. - Use
httpx[brotli,zstd]when targeting modern servers — better compression than gzip, less CPU on decode.
Version migration guide
httpx is still pre-1.0 and minor releases sometimes change behavior. Pin tight and read the changelog before bumping. The list below collects the gotchas that have bitten users since the 0.18 series.
0.18 → 0.19—Client.send()no longer auto-converts strings toURLobjects in some paths; passhttpx.URL(...)explicitly.0.20 → 0.21—Response.historysemantics aligned withrequests; redirect chains now include the final response correctly.0.22 → 0.23— defaultproxiesargument deprecated in favor ofproxy; sync clients no longer accept a dict of proxies — pass a single URL.0.24 → 0.25—Client(app=...)for ASGI/WSGI in-memory testing moved toClient(transport=httpx.ASGITransport(app=...)).0.27 → 0.28—verifyno longer acceptsTrue/Falsestrings; pass real booleans or a path.AsyncClientclose semantics tightened — leaking a client now warns.
# Before (0.24-)
client = httpx.AsyncClient(app=my_app, base_url="http://test")
# After (0.25+)
transport = httpx.ASGITransport(app=my_app)
client = httpx.AsyncClient(transport=transport, base_url="http://test")
Output: same in-memory ASGI behavior with the new transport-explicit form.
Testing & CI integration
Async HTTP tests need either a real fake server or a transport-level mock. respx is the canonical mocking layer; ASGITransport/WSGITransport let you call your own app in-memory without a network port.
# pip install respx pytest-asyncio
import httpx, respx, pytest
@pytest.mark.asyncio
async def test_fetch():
async with respx.mock(base_url="https://api.example.com") as mock:
mock.get("/users/42").respond(200, json={"id": 42, "name": "Alice Dev"})
async with httpx.AsyncClient(base_url="https://api.example.com") as c:
r = await c.get("/users/42")
assert r.json()["name"] == "Alice Dev"
assert mock["/users/42"].call_count == 1
Output: test passes; no socket opened, route assertions enforced.
# In-memory ASGI testing for a FastAPI / Starlette app
import httpx
from myapp import app # ASGI application
async def test_app():
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as c:
r = await c.get("/health")
assert r.status_code == 200
Output: the app handles the request without a uvicorn process.
Troubleshooting common errors
| Error / Symptom | Likely cause | Fix |
|---|---|---|
httpx.ConnectTimeout | Network reach or DNS slow | Increase the connect timeout; check name resolution. |
httpx.ReadTimeout | Server slow or hung | Increase read half of httpx.Timeout. |
httpx.PoolTimeout | All pool slots busy | Raise Limits.max_connections, lower client-side concurrency, or set pool timeout to None. |
httpx.RemoteProtocolError: Server disconnected without sending a response | Server closed mid-handshake | Retry with backoff; check upstream health. |
RuntimeError: This event loop is already running | Calling asyncio.run inside a running loop | Use await directly; or wrap with nest_asyncio only in Jupyter. |
httpx.UnsupportedProtocol: Request URL is missing an 'http' or 'https' protocol | Bare path passed | Prepend the scheme or set Client(base_url=...). |
HTTP/2 not negotiated despite http2=True | h2 not installed | pip install "httpx[http2]". |
| Cookies not persisted between requests | One-shot httpx.get() used | Switch to a Client/AsyncClient context. |
ssl.SSLCertVerificationError | Private CA | Pass verify="/path/to/ca.pem" or set SSL_CERT_FILE. |
| Memory grows on long-running async worker | Client never closed | Use async with or call await client.aclose() on shutdown. |
Security considerations
httpx shares requests' secure defaults — certifi CA bundle, redirects strip cross-host auth, TLS-1.2 minimum — but the async surface and HTTP/2 support add a few extra footguns.
- Never
verify=Falsein production. Same caveat asrequests. Override with a CA path orSSL_CERT_FILE. Authflows. Usehttpx.BasicAuth,httpx.DigestAuth, or write a customhttpx.Authsubclass. Don't manually setAuthorizationheaders per request — easy to leak across redirects.- HTTP/2 server push is disabled by default in
httpx; ignore the noise about push from older HTTP/2 articles. - Cookie handling per-
Client. A one-shothttpx.get()is a fresh cookie jar; never use one-shots for authenticated flows. - Proxy URL secrets. Embedded credentials in
proxy="http://user:pass@host:port"print in repr — load via env, strip before logging. follow_redirects=Falseby default.httpx.get()does NOT follow redirects (requestsdoes). Setfollow_redirects=Trueexplicitly when needed.- TLS context customization. Pass a pre-built
ssl.SSLContexttoClient(verify=ctx)for tight control (cipher list, ALPN protocols, client cert). - Watch for CVEs in
h2andhttpcore. The HTTP/2 stack has had a small number of denial-of-service issues; pin minimum versions in production.
When NOT to use this
httpx is great, but it isn't always the right answer:
- A tight one-shot script —
urllib.requestfrom the stdlib avoids the install.requestshas more docs and community examples. - An existing
requests-based codebase where async isn't needed — the migration cost rarely pays back. Stay onrequestsuntil something forces the switch. - WebSockets —
httpxdoesn't speak WebSockets. Usewebsockets,aiohttp, or the framework's own WS support. - HTTP/3 (QUIC) —
httpxdoes not support HTTP/3 yet. Useaioquicorniquestsfor QUIC. - Pre-1.0 stability concerns. If your platform forbids pre-1.0 dependencies, pick
requests(2.x stable) oraiohttp(3.x stable).
Ecosystem integrations
httpx is the default outbound HTTP client across the modern async-Python ecosystem; the integrations below are the ones to keep on hand.
respx— mock HTTP at the transport layer. The canonical test double forhttpx.tenacity/stamina— retry decorators that wraphttpxcalls with backoff and circuit breakers.hishel— RFC-compliant HTTP cache layer forhttpx. Drop-inCacheClient/AsyncCacheClient.httpx-ws— WebSockets transport for httpx (httpx itself doesn't ship WS).httpx-oauth— OAuth2 client flows on top of httpx.fastapi.testclient.TestClient— wrapshttpxfor in-process ASGI testing.opentelemetry-instrumentation-httpx— tracing.sentry-sdk— automatic httpx breadcrumbs.anyio—httpx.AsyncClientworks under both asyncio and trio because it usesanyiointernally.niquests— arequests-API-compatible alternative that also offers HTTP/2 + HTTP/3; consider when migrating fromrequestsand want familiar API.
Compatibility matrix
| Python | httpx line | Notes |
|---|---|---|
| 3.8 | 0.24 and earlier | Drop deprecated; floor of older releases. |
| 3.9 | 0.25+ | Current floor for recent minor releases. |
| 3.10 | 0.26+ | Pattern matching usable in user code. |
| 3.11 | 0.26+ | Best perf via asyncio improvements. |
| 3.12 | 0.27+ | New asyncio task-group APIs supported. |
| 3.13 | 0.28+ | Latest stable; free-threaded build untested. |
Pair compatibility:
httpx[http2]requiresh2>=4.httpx[socks]requiressocksio.respxtypically requires the same major-minor ashttpx.
Production deployment notes
httpx is a library, not a server — there's no deployment artifact per se. But long-running services that use httpx heavily share a small checklist worth treating as the deployment surface.
- One
AsyncClientper process, opened in lifespan. Initialize in your framework's startup hook; close on shutdown. Per-request clients waste TLS handshakes and skew latency tails. - Health-check separately. Add a no-op
await client.head("/health")against your most-used upstream so the keepalive pool stays warm. - Honor upstream timeouts. Set
httpx.Timeout(connect=5, read=30, write=10, pool=5)explicitly — the 5s defaultreadbites under upstream pressure. - Tune
Limitsto match worker concurrency. A FastAPI worker with--workers 4 --threads 1and a high-fanout endpoint may saturate the default 100-connection pool fast. - Avoid
httpx.get()(module-level convenience). It creates and tears down a fresh client per call. Useful for scripts; never use in a service. - Watch
httpcoreerrors. Most "weird" httpx errors bubble up fromhttpcore; the actual cause is usually network or TLS at the OS level. - Containerize with a non-blocking signal handler. Long downloads can hold the event loop past SIGTERM — register a
signal.signal(SIGTERM, ...)to close clients before exit.
See also
- Python: httpx — API tutorial, sync/async examples
- Concept: HTTP — protocol fundamentals
- Concept: async — async/await mental model
- Packages: pip-requests — the sync sibling