Guide

How to Use Proxies With LangChain

How to use proxies with LangChain: route WebBaseLoader, document loaders and scraping tools through a proxy, and why you proxy the data side, not the model call.

HProxy Team··6 min read
HProxy.Guide

Skip the dead lists.

Our free proxy list re-checks every exit every few minutes across 100+ countries, with a live last-checked time, so you copy IPs that worked moments ago, not a stale text dump.

Open the free proxy list

LangChain makes two very different kinds of outbound request, and only one of them is worth putting a proxy on. Every chain and agent calls a model endpoint, and most of them also collect data from the open web through document loaders, retrievers and scraping tools. The model call carries your API key, so its rate limit follows the key rather than your address, and no proxy changes that. The data-collection calls hit ordinary websites the same way a scraper does, which means they draw the same rate limits, geo-gates and bot walls, and that is the traffic a proxy is for. Route your loaders and tools through a proxy by configuring the HTTP client each one uses, and leave the model call alone unless a firewall forces your hand.

We run a proxy network and a live proxy checker, so the failure we see most in LangChain code is a pipeline that proxies the wrong half, or one loader that quietly keeps using the real IP because a different loader in the same script speaks a different HTTP library. Every example below is runnable. Where one needs a live proxy, pull a fresh one from our free proxy API, which returns real, recently checked endpoints with no key.

How do you use a proxy with LangChain?

Set the proxy on the loader or tool that fetches the page, not on the model. For the common WebBaseLoader and the loaders built on the requests library, pass a proxies dictionary keyed by scheme, exactly the shape requests expects, and add a timeout so a dead proxy cannot hang the chain.

from langchain_community.document_loaders import WebBaseLoader

loader = WebBaseLoader(
    "https://example.com/article",
    proxies={
        "http": "http://user:pass@203.0.113.7:8080",
        "https": "http://user:pass@203.0.113.7:8080",
    },
    requests_kwargs={"timeout": 15},
)
docs = loader.load()

The key in that dictionary is the scheme of the URL you are fetching, not of the proxy, and both usually point at the same proxy. Set only the http key and every https:// fetch skips the proxy and goes out on your real IP, which is the same silent no-op we cover in the Python requests guide.

The two sides of LangChain traffic

This is the distinction that decides where the proxy belongs, and getting it backwards is the most common mistake. A chain or agent talks to a model endpoint, and it reaches out to the web to gather context. Those two flows have opposite economics.

The model call is authenticated. Whether you route it through ten IPs or none, the provider counts every token against your key, so a proxy cannot lift a rate limit that is enforced per account. That boundary is worth stating plainly, because a lot of teams reach for proxies to fix a 429 from their model provider and it never works. We wrote up exactly why in LLM API rate limits and proxies.

The data-collection call is not authenticated by you. When a loader pulls a product page or a retriever crawls a set of sources, the target sees an IP and applies its own defenses to that IP: rate limits, country gating, and the bot walls that challenge automation. Spread that traffic across many IPs and the whole picture changes. This is the same shape of problem covered in proxies for AI agents, because a LangChain agent that browses is an AI agent by another name.

Two kinds of LangChain traffic, one side worth proxying
  1. Model API call

    carries your key, quota follows the key

  2. Loaders and tools

    hit the open web, metered per IP

  3. Proxy the data side

    residential for defended sites

  4. Leave the key call

    an IP change does not raise its limit

Source: Proxy the collection side, not the model endpoint

Proxying WebBaseLoader and the document loaders

WebBaseLoader is the loader most chains start with, and it takes the proxies dictionary directly, as above. For a whole set of loaders that are built on requests, the blunt instrument is the environment, which requests reads by default:

import os

os.environ["HTTP_PROXY"] = "http://user:pass@203.0.113.7:8080"
os.environ["HTTPS_PROXY"] = "http://user:pass@203.0.113.7:8080"
# every requests-based loader created after this line now routes through the proxy

One caveat carries real weight: LangChain loaders do not all use the same HTTP client, so a proxy set one way does not automatically cover another. The synchronous WebBaseLoader.load() uses requests, its aload() fan-out uses aiohttp, some of the newer loaders use httpx, and the Playwright loaders drive a real browser. When a loader ignores your proxies dict, the reason is almost always that it speaks a different library, and the proxy has to go where that library takes it. The mechanics for each live in the aiohttp guide and the httpx guide, and for a browser-driven loader the proxy goes on the browser launch, the same way it does in the Playwright guide.

Rotating a pool for scraping tools and retrievers

A single IP pulling every source is the exact pattern rate limiters watch for, and a retriever that fans out across dozens of pages hits that wall fast. Spread the load across a pool and pick a fresh exit per attempt, retrying on a different proxy when one fails:

import random
import requests
from langchain_community.document_loaders import WebBaseLoader

POOL = requests.get(
    "https://hproxy.com/api/proxy-list",
    params={"format": "txt", "protocol": "http", "recent": "true", "limit": 50},
    timeout=15,
).text.split()

def load_url(url, tries=4):
    for _ in range(tries):
        proxy = f"http://{random.choice(POOL)}"
        try:
            loader = WebBaseLoader(
                url,
                proxies={"http": proxy, "https": proxy},
                requests_kwargs={"timeout": 15},
            )
            return loader.load()
        except Exception:
            continue   # dead or blocked exit, try the next one
    raise RuntimeError(f"all {tries} proxies failed for {url}")

The load-bearing part is retrying with a different proxy on failure, because on any pool some exits are always down. The same loop drops into a custom LangChain tool: wrap the fetch in a @tool function so the agent calls a rotating scraper instead of a bare HTTP client, and it inherits the retry behavior for free. When a hand-rolled pool outgrows a list, a rotating gateway that hands you one endpoint and a fresh residential IP per request removes the list management entirely.

Putting a proxy on the model call, when you actually need to

There is one legitimate reason to proxy the model call, and it is never rate limits. If your code runs behind a corporate firewall that forces all outbound traffic through an egress proxy, or the provider is reachable only from a specific region, you route the model client through a proxy to satisfy that network, not to change any quota. In langchain-openai you pass an httpx client:

import httpx
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="gpt-4o-mini",
    http_client=httpx.Client(proxy="http://user:pass@203.0.113.7:8080"),
    http_async_client=httpx.AsyncClient(proxy="http://user:pass@203.0.113.7:8080"),
)

Pass both http_client and http_async_client so the synchronous and streaming paths agree, and note that httpx 0.26 and later use the singular proxy= argument where older versions used proxies=. This is a network-plumbing move, and it leaves the provider's per-key limit exactly where it was.

Where to go from here

Two habits keep a LangChain data pipeline alive once it leaves the demo: proxy the collection side and not the model, and pull from a pool that is actually alive. For the second, our free proxy API returns recently checked endpoints with no key, ideal for testing every loader in this guide before you wire in a paid pool, and the proxy checker confirms an exit is live and leaving from the country you expect before you trust it in a chain.

From here, the requests guide is the reference for the proxies dict that most loaders inherit, proxies for AI agents covers the rotation decision when a task holds state across many steps, and proxies for LLM scraping is the wider picture for building datasets from the web. When the friendly fetches should stay cheap and only the defended sites need to look residential, our pay-as-you-go residential proxies at $0.44 per GB pick up where a free pool gives out.

Sources

Frequently asked questions

Should I put the proxy on the LangChain LLM call or the data loaders?
On the data loaders. The model call carries your API key, so its rate limit is counted against the key, not your IP address, and routing it through a proxy does nothing to raise that limit. The loaders, retrievers and scraping tools fetch ordinary web pages, which is where rate limits, geo-gating and bot walls actually apply per IP, so that is the traffic a proxy helps.
How do I add a proxy to WebBaseLoader?
WebBaseLoader accepts a proxies argument in its constructor, keyed by scheme the same way the requests library expects: WebBaseLoader(url, proxies={'http': 'http://user:pass@host:port', 'https': 'http://user:pass@host:port'}). You can also pass requests_kwargs for a timeout. Set both the http and https keys, because a missing https key sends your https requests out on the real IP.
Why is my LangChain proxy being ignored?
Different loaders use different HTTP libraries under the hood. WebBaseLoader and most requests-based loaders take a proxies dict, the async paths use aiohttp, some newer loaders use httpx, and the Playwright loaders drive a browser. A proxy set for one client does not carry to another, so set it on whichever client the loader you are using actually calls, or set the HTTP_PROXY and HTTPS_PROXY environment variables to cover every requests-based loader at once.
Do proxies raise LangChain's OpenAI or model rate limits?
No. If the limit is set per account by the model provider, changing your IP does not move it, because the service counts requests against your key. Proxies solve IP-level problems such as blocks and geo-gating on the websites your chain scrapes. To raise a per-account model quota you scale accounts or upgrade the plan, not the proxy.
What proxy type is best for LangChain scraping?
Match the tier to the target. For defended sites (search, retail, social, anything with a real bot team), rotating residential keeps success rates up because the IPs read as ordinary home users. For friendly APIs and open data your tools call, cheap datacenter is fine and far faster. Most LangChain data pipelines mix both: datacenter for the easy fetches, residential for the hard sites.

Get proxies that are alive right now

Our free list re-checks every exit every few minutes and shows a last-checked time, so you copy IPs that worked moments ago, not a stale text dump. When the location has to survive a real check, the paid network holds up.

129M+ proxy checks run · 100+ countries · HTTP / HTTPS / SOCKS · re-checked every few minutes · no signup