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
pip install urllib3
Output: (none — exits 0 on success)
uv add urllib3
Output: dependency resolved + added to pyproject.toml
poetry add urllib3
Output: updated lockfile + virtualenv install
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+) and2.x(modern, Python 3.7+). On Python 3.10+ pip resolves to2.xby default. - The
2.0release was a significant cleanup — dropped Python 2, removedpyOpenSSLbackend, tightened TLS defaults to OpenSSL 1.1.1+. Some downstream packages pinnedurllib3<2temporarily after release; most have caught up by 2026. 2.xminor 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.18or2.0.7.
Package metadata
- Maintainer:
urllib3core 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]— addsbrotli(orbrotlicffion PyPy) forContent-Encoding: brdecoding.urllib3[zstd]— addszstandardforContent-Encoding: zstddecoding.urllib3[socks]— addsPySocksfor SOCKS4/5 proxy URLs.urllib3[secure](1.x only) — addspyOpenSSL,cryptography,idna,certifi. Removed in 2.x because stdlibsslis now adequate.- No required runtime deps — urllib3 has no third-party runtime dependencies on Python 3 (a deliberate design goal).
Alternatives
| Package | Trade-off |
|---|---|
requests | Higher level — sessions, cookies, JSON, auth. Standard for application HTTP. Uses urllib3 underneath. |
httpx | Sync + async, HTTP/2 support. Use when you need async. |
aiohttp | Async client + server. Heavier than httpx if you only need a client. |
niquests | requests-compatible fork with HTTP/2 + HTTP/3 + async. |
urllib (stdlib) | No dependencies, but no pooling, no retries, no compression beyond gzip. Last resort. |
pycurl | Native libcurl bindings. Fast and powerful but C-extension build pain. |
Common gotchas
urllib3 2.xrequires OpenSSL 1.1.1+ at the OS level. Older RHEL/Centos 7 stock OpenSSL is 1.0.2 —urllib3 2.xwill refuse to import. Stay on1.26.xfor those systems.PoolManageris what you want — not bareconnectionpool. BareHTTPSConnectionPool(host)is one-host;PoolManager()handles arbitrary hosts behind one object.retries=accepts aRetryobject, an int, or False. Bareretries=3retries connect failures but not status codes. For status-based retries, build aRetry(total=3, status_forcelist=[500, 502, 503]).- Default
Retrydoes not back off — setbackoff_factor=0.5to get0.5, 1.0, 2.0, …second sleeps. verify_modedefaults to CERT_REQUIRED on2.x. Older code that passedcert_reqs="CERT_NONE"to disable verification needs updating; the recommended replacement isassert_hostname=Falseonly when you have a specific reason.urlopenreturns aHTTPResponse, not a file. Call.datafor bytes,.json()(since 2.x),.stream(N)for chunks,.release_conn()when done if you bypassed thewithstatement.- Connection leaks happen if you don't read the body or use
preload_content=Falsewithoutrelease_conn(). Easier to keeppreload_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.
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.
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.
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.
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.
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
PoolManagerper process. Creating a manager per request defeats the entire purpose of the library — every call re-handshakes TLS. - Size
maxsizeto your concurrency. Default 10 chokes high-fanout workers; set to expected per-host concurrent connections. - Set
block=Truewithmaxsizeto keep the pool bounded. Otherwise urllib3 silently opens overflow connections that are never reused. num_poolsis 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/zstddecoding is on by default if the decoder is installed. Installurllib3[zstd]for the best compression-vs-CPU ratio on modern servers.- Disable chunked-encoding fallback by passing
chunked=Falsewhen you knowContent-Length.
Version migration guide
1.x → 2.0— minimum Python 3.7+; minimum OpenSSL 1.1.1+;pyOpenSSLbackend removed;cert_reqsno longer accepts strings (usessl.CERT_REQUIRED). The[secure]extra is gone — modern stdlibsslis fine.1.26 → 2.0—bodyparam renamed in some signatures;assert_hostname=Falsesemantics tightened;Retry.DEFAULT_METHOD_WHITELISTrenamed toRetry.DEFAULT_ALLOWED_METHODS.2.0 → 2.1—urllib3.contrib.pyopensslshim still present for transitional codebases; removal scheduled for 2.x.2.xongoing — minor releases tighten TLS defaults (cipher list, ALPN); pin minor versions in libraries.
# 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_REQUIREDis the default in2.x. Disabling withoutassert_fingerprintis unsafe. - Don't disable hostname checking (
assert_hostname=False) unless you've pinned a fingerprint — otherwise MITM is trivial. - Watch CVEs. Several
urllib3CVEs in the 1.26 line related to URL parsing and redirect handling; keep updated. - Redirect handling —
urllib3does 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 asrequests. - Proxy env vars (
HTTP_PROXY,HTTPS_PROXY,NO_PROXY) are honored if you useProxyManager.from_url(...).PoolManagerignores them. ssl_minimum_version— passssl.TLSVersion.TLSv1_3to 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.
# 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 viawhere().brotli/zstandard— optional content decoders.PySocks— SOCKS proxy support.opentelemetry-instrumentation-urllib3— distributed tracing for raw urllib3 calls.
Compatibility matrix
| Python | urllib3 line | Notes |
|---|---|---|
| 2.7 | 1.26.x (frozen) | EOL; security-fix-only floor. |
| 3.6 | 1.26.x | Final supported line; no 2.x. |
| 3.7 | 2.0+ | Lowest 2.x floor; OpenSSL 1.1.1+ required. |
| 3.8 | 2.0+ | Stable. |
| 3.9 | 2.0+ | Stable. |
| 3.10 | 2.0+ | Default on pip install urllib3. |
| 3.11 | 2.0+ | Best perf. |
| 3.12 | 2.0+ | Fully supported. |
| 3.13 | 2.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
PoolManagerper process stored in a module-level variable. - Configure timeouts explicitly —
Timeout(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-Agentexplicitly — urllib3's default ("python-urllib3/2.x") is fine for internal traffic but rude for crawlers. - Monitor pool exhaustion —
urllib3.exceptions.PoolErrorindicatesmaxsizeis too low; alert on its frequency. - TLS audit — confirm
ssl_minimum_versionmatches 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
requestsorhttpx. urllib3 has none of these conveniences. - Async services — urllib3 is blocking. Use
httpx,aiohttp, orniquests. - WebSockets — urllib3 doesn't speak WS. Use
websocketsorwebsocket-client. - HTTP/2 or HTTP/3 — urllib3 is HTTP/1.1 only. Use
httpx[http2]orniquestsfor HTTP/2;niquestsfor HTTP/3. - Quick one-off scripts —
requestsis shorter and more readable.
Troubleshooting common errors
| Error / Symptom | Likely cause | Fix |
|---|---|---|
MaxRetryError: HTTPSConnectionPool... | Exhausted retries | Inspect underlying cause (DNS, TLS, status); adjust Retry. |
SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] | CA bundle stale or private CA | Pass ca_certs="/path/to/ca.pem" or update certifi. |
PoolError: HTTPSConnectionPool... pool is full | maxsize too low for concurrency | Raise maxsize or lower concurrent calls. |
LocationValueError: No host specified | Bare path passed to request("GET", "/x") | Pass full URL or use HTTPConnectionPool(host="..."). |
urllib3.exceptions.ProtocolError: Connection broken | Server reset mid-response | Retry; check upstream stability. |
ReadTimeoutError | Read half of timeout too tight | Raise Timeout(read=N). |
urllib3.exceptions.LocationParseError | URL contained invalid chars | URL-encode the path/query. |
| Import fails on RHEL 7 | OpenSSL 1.0.2 stock | Stay on urllib3 1.26.x or upgrade OS. |
| Memory grows in long-running loop | Bodies not read before next request | Call .read() or .release_conn() after each. |
See also
- Python: requests — high-level client built on urllib3
- Concept: HTTP — protocol fundamentals
- Packages: pip-requests — sibling package
- Official urllib3 docs