Python Proxies: A Complete Guide with Code Examples
A complete guide with code examples for using proxies in Python.
Python’s requests library makes it easy to send traffic through a proxy. This guide starts with a basic request and builds up to sessions, SOCKS5, timeouts, retries, httpx, parallel requests and the errors you are most likely to meet.
Every example uses placeholders: replace USERNAME, PASSWORD, HOST and PORT with the details of your own proxy. The code works with current versions of Python 3, requests and httpx.
- Use one
http://proxy URL for bothhttpandhttpstraffic, and percent-encode the username and password. - Always set a timeout:
requestswaits forever by default. - Reuse a
Sessionfor many requests, and usesocks5h://for SOCKS5 so that the proxy resolves DNS. - Retry only transient errors, with a backoff, and keep parallel requests within your plan’s limits.
- Keep credentials in environment variables and never print the proxy URL.
1.What you need before you start
You need Python 3.9 or newer, the requests library and the details of your proxy: host, port, username, password and protocol (HTTP(S) or SOCKS5). Install the library with pip:
python -m pip install requestsA proxy URL has the form scheme://username:password@host:port. For an HTTP(S) proxy the scheme is http. Characters such as /, ?, #, %, @ and : have a meaning inside a URL, so percent-encode the username and password. The standard library does it:
from urllib.parse import quote
username = "USERNAME"
password = "p@ss/word#1" # a password with special characters
proxy = f"http://{quote(username, safe='')}:{quote(password, safe='')}@HOST:PORT"
# -> http://USERNAME:p%40ss%2Fword%231@HOST:PORT2.A basic Python requests proxy example
Pass your proxy URL for both http and https. Replace the placeholders with your own details.
import requests
proxy = "http://USERNAME:PASSWORD@HOST:PORT"
proxies = {"http": proxy, "https": proxy}
r = requests.get("https://example.com", proxies=proxies, timeout=15)
print(r.status_code)The scheme of the proxy URL says how you talk to the proxy, not which kind of site you open. An http:// proxy URL carries both http:// and https:// pages. For an HTTPS page, requests asks the proxy to open a tunnel, and the encrypted connection to the site runs through it untouched.
Check which IP the website sees
Our public endpoint returns the IP address and the country our server sees for the request. Up to 30 requests per minute per address are allowed, and nothing is stored.
r = requests.get("https://xameleon.cloud/api/tools/ip", proxies=proxies, timeout=15)
print(r.json()) # {'ip': '203.0.113.25', 'country': 'UA'}If the address is the carrier’s and not your own, the proxy is in use.
3.Keep your credentials out of the code
Do not hard-code your login and password. Read them from environment variables, so they stay out of your repository, logs and screenshots.
import os
from urllib.parse import quote
user = quote(os.environ["PROXY_USER"], safe="")
password = quote(os.environ["PROXY_PASS"], safe="")
proxy = f"http://{user}:{password}@{os.environ['PROXY_HOST']}" # PROXY_HOST is HOST:PORT
proxies = {"http": proxy, "https": proxy}Set the variables in your shell (export PROXY_USER=... on Linux and macOS, $env:PROXY_USER = "..." in PowerShell) or load them from a .env file that is excluded from version control. Never print or log the proxy URL: it contains your password. For more, read secure and responsible proxy use.
4.Use a session
A session reuses connections, which is faster when you send many requests.
import requests
urls = ["https://example.com/page/1", "https://example.com/page/2"]
with requests.Session() as session:
session.proxies.update(proxies)
session.headers.update({"User-Agent": "my-app/1.0 (contact: [email protected])"})
for url in urls:
r = session.get(url, timeout=15)
print(url, r.status_code)A session also keeps cookies between requests, and because the IP is sticky, every request in it leaves from the same address while you keep working. The with block closes the connections when you are done.
Environment variables can override session proxies
requests also reads HTTP_PROXY, HTTPS_PROXY and NO_PROXY from the environment. When they are set, they take priority over session.proxies (but not over proxies= passed to the call itself), so a session can quietly use the wrong proxy. Either pass proxies= to each request or turn the environment off for the session:
session.trust_env = False # ignore HTTP_PROXY, HTTPS_PROXY and NO_PROXYtrust_env = False also makes requests ignore REQUESTS_CA_BUNDLE and .netrc, so use it only where you do not rely on them.
5.SOCKS5 proxies in Python
For SOCKS5, install the extra package and change the scheme of the proxy URL.
python -m pip install "requests[socks]"proxy = "socks5h://USERNAME:PASSWORD@HOST:PORT"
proxies = {"http": proxy, "https": proxy}
r = requests.get("https://example.com", proxies=proxies, timeout=15)
print(r.status_code)The h in socks5h matters. With socks5:// your computer looks up the website’s IP address and sends only the number to the proxy. With socks5h:// the website’s name goes to the proxy, which resolves it. Prefer socks5h://: it needs no local DNS lookups and works for names your own machine cannot resolve.
Use HTTP(S) for web requests. Choose SOCKS5 when your tool talks to servers other than websites, or when it supports only SOCKS.
6.Proxy timeouts and retries in Python
Always set a timeout and retry transient errors with a backoff.
Timeouts
requests has no default timeout, so a stalled connection can freeze your program forever. Set timeout on every call. A single number applies to connecting and to reading; a tuple sets them separately:
# (connect timeout, read timeout) in seconds
r = session.get(url, timeout=(5, 20))The connect timeout covers reaching the proxy and opening the connection. The read timeout is how long to wait for the next bytes of the response. Cellular links add latency, so start with generous values and tune them from your own measurements.
Retries with a backoff
Retry only transient problems: connection errors and responses such as 429, 502, 503 and 504. Do not retry errors that will not go away, such as 404 or 407. The urllib3 library can retry for you and wait longer after each failed attempt:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
retry = Retry(
total=3,
backoff_factor=1, # wait longer after each failed attempt
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "HEAD"], # retry only requests that are safe to repeat
respect_retry_after_header=True,
)
adapter = HTTPAdapter(max_retries=retry)
session = requests.Session()
session.proxies.update(proxies)
session.mount("http://", adapter)
session.mount("https://", adapter)The Retry-After header is honored, and allowed_methods limits retries to requests that are safe to repeat. When the attempts run out, requests raises RetryError.
Handle the errors
try:
r = session.get(url, timeout=(5, 20))
r.raise_for_status()
except requests.exceptions.ProxyError:
print("The proxy refused, dropped or did not answer the connection")
except requests.exceptions.Timeout:
print("Timed out: raise the timeout or lower the concurrency")
except requests.exceptions.RequestException as exc:
print("Request failed:", type(exc).__name__)Print the exception class and not the whole message: messages can contain host names and URLs you do not want in your logs.
7.Check the IP and change it
With a sticky IP, your requests leave from the same address while you keep working. Press Change IP in your account when you want a new one; API control of IP changes is planned. This helper reads the current exit IP, so you can confirm that a change went through:
import requests
def new_session():
s = requests.Session()
s.proxies.update(proxies)
return s
def exit_ip(session):
r = session.get("https://xameleon.cloud/api/tools/ip", timeout=(5, 20))
r.raise_for_status()
return r.json()["ip"]
session = new_session()
before = exit_ip(session)
input("Press Change IP in your account, then press Enter... ")
session.close() # drop connections that still point at the old IP
session = new_session()
after = exit_ip(session)
print("IP changed" if after != before else "Same IP: check that the change went through")Change the IP between jobs, not in the middle of a login or a checkout. How to rotate proxies shows patterns that combine on-demand changes with several proxies.
8.Proxies with httpx and async code
httpx follows the same idea with a slightly different API, plus async support. Pass the proxy to the client. Versions of httpx older than 0.26 take proxies= instead of proxy=.
import httpx
proxy = "http://USERNAME:PASSWORD@HOST:PORT"
timeout = httpx.Timeout(20.0, connect=5.0)
with httpx.Client(proxy=proxy, timeout=timeout) as client:
r = client.get("https://example.com")
print(r.status_code)For SOCKS5, install the extra with python -m pip install "httpx[socks]" and use a socks5:// URL. For many requests at once, use AsyncClient with a semaphore, so that the number of simultaneous requests stays under your plan’s limit:
import asyncio
import httpx
URLS = [f"https://example.com/page/{i}" for i in range(1, 11)]
async def main():
sem = asyncio.Semaphore(5) # at most 5 requests at a time
async with httpx.AsyncClient(proxy=proxy, timeout=20) as client:
async def fetch(url):
async with sem:
r = await client.get(url)
return url, r.status_code
for url, status in await asyncio.gather(*(fetch(u) for u in URLS)):
print(url, status)
asyncio.run(main())9.Run requests in parallel within your limits
A Session is not documented as thread-safe, so give every worker thread its own session. Keep max_workers at or below the number of concurrent connections your plan allows.
import threading
from concurrent.futures import ThreadPoolExecutor
local = threading.local()
def get_session():
if not hasattr(local, "session"):
local.session = requests.Session()
local.session.proxies.update(proxies)
return local.session
def fetch(url):
r = get_session().get(url, timeout=(5, 20))
return url, r.status_code
with ThreadPoolExecutor(max_workers=5) as pool:
for url, status in pool.map(fetch, urls):
print(url, status)Parallel requests share the same IP and the same traffic allowance. More workers do not make a website accept more requests, so lower the number when you see 429 responses.
10.Common errors and how to fix them
The message tells you where to look. These are the ones you will meet most often with requests:
| What you see | Likely cause | Fix |
|---|---|---|
ProxyError with Tunnel connection failed: 407 Proxy Authentication Required | A wrong username or password | Re-enter the credentials from your account and percent-encode them. For plain http:// pages you get a response with status 407 instead of an exception. |
ProxyError with Failed to establish a new connection | A wrong host or port, or the wrong protocol | Check host, port and protocol (HTTP or SOCKS5) exactly as shown in your account. |
ProxyError with ConnectTimeoutError | The proxy cannot be reached from your network | Check the host, your firewall and your network. With a proxy, a connect timeout is reported as ProxyError, not ConnectTimeout. |
ReadTimeout | The website or the mobile connection answered too slowly | Raise the read timeout, lower the concurrency and retry with a backoff. |
InvalidURL: Failed to parse | Special characters in the proxy URL | Percent-encode the username and password with quote(value, safe=""). |
InvalidSchema: Missing dependencies for SOCKS support | The SOCKS extra is not installed | Run python -m pip install "requests[socks]". |
SSLError with CERTIFICATE_VERIFY_FAILED | An outdated or corporate certificate bundle on your side | Update certifi or point REQUESTS_CA_BUNDLE at the right bundle. Do not switch verification off with verify=False. |
| The site shows your own IP | The proxy is not used: only one of the http and https keys is set | Set both keys in proxies, then call the IP endpoint to confirm. |
11.Be a good citizen
Add delays between requests, respect robots.txt and each site's terms, and keep concurrency within your plan's limits.
To check robots.txt through the same proxy, download it with your session and hand it to the parser from the standard library:
import random
import time
from urllib.robotparser import RobotFileParser
base = "https://example.com"
r = session.get(base + "/robots.txt", timeout=(5, 20))
robots = RobotFileParser()
robots.parse(r.text.splitlines() if r.ok else []) # no robots.txt: nothing is disallowed
for path in ["/page/1", "/page/2"]:
if not robots.can_fetch("my-app", base + path):
continue # the site asks bots to stay out
r = session.get(base + path, timeout=(5, 20))
time.sleep(random.uniform(1, 3)) # a pause between requestsIdentify your program with a clear User-Agent, collect only data you are entitled to use and read the terms of the site you work with. Web scraping at scale covers request budgets and error handling in more depth.
12.FAQs
Which Python library is best for proxies?
requests is the simplest choice for scripts. httpx adds async support and a stricter timeout model. Both take the same kind of proxy URL, so you can switch later without changing your proxy details.
Does Python use my system proxy automatically?
requests and httpx read the HTTP_PROXY, HTTPS_PROXY and NO_PROXY environment variables by default. Set trust_env=False to ignore them.
Should I use HTTP or SOCKS5?
Use HTTP(S) for web requests. Use SOCKS5 when your tool needs it, and prefer the socks5h:// scheme.
How do I keep the same IP across requests?
Your IP is sticky: it stays the same while you keep working, and you change it in your account when you want a new one. Reuse one session for a job, and do not press Change IP in the middle of it.
How do I use several proxies in Python?
Create one session per proxy and pick between them in your code. How to rotate proxies shows the patterns.
Can I use a proxy with Playwright?
Yes. Pass the proxy when you launch the browser: proxy={"server": "http://HOST:PORT", "username": "USERNAME", "password": "PASSWORD"}.
Still have questions about mobile proxies? Visit our FAQ page for detailed answers, or contact our support team — we're here to help.