Home/Blog/Requests vs HTTPX vs aiohttp
Developer Guide

Requests vs HTTPX vs aiohttp: Proxy Support

Python has three mainstream HTTP clients, and they handle proxies differently: different parameters, different SOCKS stories, different environment-variable defaults. Here is how each one routes traffic through a proxy, and which one fits your scraper.

9 min read·Last updated: July 2026

Quick Answer

All three clients speak to HTTP proxies out of the box; the differences are in concurrency model and SOCKS. requests is synchronous and uses a proxies= dict keyed by scheme. HTTPX offers both sync and async clients, takes a single proxy= argument, and optionally speaks HTTP/2. aiohttp is async-only, takes proxy= per request or per session, and needs the aiohttp-socks package for SOCKS.

  • Small sync scripts: requests. Migrating to async without rewriting everything: HTTPX. Raw async throughput: aiohttp
  • SOCKS5 is an extra install in all three: requests[socks], httpx[socks], or aiohttp-socks
  • requests and HTTPX read HTTP_PROXY/HTTPS_PROXY by default; aiohttp does not unless you pass trust_env=True

If you are building a scraper on dedicated 4G/5G proxies, the HTTP client is the layer that actually carries your credentials, decides where DNS gets resolved, and controls how many connections hit the proxy at once. Picking the wrong one will not break anything, but it will shape how much code you write and how your concurrency behaves. This guide compares the three libraries strictly on proxy handling, with the configuration patterns pulled from their official docs. For the wider picture of running a scraping stack on mobile IPs, see our web scraping solutions.

Proxy support at a glance

FeaturerequestsHTTPXaiohttp
Concurrency modelSync only (blocking IO)Sync Client + AsyncClientAsync only (asyncio)
Proxy parameterproxies= dict keyed by schemeproxy= URL, or mounts= per schemeproxy= per request or per session
SOCKS5requests[socks] extra (PySocks)httpx[socks] extraThird-party aiohttp-socks
Remote DNS via proxysocks5h:// schemesocks5h accepted since 0.28rdns=True (default for SOCKS5)
Env vars (HTTP_PROXY etc.)Read by defaultRead by default (trust_env=False to ignore)Ignored unless trust_env=True
HTTP/2NoOptional, httpx[http2] + http2=TrueNo (client is HTTP/1.1)

requests: sync and simple

requests is the default choice for anything that does not need concurrency. Proxies are a dict keyed by URL scheme, passed per request or set on a Session. The docs are explicit that requests "does not provide any kind of non-blocking IO", so scaling beyond a handful of parallel fetches means threads or a different library.

import requests

# HTTP proxy port for the dedicated slot us-mob-01
PROXY = "http://u_4a9c:p_2X7q@proxy.mobileproxies.org:8000"
proxies = {"http": PROXY, "https": PROXY}

r = requests.get("https://api.ipify.org", proxies=proxies, timeout=20)
print(r.text)  # carrier IP, not your machine's

SOCKS support arrived in requests 2.10.0 and is an optional extra: pip install "requests[socks]", which pulls in PySocks. The scheme controls DNS: socks5:// resolves hostnames on your machine, socks5h:// resolves them on the proxy, which is what you usually want with a remote mobile proxy. We cover the full pattern, including auth, retries and rotation, in our Python requests SOCKS5 proxy example.

Gotcha: the requests docs warn that values set via session.proxies can be overwritten by environmental proxies (the ones urllib.request.getproxies returns). To guarantee your proxy is used when HTTP_PROXY-style variables exist, pass proxies= on each request.

HTTPX: sync + async, one API

HTTPX gives you a requests-like API in both flavors: a synchronous Client and an AsyncClient. Proxying is configured with a single proxy= argument on the client (or on top-level calls like httpx.get). Note the naming: the older proxies= argument was deprecated in HTTPX 0.26 and removed in 0.28, so snippets written for requests need that one change.

import asyncio
import httpx

PROXY = "http://u_4a9c:p_2X7q@proxy.mobileproxies.org:8000"

# Sync client
with httpx.Client(proxy=PROXY, timeout=20) as client:
    print(client.get("https://api.ipify.org").text)

# Async client - same API, awaited
async def main():
    async with httpx.AsyncClient(proxy=PROXY, timeout=20) as client:
        r = await client.get("https://api.ipify.org")
        print(r.text)

asyncio.run(main())

For SOCKS, install the extra with pip install httpx[socks] and pass a socks5:// URL, for example httpx.Client(proxy="socks5://u_4a9c:p_2X7q@proxy.mobileproxies.org:1080"). Since 0.28 HTTPX also treats socks5h as a valid scheme for proxy-side DNS resolution. If you need different proxies for different schemes or hosts, the mounts= dict maps URL patterns to httpx.HTTPTransport(proxy=...) instances.

HTTPX is also the only one of the three with optional HTTP/2: install httpx[http2] and pass http2=True. It is off by default, and it only kicks in when the server supports it, but for scraping targets that fingerprint clients at the protocol level, having the option matters.

aiohttp: async-first, SOCKS via a plugin

aiohttp is built for asyncio from the ground up; there is no synchronous mode. HTTP proxies are supported natively: pass proxy= to an individual request or to the ClientSession itself, with credentials embeddable in the proxy URL. The docs note that support for proxies you connect to over https:// is limited; a plain http:// proxy endpoint that tunnels HTTPS traffic via CONNECT, which is how most mobile proxy gateways work, is the supported path.

import asyncio
import aiohttp

PROXY = "http://u_4a9c:p_2X7q@proxy.mobileproxies.org:8000"

async def main():
    # Session-level proxy; can also pass proxy= per request
    async with aiohttp.ClientSession(proxy=PROXY) as session:
        async with session.get("https://api.ipify.org") as resp:
            print(await resp.text())

asyncio.run(main())

SOCKS is not in aiohttp itself. The standard answer is the third-party aiohttp-socks package, which provides a ProxyConnector supporting SOCKS4(a), SOCKS5(h) and HTTP proxies, with remote DNS (rdns) defaulting to on for SOCKS5.

# pip install aiohttp-socks
import asyncio
import aiohttp
from aiohttp_socks import ProxyConnector

async def main():
    connector = ProxyConnector.from_url(
        "socks5://u_4a9c:p_2X7q@proxy.mobileproxies.org:1080"
    )
    async with aiohttp.ClientSession(connector=connector) as session:
        async with session.get("https://api.ipify.org") as resp:
            print(await resp.text())

asyncio.run(main())

Whether SOCKS5 is even worth the extra dependency depends on your use case; for most scraping jobs the HTTP CONNECT port behaves identically for HTTPS targets. We break down the real differences in SOCKS5 vs HTTP proxies.

The env-var trap

The single most common source of "why is my proxy not being used" (or "why is a proxy being used") bugs is that the three libraries treat HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY differently.

requests: on by default

Falls back to the standard environment variables when no proxies are passed, and env proxies can even override session-level settings. Set session.trust_env = False or pass explicit proxies= per request to stay in control.

HTTPX: on by default

Uses environment variables by default; pass trust_env=False to the client to ignore them.

aiohttp: off by default

The docs state that, contrary to requests, aiohttp will not read proxy environment variables by default; you opt in with trust_env=True on the session, which also picks up proxy credentials from ~/.netrc.

Which one for a mobile proxy scraper?

An honest note first: with a dedicated mobile proxy, the HTTP client is rarely your bottleneck. Each slot is a real SIM in a real modem, so its bandwidth and sensible request rate are bounded by the carrier connection, not by how fast Python can open sockets. Mobile IPs earn their keep on trust (they sit behind carrier-grade NAT shared with real subscribers), not on raw throughput, and they cost more than datacenter IPs. Plan concurrency around the proxy, then pick the client that matches that plan:

Pick requests if

You run sequential or lightly threaded jobs: checkout flows, account tasks, small crawls. Least code, most Stack Overflow answers, battle-tested proxy behavior.

Pick HTTPX if

You want one API for sync scripts and async workers, may need HTTP/2, or are migrating a requests codebase toward async gradually.

Pick aiohttp if

Your stack is already asyncio-native and you run many concurrent fetches across several proxy slots. Accept the aiohttp-socks dependency if you need SOCKS5.

One more architectural choice interacts with the client: whether your IP rotates. A dedicated slot with API-triggered rotation pairs naturally with a long-lived session object in any of the three libraries, while forced rotation changes how you think about connection reuse. See rotating vs static mobile proxies for how to choose.

Sources

Related Guides

One endpoint, any client

Our dedicated 4G/5G mobile proxies expose both an HTTP port and a SOCKS5 port on the same slot, so the same credentials work in requests, HTTPX and aiohttp. Real carrier SIMs in the US, UK, France and the Netherlands.