cheat sheet

urllib3

Package-level reference for urllib3 on PyPI — connection pooling, retries, TLS, version policy, and alternatives.

urllib3

What it is

urllib3 is a thread-safe, low-level HTTP client for Python. It sits beneath requests, botocore, kubernetes, and dozens of other libraries — most production Python codebases depend on it transitively whether they import it directly or not. The library exposes connection pooling, request retries, file uploads, TLS verification, proxy support, and gzip/deflate decoding, all without the higher-level conveniences (cookies, session persistence, JSON helpers) that requests layers on top.

Reach for urllib3 directly when you need wire-level control: custom connection pool tuning, fine-grained retry strategies, certificate pinning, or building your own higher-level HTTP library. Most application code is better served by requests or httpx.

Install

bash
pip install urllib3

Output: (none — exits 0 on success)

bash
uv add urllib3

Output: dependency resolved + added to pyproject.toml

bash
poetry add urllib3

Output: updated lockfile + virtualenv install

bash
pip install "urllib3[brotli]"
pip install "urllib3[zstd]"
pip install "urllib3[socks]"
pip install "urllib3[secure]"   # legacy on 1.x; removed on 2.x

Output: core urllib3 plus the named optional decoders / proxy / TLS helpers

Versioning & Python support

  • Two parallel lines historically: 1.26.x (legacy, Python 2.7+) and 2.x (modern, Python 3.7+). On Python 3.10+ pip resolves to 2.x by default.
  • The 2.0 release was a significant cleanup — dropped Python 2, removed pyOpenSSL backend, tightened TLS defaults to OpenSSL 1.1.1+. Some downstream packages pinned urllib3<2 temporarily after release; most have caught up by 2026.
  • 2.x minor releases preserve the public API. Watch the changelog before bumping minor numbers in production.
  • Pre-1.26 versions had known CVEs — never run anything older than 1.26.18 or 2.0.7.

Package metadata

  • Maintainer: urllib3 core team (Andrey Petrov, Seth Larson, et al.)
  • Project home: github.com/urllib3/urllib3
  • Docs: urllib3.readthedocs.io
  • PyPI: pypi.org/project/urllib3
  • License: MIT
  • Governance: community-maintained; grant-funded core development via the Python Software Foundation in recent years
  • First released: 2008
  • Downloads: consistently top 5 on PyPI; >1 billion downloads per month including transitive

Optional dependencies & extras

  • urllib3[brotli] — adds brotli (or brotlicffi on PyPy) for Content-Encoding: br decoding.
  • urllib3[zstd] — adds zstandard for Content-Encoding: zstd decoding.
  • urllib3[socks] — adds PySocks for SOCKS4/5 proxy URLs.
  • urllib3[secure] (1.x only) — adds pyOpenSSL, cryptography, idna, certifi. Removed in 2.x because stdlib ssl is now adequate.
  • No required runtime deps — urllib3 has no third-party runtime dependencies on Python 3 (a deliberate design goal).

Alternatives

PackageTrade-off
requestsHigher level — sessions, cookies, JSON, auth. Standard for application HTTP. Uses urllib3 underneath.
httpxSync + async, HTTP/2 support. Use when you need async.
aiohttpAsync client + server. Heavier than httpx if you only need a client.
niquestsrequests-compatible fork with HTTP/2 + HTTP/3 + async.
urllib (stdlib)No dependencies, but no pooling, no retries, no compression beyond gzip. Last resort.
pycurlNative libcurl bindings. Fast and powerful but C-extension build pain.

Common gotchas

  1. urllib3 2.x requires OpenSSL 1.1.1+ at the OS level. Older RHEL/Centos 7 stock OpenSSL is 1.0.2 — urllib3 2.x will refuse to import. Stay on 1.26.x for those systems.
  2. PoolManager is what you want — not bare connectionpool. Bare HTTPSConnectionPool(host) is one-host; PoolManager() handles arbitrary hosts behind one object.
  3. retries= accepts a Retry object, an int, or False. Bare retries=3 retries connect failures but not status codes. For status-based retries, build a Retry(total=3, status_forcelist=[500, 502, 503]).
  4. Default Retry does not back off — set backoff_factor=0.5 to get 0.5, 1.0, 2.0, … second sleeps.
  5. verify_mode defaults to CERT_REQUIRED on 2.x. Older code that passed cert_reqs="CERT_NONE" to disable verification needs updating; the recommended replacement is assert_hostname=False only when you have a specific reason.
  6. urlopen returns a HTTPResponse, not a file. Call .data for bytes, .json() (since 2.x), .stream(N) for chunks, .release_conn() when done if you bypassed the with statement.
  7. Connection leaks happen if you don't read the body or use preload_content=False without release_conn(). Easier to keep preload_content=True (default) unless streaming.

Real-world recipes

The recipes lean toward what urllib3 is uniquely good at: pool tuning, retry policy, proxies, and certificate handling. Higher-level usage is better served by requests.

Recipe 1 — Pool manager + GET request.

python
import urllib3

http = urllib3.PoolManager(num_pools=10, maxsize=20)
r = http.request("GET", "https://api.example.com/health", timeout=5.0)
print(r.status, r.headers.get("content-type"))
print(r.data[:200])

Output: 200 application/json plus the first 200 bytes of the response body. The PoolManager handles multiple hosts behind one object; maxsize=20 allows up to 20 concurrent connections per host.

Recipe 2 — Configure pool sizes for high concurrency.

python
import urllib3

http = urllib3.PoolManager(
    num_pools=50,       # number of distinct host pools to keep
    maxsize=100,        # max connections per host
    block=True,         # block when pool exhausted rather than open extra
    timeout=urllib3.Timeout(connect=5.0, read=30.0),
)

Output: a manager sized for fan-out workloads. block=True is critical — otherwise urllib3 silently opens unpooled connections.

Recipe 3 — Retry on 5xx with exponential backoff.

python
import urllib3
from urllib3.util.retry import Retry

retry = Retry(
    total=5,
    backoff_factor=0.5,
    status_forcelist=[429, 500, 502, 503, 504],
    allowed_methods={"GET", "HEAD", "PUT", "DELETE"},   # "OPTIONS"/"POST" excluded by default
    respect_retry_after_header=True,
)
http = urllib3.PoolManager(retries=retry)
r = http.request("GET", "https://flaky.example.com/data")
print(r.status)

Output: up to 5 retries on the listed statuses with 0.5, 1.0, 2.0, 4.0, 8.0 second waits; honors Retry-After headers.

Recipe 4 — HTTP/SOCKS proxy with auth.

python
import urllib3

http = urllib3.ProxyManager(
    "http://alicedev:secret@proxy.example.com:3128",
    proxy_headers=urllib3.make_headers(proxy_basic_auth="alicedev:secret"),
)
r = http.request("GET", "https://api.example.com/")
print(r.status)

Output: request routed through the proxy. For SOCKS, install urllib3[socks] and use socks5://user:pass@host:port.

Recipe 5 — Certificate pinning with assert_fingerprint.

python
import urllib3, hashlib

# Get the SHA-256 fingerprint of the expected server certificate (DER form), hex with colons.
expected = "A1:B2:C3:...:FF"

http = urllib3.PoolManager(
    cert_reqs="CERT_REQUIRED",
    ca_certs="/etc/ssl/certs/ca-bundle.crt",
    assert_fingerprint=expected,
)
r = http.request("GET", "https://api.example.com/")
print(r.status)

Output: request succeeds only if the leaf certificate matches the pinned fingerprint; mismatched cert raises SSLError.

Performance tuning

  • Reuse one PoolManager per process. Creating a manager per request defeats the entire purpose of the library — every call re-handshakes TLS.
  • Size maxsize to your concurrency. Default 10 chokes high-fanout workers; set to expected per-host concurrent connections.
  • Set block=True with maxsize to keep the pool bounded. Otherwise urllib3 silently opens overflow connections that are never reused.
  • num_pools is the LRU cache for distinct hosts; default 10. Bump to 50-100 for crawlers hitting many domains.
  • Pre-warm pools with a HEAD request to your top hosts during application startup — first request pays the TLS cost.
  • gzip/brotli/zstd decoding is on by default if the decoder is installed. Install urllib3[zstd] for the best compression-vs-CPU ratio on modern servers.
  • Disable chunked-encoding fallback by passing chunked=False when you know Content-Length.

Version migration guide

  • 1.x → 2.0 — minimum Python 3.7+; minimum OpenSSL 1.1.1+; pyOpenSSL backend removed; cert_reqs no longer accepts strings (use ssl.CERT_REQUIRED). The [secure] extra is gone — modern stdlib ssl is fine.
  • 1.26 → 2.0body param renamed in some signatures; assert_hostname=False semantics tightened; Retry.DEFAULT_METHOD_WHITELIST renamed to Retry.DEFAULT_ALLOWED_METHODS.
  • 2.0 → 2.1urllib3.contrib.pyopenssl shim still present for transitional codebases; removal scheduled for 2.x.
  • 2.x ongoing — minor releases tighten TLS defaults (cipher list, ALPN); pin minor versions in libraries.
python
# Pre-2.0
http = urllib3.PoolManager(cert_reqs="CERT_REQUIRED")

# 2.0+
import ssl
http = urllib3.PoolManager(cert_reqs=ssl.CERT_REQUIRED)

Output: same TLS verification posture; explicit ssl constant required on 2.x.

Security considerations

  • TLS verification is on by default. cert_reqs=ssl.CERT_REQUIRED is the default in 2.x. Disabling without assert_fingerprint is unsafe.
  • Don't disable hostname checking (assert_hostname=False) unless you've pinned a fingerprint — otherwise MITM is trivial.
  • Watch CVEs. Several urllib3 CVEs in the 1.26 line related to URL parsing and redirect handling; keep updated.
  • Redirect handlingurllib3 does NOT follow redirects by default (redirect=False). Be explicit if you opt in.
  • Auth in URLs (http://user:pass@host/) is parsed but never forwarded across redirects. Same behavior as requests.
  • Proxy env vars (HTTP_PROXY, HTTPS_PROXY, NO_PROXY) are honored if you use ProxyManager.from_url(...). PoolManager ignores them.
  • ssl_minimum_version — pass ssl.TLSVersion.TLSv1_3 to force TLS 1.3 when targeting modern hosts.

Testing & CI

Most testing is done at the layer above (mock requests or httpx). For direct urllib3 testing, the httpretty/responses libraries don't apply; use pytest-httpserver for in-process fake servers.

python
# pip install pytest-httpserver
def test_urllib3_against_local(httpserver):
    httpserver.expect_request("/ping").respond_with_data("pong")
    http = urllib3.PoolManager()
    r = http.request("GET", httpserver.url_for("/ping"))
    assert r.data == b"pong"

Output: test passes; real socket but local-only.

Ecosystem integrations

  • requests — the canonical higher-level wrapper; uses urllib3 underneath.
  • botocore — AWS SDK transport.
  • kubernetes (Python client) — built on urllib3.
  • certifi — bundled CA cert store; urllib3 reads it via where().
  • brotli / zstandard — optional content decoders.
  • PySocks — SOCKS proxy support.
  • opentelemetry-instrumentation-urllib3 — distributed tracing for raw urllib3 calls.

Compatibility matrix

Pythonurllib3 lineNotes
2.71.26.x (frozen)EOL; security-fix-only floor.
3.61.26.xFinal supported line; no 2.x.
3.72.0+Lowest 2.x floor; OpenSSL 1.1.1+ required.
3.82.0+Stable.
3.92.0+Stable.
3.102.0+Default on pip install urllib3.
3.112.0+Best perf.
3.122.0+Fully supported.
3.132.2+Latest wheel availability.

Production deployment

  • Pin to a tight range (urllib3>=2.2,<3) and bump deliberately. The library is hit hard enough that even minor releases sometimes change error subclasses.
  • Use a single PoolManager per process stored in a module-level variable.
  • Configure timeouts explicitlyTimeout(connect=5, read=30) is the bare minimum for production.
  • Disable redirects unless you understand the security implications, and never let untrusted users supply the URL.
  • Set User-Agent explicitly — urllib3's default ("python-urllib3/2.x") is fine for internal traffic but rude for crawlers.
  • Monitor pool exhaustionurllib3.exceptions.PoolError indicates maxsize is too low; alert on its frequency.
  • TLS audit — confirm ssl_minimum_version matches your security policy. urllib3 defaults track modern OpenSSL but not the bleeding edge.

When NOT to use this

  • Application code that needs cookies, sessions, JSON helpers — use requests or httpx. urllib3 has none of these conveniences.
  • Async services — urllib3 is blocking. Use httpx, aiohttp, or niquests.
  • WebSockets — urllib3 doesn't speak WS. Use websockets or websocket-client.
  • HTTP/2 or HTTP/3 — urllib3 is HTTP/1.1 only. Use httpx[http2] or niquests for HTTP/2; niquests for HTTP/3.
  • Quick one-off scriptsrequests is shorter and more readable.

Troubleshooting common errors

Error / SymptomLikely causeFix
MaxRetryError: HTTPSConnectionPool...Exhausted retriesInspect underlying cause (DNS, TLS, status); adjust Retry.
SSLError: [SSL: CERTIFICATE_VERIFY_FAILED]CA bundle stale or private CAPass ca_certs="/path/to/ca.pem" or update certifi.
PoolError: HTTPSConnectionPool... pool is fullmaxsize too low for concurrencyRaise maxsize or lower concurrent calls.
LocationValueError: No host specifiedBare path passed to request("GET", "/x")Pass full URL or use HTTPConnectionPool(host="...").
urllib3.exceptions.ProtocolError: Connection brokenServer reset mid-responseRetry; check upstream stability.
ReadTimeoutErrorRead half of timeout too tightRaise Timeout(read=N).
urllib3.exceptions.LocationParseErrorURL contained invalid charsURL-encode the path/query.
Import fails on RHEL 7OpenSSL 1.0.2 stockStay on urllib3 1.26.x or upgrade OS.
Memory grows in long-running loopBodies not read before next requestCall .read() or .release_conn() after each.

See also