Link copied to clipboard!
ProductsPricingUse CasesSolutionsResourcesCompanyFAQ
Get Started
Home Blog Integrate Proxies with Python
Guides

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.

Key takeaways
  • Use one http:// proxy URL for both http and https traffic, and percent-encode the username and password.
  • Always set a timeout: requests waits forever by default.
  • Reuse a Session for many requests, and use socks5h:// 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 requests

A 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:PORT

2.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_PROXY

trust_env = False also makes requests ignore REQUESTS_CA_BUNDLE and .netrc, so use it only where you do not rely on them.

Want to see how this works for your project?
Tell us about your use case and we will suggest a setup.
View Pricing

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 seeLikely causeFix
ProxyError with Tunnel connection failed: 407 Proxy Authentication RequiredA wrong username or passwordRe-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 connectionA wrong host or port, or the wrong protocolCheck host, port and protocol (HTTP or SOCKS5) exactly as shown in your account.
ProxyError with ConnectTimeoutErrorThe proxy cannot be reached from your networkCheck the host, your firewall and your network. With a proxy, a connect timeout is reported as ProxyError, not ConnectTimeout.
ReadTimeoutThe website or the mobile connection answered too slowlyRaise the read timeout, lower the concurrency and retry with a backoff.
InvalidURL: Failed to parseSpecial characters in the proxy URLPercent-encode the username and password with quote(value, safe="").
InvalidSchema: Missing dependencies for SOCKS supportThe SOCKS extra is not installedRun python -m pip install "requests[socks]".
SSLError with CERTIFICATE_VERIFY_FAILEDAn outdated or corporate certificate bundle on your sideUpdate certifi or point REQUESTS_CA_BUNDLE at the right bundle. Do not switch verification off with verify=False.
The site shows your own IPThe proxy is not used: only one of the http and https keys is setSet 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 requests

Identify 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.

Python proxies: key takeaways and where to go next

Working with Python proxies comes down to a few dependable habits. Reuse one session for many requests, set a timeout on every call and keep credentials out of your source code. Together they keep a script fast, predictable and safe to share with colleagues.

How do you make a Python requests proxy setup reliable?

Retry only temporary errors, such as timeouts and 429 or 5xx responses, and wait longer after each attempt. If a site sends a Retry-After header, honor it. Do not retry permanent errors such as 404. For SOCKS5, use the socks5h scheme so the proxy resolves hostnames, and read the username and password from environment variables rather than the code. Our 4G mobile proxies accept HTTP(S) and SOCKS5, so once the SOCKS extra is installed the same script can switch protocols by changing the scheme of the proxy URL.

Where to go after your first working script

Add rotation next: the guide to rotating proxies automatically shows how to cycle through several proxies in code, and web scraping at scale covers rate limits and error handling. If you build data collection tools, see web scraping proxies and compare plans on the mobile proxy pricing page.