ProxyWing LogoProxyWing

How to Use a Proxy with Python Requests: Setup, Authentication & Rotation 2026

Using Python to route your HTTP requests through a proxy can be a real revolution when you need your traffic to go through a middleman server. Setting up a proxy server with the Python Requests library is simple—just instruct it to route your requests through a proxy server. Whether you’re web scraping, trying to access stuff that’s blocked in your area, or just experimenting, knowing how to work with Python requests proxy server is super handy. Proxying your requests in Python helps keep you under the radar, prevents sites from blocking you, and even allows you to rotate your IPs for more privacy. In this Python scraping guide, we’ll go over everything—from jumping into a basic proxy set-up to more advanced stuff like proxy-auth set-up, session-set management, using environment variables, rotating proxy servers for scraping, picking the best proxy servers, and troubleshooting common issues.

Published:April 25, 2025
Reading time:11 min
Last updated:May 25, 2026

TL;DR: Set a Proxy in Python Requests in 30 Seconds

If you came here for the quick fix, here it is. Pass a proxies dict to your request, with both http and https keys pointing to your proxy:

​```python
import requests

proxies = {
    "http": "http://USER:PASS@proxy.example.com:8080",
    "https": "http://USER:PASS@proxy.example.com:8080",
}

resp = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(resp.json())   # should show the proxy's IP, not yours
​```

That’s for the basic case. The rest of this guide covers what happens when this doesn’t “just work”: authentication errors, SOCKS5 proxies, rotation for scraping, environment variables, and the weird edge cases that eat up afternoons.

Quick Start: Setting Up a Proxy in Python Requests

How to Set a Proxy in Python Requests Library

Let’s get right into sending HTTP requests through a proxy in Python. First, you’ll want to set up the requests library, and then you can set it to make requests over a proxy server. It’ll also be a good chance to see how to set a username and password for proxy authentication in your Python code, too.

Installing the Requests Library

Install the requests library using pip if you haven’t already:

pip install requests

After installing, import it into your Python program (import requests) to use.

Basic Proxy Configuration in Python Requests

The Python requests library enables proxying HTTP requests in Python through a proxy dictionary definition. The dictionary is set up by protocols and backed by the proxy URL. Assuming your proxy server is at 123.45.67.89:8080, set it up like this in Python: Here we’ve passed a proxy dictionary to requests.get. This proxy server, once set, will capture your Python request and send it to the target site. The response to the target will be received by a different IP than yours, confirming that your Python request came via the proxy server:

python
import requests

# Define proxy server address for HTTP and HTTPS

proxies = {

 'http': 'http://<YOUR_PROXY_HERE>:8080',

 'https': 'http://<YOUR_PROXY_HERE>:8080'

}

response = requests.get('http://httpbin.org/ip', proxies=proxies)

print(response.text)

Note: In Python, always set both ‘http’ in the proxy’s dictionary. If you fail to set one and make a request to that kind of URL, that request won’t use the proxy.

How to verify your proxy is actually working

Sending a request through a proxy and getting a 200 back doesn’t mean the proxy was used, Requests can silently fall back to a direct connection in some failure modes. The only reliable check is to ask a service that echoes your IP and compare what comes back to your real public IP.

​```python
import requests

proxies = {
    "http":  "http://USER:PASS@proxy.example.com:8080",
    "https": "http://USER:PASS@proxy.example.com:8080",
}
​```

1. Get your real IP (no proxy)

real_ip = requests.get("https://api.ipify.org").text

2. Get your IP through the proxy

proxy_ip = requests.get("https://api.ipify.org", proxies=proxies, timeout=10).text

print(f"Real IP:  {real_ip}")
print(f"Proxy IP: {proxy_ip}")
assert real_ip != proxy_ip, "Proxy didn't change your IP — check your config"
​```

Use https://api.ipify.org, https://httpbin.org/ip, or https://ifconfig.me/ip: they all just return the IP that made the request, in plain text or JSON. If the two values match, your proxy isn’t being applied: most often the https key is missing from your dict, the proxy is down, or trust_env is overriding you from an environment variable.

Python Requests Proxy Authentication: Username, Password & Common Errors

Most paid proxies don’t let just anyone in, you’ll need to authenticate. Python Requests handles this through the proxy URL itself: you embed credentials directly into the string, and the library takes care of the Proxy-Authorization header for you.

Basic auth in the proxy URL

The format is http://username:password@host:port. Same dict shape as before, just with credentials baked in:

​```python
import requests

proxies = {
    "http": "http://myuser:mypassword@proxy.example.com:8080",
    "https": "http://myuser:mypassword@proxy.example.com:8080",
}

resp = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(resp.status_code)
​```

If credentials are correct, the request goes through. If not, you’ll get a 407 Proxy Authentication Required — that’s the proxy server telling you the username or password is wrong.

URL-encoding special characters in the password

Here’s the gotcha that eats hours: if your password contains @, :, /, %, or #, the URL parser will break. The fix is to URL-encode the password before slotting it into the proxy string:

​​```python
import requests
from urllib.parse import quote

username = "myuser"
password = "p@ss:word#1"           # has special characters
encoded = quote(password, safe="")  # → 'p%40ss%3Aword%231'

proxies = {
    "http":  f"http://{username}:{encoded}@proxy.example.com:8080",
    "https": f"http://{username}:{encoded}@proxy.example.com:8080",
}

resp = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(resp.json())
​```

This is the #1 cause of mysterious 407 errors when the password “looks right” but the request still fails. Always quote.

IP whitelisting as an alternative

Some providers (ProxyWing included) let you skip the username/password dance entirely by whitelisting your server’s outgoing IP. You authorize the IP once in the dashboard, and after that any request from that machine works without credentials in the URL:

​```python
proxies = {
    "http":  "http://proxy.example.com:8080",
    "https": "http://proxy.example.com:8080",
}

resp = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
​```

This is cleaner for production: no secrets in environment variables, no encoding headaches. The trade-off is that you’re locked to a static IP — fine for a server, painful for a laptop on Wi-Fi.

Authentication errors and how to fix them

ErrorWhat it meansFix
407 Proxy Authentication RequiredWrong credentials or unencoded special charsVerify user/pass, URL-encode the password
403 ForbiddenProxy works, but target site blocked the requestRotate IPs, lower request rate
502 Bad GatewayProxy is up but can’t reach the targetTry a different proxy in the pool
Connection refusedProxy is down or wrong host/portCheck the endpoint, try another proxy
SSL handshake failedProxy intercepts HTTPS without a trusted certUse verify=False for debug only, install proxy’s CA cert for production

Using Python Requests with Different Proxy Setups

How to Set a Proxy in Python Requests Library

With the basics out of the way, let’s discuss some special cases. Let’s discuss the use of a persistent session with request-bound proxy connections, specifying proxies in environment variables, and a SOCKS proxy server.

Setting Up a Proxy Session for Persistent Connections

If you are going to make many requests to the same proxy, use a request. Session() to avoid having to specify the proxy configuration each time. A session also reuses a TCP connection, which will help with performance.

python
import requests

session = requests.Session()

session.proxies = {

 'http': 'http://<YOUR_PROXY_HERE>:8080',

 'https': 'http://<YOUR_PROXY_HERE>:8080'

}

response = session.get('http://httpbin.org/ip')

print(response.text)

In this example, we set up the proxies only once in a session. Any request made with session.get() or session.post() will default to the configured route. A session can be helpful in the case of web scraping when you want each request to make a consistent path over the same routing setup without repeatedly defining it.

Using Environment Variables for Proxy Configuration

Python Requests parses proxy settings from the environment variables, so don’t hardcode them. Configure HTTP_PROXY in your environment (export under Linux/MacOS, set in Windows):

bash
 
export HTTP_PROXY="http://<YOUR_PROXY_HERE>:8080"
export HTTPS_PROXY="http://<YOUR_PROXY_HERE>:8080"


If you have set these within your terminal, any Python program that utilizes the requests library will be proxied by default. For instance, typing:

python
 
import requests
response = requests.get('http://httpbin.org/ip')
print(response.text)

will use the proxy server in the environmental variables, even though we have not passed a proxy’s dictionary to code. This is useful to enable or disable the proxies for specific request scenarios without needs to change the script.

Disabling environment proxies on a per-session basis

By default, requests.Session() reads HTTP_PROXY / HTTPS_PROXY from your environment — that’s trust_env=True. If you want one specific session to ignore those env variables (for example, when you’ve set proxies for the whole system but want one script to go direct), set trust_env to False:

​```python
import requests

s = requests.Session()
s.trust_env = False          # ignore HTTP_PROXY / HTTPS_PROXY
resp = s.get("https://httpbin.org/ip")
​```

Bypassing the proxy for specific hosts with NO_PROXY

If you want most traffic to go through a proxy but a few internal hosts to go direct, use the NO_PROXY environment variable. It takes a comma-separated list of hostnames or domains:

​```bash
export HTTP_PROXY="http://proxy.example.com:8080"
export HTTPS_PROXY="http://proxy.example.com:8080"
export NO_PROXY="localhost,127.0.0.1,.internal.company.com"
​```

Requests will route external traffic through the proxy but call localhost, 127.0.0.1, and anything under .internal.company.com directly. You can also override this per-request with proxies={"no_proxy": "localhost"}, but the env var is cleaner for most setups.

Handling HTTP, HTTPS, and SOCKS Proxies

By default, the requests library supports HTTPS proxies. For a SOCKS proxy (e.g., socks5:// for Tor or similar), you need to install support via:

bash
 
pip install requests[socks]
Then you can use a SOCKS proxy by specifying the scheme in the URL:
python
 
import requests
proxies = {
 'http': 'socks5://<YOUR_PROXY_HERE>:1080',
 'https': 'socks5://<YOUR_PROXY_HERE>:1080'
}
response = requests.get('http://httpbin.org/ip', proxies=proxies)
print(response.text)

This will proxy the request via a SOCKS5 proxy. You can supply a username and a password if your SOCKS proxy requires them by adding them in the URL like this. HTTP proxies are directly supported in requests, and SOCKS proxies are supported too if you include additional dependency. You’ve set up basic proxies, sessions, env-vars, and SOCKS proxies in Python. Next comes a deeper dive into rotating proxies for web scraping.

socks5:// vs socks5h:// — why DNS matters

There’s a subtle but important difference between the two SOCKS5 schemes:

  • socks5:// — your client resolves the hostname locally, then sends the IP to the proxy. Your DNS query leaks.
  • socks5h:// — your client sends the hostname to the proxy, and the proxy resolves it. No DNS leak.

For anonymity or geo-targeting (where you want DNS resolution from the proxy’s location, not yours), always use socks5h://:

​```python
import requests

proxies = {
    "http":  "socks5h://user:pass@proxy.example.com:1080",
    "https": "socks5h://user:pass@proxy.example.com:1080",
}

resp = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(resp.json())
​```

If you forget the h, the request will still work, but anyone watching your DNS can see what sites you’re visiting, defeating most of the point of using a SOCKS proxy in the first place.

Rotating Proxies in Python Requests for Web Scraping

How to Set a Proxy in Python Requests Library

When web scraping in Python, too many requests sent by the same IP (even by a single external server) can lead to blockages. The solution is to use rotating proxies—change the connection endpoint (and use multiple IPs) in your Python script that is used to make different requests. Rotating different proxy servers by a collection of requests in your Python code makes every single request come from a different place. Possessing a list of proxy servers inside your Python scraper is helpful since each one of your requests is sent with a different IP. This reduces the chances of getting blocked when Python-based scraping at a mass level. In essence, rotating IP addresses is the secret to successful scraping.

Why You Need Rotating Proxies for Web Scraping

The sites are more likely to flag or slow traffic that is seen coming in from a single IP at a very intense level. Using a pool of proxies in Python helps because your requests are spread all over different IP addresses. It looks like different people are making the requests instead of a single person. Rotating between different proxy servers and their IP addresses per request in your Python scraper thus hugely reduces the chances of getting blocked whilst scraping massive data.

Implementing a Simple Proxy Rotation Script: Round-robin vs random rotation

There are two common strategies for rotating proxies:

  • Round-robin: cycle through the list in order. Predictable, every proxy gets equal use, easy to debug.
  • Random: pick a random proxy each time. Less predictable, harder for the target site to fingerprint your pattern, but proxy usage isn’t evenly distributed.

For most scraping jobs, random is the safer default. Round-robin is fine when you know all your proxies are equally healthy.

```python
import requests
import random
from itertools import cycle

proxy_list = [
    "http://user:pass@proxy1.example.com:8080",
    "http://user:pass@proxy2.example.com:8080",
    "http://user:pass@proxy3.example.com:8080",
]

# --- random ---
def get_random_proxy():
    p = random.choice(proxy_list)
    return {"http": p, "https": p}

# --- round-robin ---
proxy_pool = cycle(proxy_list)
def get_next_proxy():
    p = next(proxy_pool)
    return {"http": p, "https": p}
​```

Handling dead proxies in your pool

Free and even paid proxies die regularly. A production-ready rotator needs to drop bad proxies on the fly so it doesn’t keep retrying them:

```python
import requests
import random

class ProxyRotator:
    def __init__(self, proxies):
        self.alive = list(proxies)
        self.dead = set()

    def get(self):
        if not self.alive:
            raise RuntimeError("No alive proxies left")
        return random.choice(self.alive)

    def mark_dead(self, proxy):
        if proxy in self.alive:
            self.alive.remove(proxy)
            self.dead.add(proxy)

rotator = ProxyRotator([
    "http://user:pass@proxy1.example.com:8080",
    "http://user:pass@proxy2.example.com:8080",
    "http://user:pass@proxy3.example.com:8080",
])

for url in urls_to_scrape:
    for _ in range(3):           # 3 attempts per URL
        proxy = rotator.get()
        try:
            resp = requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=10)
            resp.raise_for_status()
            break
        except (requests.exceptions.ProxyError,
                requests.exceptions.ConnectTimeout,
                requests.exceptions.SSLError):
            rotator.mark_dead(proxy)
    else:
        print(f"Failed: {url}")
​```

In real production you’d want to retry dead proxies after a cool-down (some come back), and you’d want per-proxy success/failure counters to score them. But this is enough for 90% of scraping projects.

When to stop building your own rotator

If you’re hitting more than 50–100k requests per day, managing your own pool starts to cost more in engineering time than it saves. At that scale a rotating residential or ISP proxy endpoint, one single URL where the provider handles rotation internally, is usually the right move. You just point Requests at one address, and every call comes out of a different IP ProxyWing’s rotating proxies work exactly this way.

Using Proxy Pools to Avoid Blocks

Instead of managing proxies yourself in Python, consider a rotating proxy with an IP pool. Such services are in the business of providing many proxies and rotating them, so every request is sent with a new one from the pool. The more there are in your pool, the fewer requests each will get, and thus detection becomes less likely. By distributing requests over many different IP addresses, you vastly reduce the chances of any one of them becoming blocked.

Choosing the Best Proxies for Python Requests

How to Set a Proxy in Python Requests Library

When choosing proxies to use with requests in Python, the following should be considered for effective request routing and anonymity:

Paid vs. Free: Free proxies are easily accessible but slow, unstable, or already blocked by a very large quantity of sites. Paid service provider sellers have faster and more stable Python-friendly paid proxies. In a situation involving a large Python scraping project or bulk use, good-quality proxies need to be purchased to ensure stable request delivery at scale.

Datacenter vs. Domestic: Datacenter proxies are cloud or datacenter provider proxies. They are more accessible and faster but are blockable and detectable instantly due to the known addresses. Domestic proxies are ISP (real user connection) proxies and are much harder to identify to sites and are thus suitable for Python-based scraping that involves frequent or high-volume requests. The only drawback is that the Python-ready residential option is costly.

Ease of use & Authentication: Define what authentication methods are offered by the provider (i.e., username/password basic auth or IP whitelisting) and that it is easy to include the request proxy’s parameter in each outbound request. The easier it is to set up (i.e., all traffic over a single proxy endpoint), the easier your Python development will be.

Troubleshooting Proxy Issues in Python Requests

How to Set a Proxy in Python Requests Library

Despite a proper Python setup, request-handling proxies will still give you issues. Some of the common issues and the corresponding solutions are set out below:

• Connection errors/timeouts: When there is no connection to a request, the connection is either down or not properly set up in your Python code. Try a different server address.

• SSL Certificate Errors: HTTPS tunneling can cause SSL validation errors. As a workaround when debugging in Python, pass verify=False in your call to avoid such errors and to let the request proceed without certificate checks. Use this only when Python-level debugging because this disables the certificate security validation.

By checking these issues and fixing them, you will avoid most issues with proxies in your Python requests and conduct your scraping or API requests with ease.

Conclusion

Python proxies with the request’s library enable you to control your trace IP when making HTTP requests. We have covered configuring setup in requests, authentication credentials, setting up sessions with environmental variables, and IP rotation to avoid getting blocked and maintain request continuity. Touching upon choosing good proxies for request success and debugging, with all those methods you can continue with your Python requests proxy set for web scraping, tests, or anything. Always be ethical with your use of your proxies and stay within the websites that you are targeting with your requests.

Article written by:

Alexandre Parfonov

Full Stack AI Engineer

Alexandre brings deep full-stack expertise to Proxywing's engineering efforts — from backend architecture and performance optimization to AI-driven development workflows. His hands-on work spans Node.js, React, cloud infrastructure, and RAG pipelines, giving him a rare ability to tackle both proxy platform internals and user-facing product challenges. At Proxywing, Alexandre focuses on designing resilient systems, eliminating performance bottlenecks, and integrating modern AI tooling into the development process. Outside of coding, he's passionate about exploring the frontiers of AI engineering and building side projects that push his technical boundaries.

All articles by author (44)

FAQ

Almost always one of three things: the `https` key is missing from your proxies dict, you’re hitting an HTTP-only URL while only the `http` key is set, or an `HTTP_PROXY` environment variable is overriding your in-code config. Set both keys explicitly and run the verification snippet above.

Yes, `pip install requests[socks]`. This pulls in PySocks, which Requests uses under the hood. Without it you’ll get an `InvalidSchema` error when you pass a `socks5://` or `socks5h://` URL.

The proxy is reachable but rejected your credentials. Check the username and password, and URL-encode any special characters (`@`, `:`, `#`, `%`) with `urllib.parse.quote()`. Embedded credentials must be URL-safe.

Yes. The proxies dict takes per-scheme values, so you can route HTTP through one server and HTTPS through another. In practice most providers expose a single endpoint for both, so you’ll usually point both keys to the same URL.

Pass `proxies={“http”: None, “https”: None}` to that call. This overrides any environment-level proxy configuration for that single request. To disable env-proxies for a whole session, set `session.trust_env = False`.

A proxy pool is the list of proxies you have available. Rotation is the strategy of moving between them — round-robin, random, or based on success rate. Some providers also sell a single “rotating endpoint” where the rotation happens server-side and you only see one URL.

For learning and testing, yes. For anything production-grade, no — free proxies are slow, frequently dead, often abused, and many log your traffic. If you’re scraping at any kind of scale, paid residential or ISP proxies pay for themselves in saved engineering time.

Set `session.proxies` once after creating the session, and every request from that session will use it. No need to pass `proxies=` on each call. This is also a TCP connection reuse win — sessions are faster than one-off requests for repeated calls to the same host.

Have any questions?