     [Blog](https://scrapfly.io/blog)   /  [proxies](https://scrapfly.io/blog/tag/proxies)   /  [How to Use Proxies with Selenium: Authentication, Rotation, and Residential Proxies](https://scrapfly.io/blog/posts/how-to-use-proxies-with-selenium)   # How to Use Proxies with Selenium: Authentication, Rotation, and Residential Proxies

 by [Mayada Shaaban](https://scrapfly.io/blog/author/mayada-shaaban-90143e67) Oct 19, 2026 33 min read [\#proxies](https://scrapfly.io/blog/tag/proxies) [\#python](https://scrapfly.io/blog/tag/python) [\#selenium](https://scrapfly.io/blog/tag/selenium) 

 [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fhow-to-use-proxies-with-selenium "Share on LinkedIn") [  ](https://x.com/intent/tweet?url=https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fhow-to-use-proxies-with-selenium&text=How%20to%20Use%20Proxies%20with%20Selenium%3A%20Authentication%2C%20Rotation%2C%20and%20Residential%20Proxies "Share on X") [  ](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fhow-to-use-proxies-with-selenium "Share on Facebook")    

 

 

Summarize this article with

 [  ](https://chat.openai.com/?q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fhow-to-use-proxies-with-selenium) [  ](https://claude.ai/new?q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fhow-to-use-proxies-with-selenium) [  ](https://x.com/i/grok?text=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fhow-to-use-proxies-with-selenium) [  ](https://www.perplexity.ai/search/new?q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fhow-to-use-proxies-with-selenium) [  ](https://www.google.com/search?udm=50&aep=11&q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fhow-to-use-proxies-with-selenium) 



         

  **Browser API**Automate browsers in the cloud with full JavaScript rendering support.

 

 [ Learn More  ](https://scrapfly.io/products/cloud-browser-api) [  Docs ](https://scrapfly.io/docs/scrape-api/javascript-rendering) 

 

 

In this guide, we'll take a look at how to use proxies with Selenium and Python. We'll cover proxy authentication, rotating proxies, residential proxy gateways and how to check which IP the browser is really using. Finally, we'll see why a working proxy can still get our scraper blocked.

So what's tricky about Selenium proxies? Setting a proxy is easy, as it's just a Chrome flag. Authentication is where it gets difficult.

If we put the credentials into the `--proxy-server` flag as `user:pass@host:port`, Selenium launches the browser just fine. However, Chrome doesn't accept them and the page fails with `ERR_NO_SUPPORTED_PROXIES`.

Without credentials it's even worse, as headless Chrome loads a blank page without raising any error. The only sign of the problem is a 407 response in the proxy's logs!

All of the examples below were run in October 2026 on macOS, with Selenium 4.51.0 and headless Chrome 155 (both Google Chrome and Chrome for Testing).



## Key Takeaways

- Pass the proxy to Chrome through the `--proxy-server` flag on browser start. It can't be changed afterwards, so rotation requires a new driver or a rotating gateway.
- For proxy credentials - use CDP's `Fetch.continueWithAuth` or a local pproxy forwarder, as Chrome ignores `user:pass@` in the flag.
- Verify the exit IP before scraping. Usual culprits are the `ERR_NO_SUPPORTED_PROXIES` error page and the blank 407 page, neither of which Selenium raises as an exception.
- Keep the same IP (sticky session) for logins and multi-step flows.
- Ensure the browser timezone and language match the proxy location, as headless Chrome only gets a new IP.

**Get web scraping tips in your inbox**Trusted by 100K+ developers and 30K+ enterprises. Unsubscribe anytime.







## How Do You Set a Proxy in Selenium with --proxy-server?

Setting a proxy in Chrome is done with the `--proxy-server` launch flag. All we have to do is add it to our `ChromeOptions` together with the proxy address, like `--proxy-server=http://127.0.0.1:8001`!

Let's install Selenium first, together with a few packages for the later examples. If you're new to Selenium, take a look at our [web scraping with Selenium](https://scrapfly.io/blog/posts/web-scraping-with-selenium-and-python) guide first.

shell

```shell
pip install selenium pproxy websocket-client requests scrapfly-sdk playwright
```



There's no need to download chromedriver, as Selenium Manager comes with Selenium and fetches it for us.

For our tests, we'll run two local proxies with [pproxy](https://github.com/qwj/python-proxy). The first one is open, and the second one requires a username and password:

shell

```shell
pproxy -l http://127.0.0.1:8001 -v
pproxy -l "http://127.0.0.1:8010#testuser:testpass" --auth 0 -v
```



Here, the `--auth 0` part is important. By default, pproxy remembers a client IP that authenticated once and lets it in without credentials for the next 30 days!

Let's start with the open proxy. We'll point Chrome at it and read our exit IP from httpbin.dev:

python

```python
import json
import os

from selenium import webdriver
from selenium.webdriver.common.by import By

proxy = f"http://{os.environ['PROXY_HOST']}:{os.environ['PROXY_PORT']}"

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument(f"--proxy-server={proxy}")

driver = webdriver.Chrome(options=options)
try:
    driver.get("https://httpbin.dev/anything")
    data = json.loads(driver.find_element(By.TAG_NAME, "pre").text)
    print("origin:", data["origin"])
    print("browser:", driver.capabilities["browserVersion"])
finally:
    driver.quit()
```



In the example above, we request `https://httpbin.dev/anything` and print the `origin` field from its JSON response. We don't use `/ip` here, as it returns 404 on httpbin.dev and breaks a lot of copied snippets.

Since our test proxy runs on the same machine, the origin is just our own IP, masked below:

text

```text
origin: XXX.XXX.XXX.XXX
browser: 155.0.8059.40
```



So how do we know the request really went through the proxy? The pproxy log shows both connections to httpbin.dev:

text

```text
http 127.0.0.1:58500 -> httpbin.dev:443
http 127.0.0.1:58502 -> httpbin.dev:443
```



We can also assign a Selenium `Proxy` object to `options.proxy`, using `ProxyType.MANUAL` with `httpProxy` and `sslProxy`, and that variant reached our test proxy as well.

Either way, Chrome only reads the proxy when the driver starts. To switch proxies, we have to start a new driver.

Note that this only works with proxies that don't need credentials, like an IP-allowlisted proxy or the local forwarder we'll build later. For more on proxy types, see our [introduction to proxies in web scraping](https://scrapfly.io/blog/posts/introduction-to-proxies-in-web-scraping).

### How Do You Set a Proxy in Firefox with Selenium?

Firefox is a bit different, as it doesn't read Chrome flags. Instead, we assign the same kind of `Proxy` object to `FirefoxOptions().proxy`:

python

```python
import json
import os

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.proxy import Proxy, ProxyType

address = f"{os.environ['PROXY_HOST']}:{os.environ['PROXY_PORT']}"

options = webdriver.FirefoxOptions()
options.add_argument("-headless")
options.set_preference("devtools.jsonview.enabled", False)  # keep the raw  JSON
options.proxy = Proxy({"proxyType": ProxyType.MANUAL, "httpProxy": address, "sslProxy": address})

driver = webdriver.Firefox(options=options)
try:
    driver.get("https://httpbin.dev/anything")
    print("origin:", json.loads(driver.find_element(By.TAG_NAME, "pre").text)["origin"])
    print("browser:", driver.capabilities["browserName"], driver.capabilities["browserVersion"])
finally:
    driver.quit()
```



In the example above, we also disable `devtools.jsonview.enabled`. Without it, Firefox shows JSON in its own viewer and there's no `<pre>` element to find (our first run crashed with `NoSuchElementException`).

Running it prints our IP and the Firefox version that Selenium Manager downloaded:

text

```text
origin: XXX.XXX.XXX.XXX
browser: firefox 157.0.1
```



How about a proxy with a password? Here, the browser doesn't even open, as Selenium Manager sends its own lookups through the proxy too. It gets `proxy authorization required` back and Selenium gives up with `NoSuchDriverException: Unable to obtain driver for firefox`.

For Firefox, we'll use the local forwarder from the authentication section instead. More Firefox setup is in our [headless Firefox scraping](https://scrapfly.io/blog/posts/how-to-scrape-with-headless-firefox) guide.



## Why Does Chrome Ignore Proxy Credentials in Selenium?

As it turns out, Chrome doesn't support credentials in its proxy settings. The [Chromium proxy docs](https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md) put it plainly: Chrome "will not use any credentials embedded in the proxy settings".

To see what this looks like, let's start Chrome with our password proxy twice. The first time we'll put the credentials in the flag, and the second time we'll leave them out:

python

```python
import os

from selenium import webdriver
from selenium.webdriver.common.by import By

host, port = os.environ["PROXY_HOST"], os.environ["PROXY_PORT"]
user, password = os.environ["PROXY_USER"], os.environ["PROXY_PASS"]

for proxy in [f"http://{user}:{password}@{host}:{port}", f"http://{host}:{port}"]:
    options = webdriver.ChromeOptions()
    options.add_argument("--headless=new")
    options.add_argument(f"--proxy-server={proxy}")
    driver = webdriver.Chrome(options=options)
    try:
        driver.get("https://httpbin.dev/anything")  # no exception in either case
        body = driver.find_element(By.TAG_NAME, "body").text
        error = "ERR_NO_SUPPORTED_PROXIES" if "ERR_NO_SUPPORTED_PROXIES" in body else ""
        print("with credentials:" if "@" in proxy else "without credentials:",
              repr(body.split("\n")[0]), error)
    finally:
        driver.quit()
```



Here we load httpbin.dev with each proxy and print the first line of the page. Neither run raised an exception, and we got:

text

```text
with credentials: 'This site can’t be reached' ERR_NO_SUPPORTED_PROXIES
without credentials: ''
```



With credentials, Chrome shows its error page. Without them, the page is empty and the only trace is in the pproxy log: 7 `Unauthorized HTTP from 127.0.0.1` lines, aka the [407 Proxy Authentication Required](https://scrapfly.io/blog/posts/what-is-http-407-status-code-and-how-to-fix-it) response that never reaches Python (Chrome for Testing 155 did the same).

So a scraper that only catches exceptions would keep going, scraping an error page or nothing at all!

How do we fix it? If the proxy provider supports IP allowlisting, we can skip credentials altogether. Otherwise, we'll need one of the methods below.



## How Do You Use an Authenticated Proxy in Selenium?

So how can we use proxies that require a login? Chrome won't take credentials through `--proxy-server`, so we have to answer the proxy's login request some other way. Let's start with the Chrome DevTools Protocol.

### How Do You Answer Proxy Authentication with CDP in Chrome?

Chrome DevTools Protocol (CDP) is what Chrome's developer tools use to talk to the browser, and Selenium's Chrome driver runs with it enabled. So, the first thing we need is a connection to it:

python

```python
import json
import os
import threading

import requests
import websocket
from selenium import webdriver
from selenium.webdriver.common.by import By


class ChromeWithProxyAuth:
    def __init__(self, host, port, username, password, options):
        self.username, self.password = username, password
        options.add_argument(f"--proxy-server=http://{host}:{port}")
        options.add_argument("--remote-allow-origins=*")
        self.driver = webdriver.Chrome(options=options)
        # find the devtools websocket of our browser tab and connect to it
        address = self.driver.capabilities["goog:chromeOptions"]["debuggerAddress"]
        tab = next(t for t in requests.get(f"http://{address}/json").json() if t["type"] == "page")
        self.ws = websocket.create_connection(tab["webSocketDebuggerUrl"])
        self.msg_id = 0
        # listen to devtools events in the background
        threading.Thread(target=self.listen, daemon=True).start()
        # ask chrome to pause and tell us when a login is needed
        self.send("Fetch.enable", {"handleAuthRequests": True})

    def send(self, method, params):
        self.msg_id += 1
        self.ws.send(json.dumps({"id": self.msg_id, "method": method, "params": params}))
```



Here, we start Chrome with our proxy and find the devtools websocket of our browser tab through the `debuggerAddress` capability Selenium gives us. Then, we enable the [Fetch domain](https://chromedevtools.github.io/devtools-protocol/tot/Fetch/) with `handleAuthRequests`, which makes Chrome pause and notify us every time the proxy asks for a login.

Now, let's add the listener that answers these login requests to the same class:

python

```python
    # ...continuing our ChromeWithProxyAuth class from above

    def listen(self):
        while self.ws.connected:
            try:
                msg = json.loads(self.ws.recv())
            except Exception:
                return
            params = msg.get("params", {})
            # let all other requests continue as normal
            if msg.get("method") == "Fetch.requestPaused":
                self.send("Fetch.continueRequest", {"requestId": params["requestId"]})
            elif msg.get("method") == "Fetch.authRequired":
                if params["authChallenge"]["source"] == "Proxy":
                    answer = {"response": "ProvideCredentials",
                              "username": self.username, "password": self.password}
                else:  # websites asking for a login don't get our proxy password
                    answer = {"response": "Default"}
                self.send("Fetch.continueWithAuth",
                          {"requestId": params["requestId"], "authChallengeResponse": answer})

    def quit(self):
        self.ws.close()
        self.driver.quit()
```



Every paused request is continued right away, and when a `Fetch.authRequired` event arrives we reply with our username and password. Note that we only do this when the challenge source is `Proxy` - otherwise any website asking for a login would get our proxy password too!

Finally, let's give it a spin by loading two pages through our proxy:

python

```python
options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
chrome = ChromeWithProxyAuth(os.environ["PROXY_HOST"], os.environ["PROXY_PORT"],
                             os.environ["PROXY_USER"], os.environ["PROXY_PASS"], options)
try:
    for url in ["https://httpbin.dev/anything", "https://httpbin.dev/anything?page=2"]:
        chrome.driver.get(url)
        data = json.loads(chrome.driver.find_element(By.TAG_NAME, "pre").text)
        print(data["url"], "->", data["origin"])
finally:
    chrome.quit()
```



If we run it, we can see both requests went out through the proxy:

text

```text
https://httpbin.dev/anything -> XXX.XXX.XXX.XXX
https://httpbin.dev/anything?page=2 -> XXX.XXX.XXX.XXX
```



This worked in both regular Chrome and Chrome for Testing. We also tried httpbin.dev's Basic-auth page, which stayed empty, so the proxy password never reached the website.

Unfortunately, this approach has two downsides. The first one is that every browser request has to wait for our Python script. The second is that it only works for the current tab, so popups and new tabs would need their own connection.

### Does a Manifest V3 Proxy Auth Extension Still Work in Chrome?

Not anymore. Chrome 137 removed the `--load-extension` flag ([Chrome Extensions news](https://developer.chrome.com/blog/extension-news-june-2025)), and in our tests Chrome 155 couldn't load our extension through either `add_extension()` or `--load-extension`.

However, extensions still work in Chrome for Testing and Chromium builds. Conveniently, [Selenium Manager](https://www.selenium.dev/documentation/selenium_manager/) downloads Chrome for Testing for us when Chrome isn't installed and saves it in `~/.cache/selenium/chrome`. So, we can use it with a small extension of our own.

First, let's build the extension. We'll put it in a `proxy_auth` folder, starting with the `manifest.json` file which tells Chrome what our extension is allowed to do:

json

```json
{
  "manifest_version": 3,
  "name": "proxy-auth",
  "version": "1.0",
  "permissions": ["webRequest", "webRequestAuthProvider"],
  "host_permissions": ["<all_urls>"],
  "background": {"service_worker": "background.js"}
}
```



Then comes the `background.js` script. It listens to Chrome's `onAuthRequired` event, which fires whenever a login is requested - both by proxies and by websites - so we check the [isProxy](https://developer.chrome.com/docs/extensions/reference/api/webRequest) field before handing out anything:

javascript

```javascript
chrome.webRequest.onAuthRequired.addListener(
  (details, callback) => {
    // websites can ask for logins too, we only answer the proxy
    if (!details.isProxy) return callback({});
    callback({authCredentials: {username: "PROXY_USER", password: "PROXY_PASS"}});
  },
  {urls: ["<all_urls>"]},
  ["asyncBlocking"]
);
```



Notice the `"PROXY_USER"` and `"PROXY_PASS"` placeholders? We don't want real credentials sitting in a file, so our Python script fills them in just before packing the extension into a zip and starting Chrome for Testing:

python

```python
import json
import os
import tempfile
import time
import zipfile

from selenium import webdriver
from selenium.webdriver.common.by import By

# fill in our proxy credentials and pack the extension into a zip
background = open("proxy_auth/background.js").read()
background = background.replace('"PROXY_USER"', json.dumps(os.environ["PROXY_USER"]))
background = background.replace('"PROXY_PASS"', json.dumps(os.environ["PROXY_PASS"]))
zip_path = os.path.join(tempfile.mkdtemp(), "proxy_auth.zip")
with zipfile.ZipFile(zip_path, "w") as z:
    z.write("proxy_auth/manifest.json", "manifest.json")
    z.writestr("background.js", background)

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument(f"--proxy-server=http://{os.environ['PROXY_HOST']}:{os.environ['PROXY_PORT']}")
options.add_extension(zip_path)
options.binary_location = os.environ["CHROME_FOR_TESTING"]

driver = webdriver.Chrome(options=options)
try:
    time.sleep(2)  # wait for the extension to start up
    driver.get("https://httpbin.dev/anything")
    print("origin:", json.loads(driver.find_element(By.TAG_NAME, "pre").text)["origin"])
finally:
    driver.quit()
    os.remove(zip_path)
```



If we run it, we'll see our proxy's IP address:

text

```text
origin: XXX.XXX.XXX.XXX
```



Note the `time.sleep(2)` call though! Without it, our first request went out before the extension was ready, and the page came back empty in all three of our test runs:

text

```text
page text: ''
```



### How Does a Local Forwarder Handle Proxy Credentials and SOCKS5?

What if we don't want Chrome to handle the login at all? In that case, we can keep the login out of the browser and start a local pproxy server without a password. It sends everything on to our real proxy and logs in for us:

python

```python
import json
import os
import subprocess
import time

from selenium import webdriver
from selenium.webdriver.common.by import By

scheme = os.environ.get("PROXY_SCHEME", "http")  # use "socks5" for socks5 proxies
upstream = (f"{scheme}://{os.environ['PROXY_HOST']}:{os.environ['PROXY_PORT']}"
            f"#{os.environ['PROXY_USER']}:{os.environ['PROXY_PASS']}")

# start local proxy without auth that forwards everything to our real proxy
forwarder = subprocess.Popen(["pproxy", "-l", "http://127.0.0.1:8897", "-r", upstream])
time.sleep(2)  # wait for pproxy to start

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument("--proxy-server=http://127.0.0.1:8897")
driver = webdriver.Chrome(options=options)
try:
    driver.get("https://httpbin.dev/anything")
    print("origin:", json.loads(driver.find_element(By.TAG_NAME, "pre").text)["origin"])
finally:
    driver.quit()
    forwarder.terminate()
```



Above, we start pproxy with `-l` set to our local address and `-r` set to our real proxy. Note the `#` symbol, which separates the proxy address from the credentials. Then, we simply point Chrome to `127.0.0.1:8897`, just like in our first example:

text

```text
origin: XXX.XXX.XXX.XXX
```



This trick also solves another problem - SOCKS5 proxies with authentication. Chrome can't authenticate SOCKS5 proxies at all ([Chromium docs](https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md)) and when we pointed it directly at ours, it failed with `net::ERR_SOCKS_CONNECTION_FAILED`. With the forwarder, all we have to do is set `PROXY_SCHEME=socks5` and pproxy takes care of the rest. For more on SOCKS5, see our [SOCKS5 proxies](https://scrapfly.io/blog/answers/what-are-socks5-proxies-in-web-scraping) article.

Note: pproxy 2.7.9 crashes on Python 3.14 with `RuntimeError: There is no current event loop`, so stick with Python 3.13 for now. Also, anyone who can connect to the forwarder can use our proxy account, so make sure it only listens on `127.0.0.1`.

### What About Selenium's BiDi Auth Handler and Selenium Wire?

Selenium also comes with its own [BiDi authentication handler](https://www.selenium.dev/documentation/webdriver/bidi/network/), which should handle auth prompts natively. Unfortunately, it didn't work for us at all. To rule out the proxy, we tried it on httpbin.dev's Basic-auth page with no proxy involved:

python

```python
from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.enable_bidi = True
driver = webdriver.Chrome(options=options)
driver.set_page_load_timeout(30)
try:
    # selenium's own handler should answer the login prompt for us
    driver.network.add_auth_handler("testuser", "testpass")
    driver.get("https://httpbin.dev/basic-auth/testuser/testpass")
    print(driver.find_element("tag name", "body").text)
except Exception as e:
    print(f"{type(e).__name__}: {str(e).splitlines()[0]}")
finally:
    driver.quit()
```



Instead of the login result, Selenium 4.51.0 waited out the whole 30 second page load timeout and gave up:

text

```text
TimeoutException: Message: timeout: Timed out receiving message from renderer: -0.005
```



We got the same timeout through our proxy too, so we can't recommend this handler yet.

As for [Selenium Wire](https://scrapfly.io/blog/posts/how-to-intercept-background-requests-with-selenium-wire) - older tutorials often recommend it for proxy authentication, however the project is archived and doesn't even import in a fresh Python 3.13 environment anymore (see the FAQ below for the full error).

Finally, if you're using [SeleniumBase](https://scrapfly.io/blog/posts/guide-to-seleniumbase-better-selenium) or [undetected ChromeDriver](https://scrapfly.io/blog/posts/web-scraping-without-blocking-using-undetected-chromedriver), both have their own proxy options, which are covered in their respective guides.



## How Do You Check That Selenium Is Using the Proxy?

As we've seen earlier, a broken proxy setup doesn't raise any errors in Selenium - we just get an error page or a blank page. So, before we start scraping, we should load a page that shows our IP address and confirm the proxy is really working:

python

```python
import json

from selenium.webdriver.common.by import By


def check_exit(driver):
    # load a page that shows our IP address
    driver.get("https://httpbin.dev/anything")
    pre = driver.find_elements(By.TAG_NAME, "pre")
    # no JSON means we got chrome's error page or a blank page
    if not pre:
        raise RuntimeError(f"proxy check failed: {driver.find_element(By.TAG_NAME, 'body').text[:80]!r}")
    return json.loads(pre[0].text)["origin"]
```



Here, our `check_exit()` function (saved as `check_exit.py`, since the later examples import it) looks for httpbin.dev's JSON response and raises an exception if it isn't there. Let's try it with all three setups from earlier - the open proxy, the credentials in the `--proxy-server` flag and the password proxy without credentials:

text

```text
exit IP: XXX.XXX.XXX.XXX
proxy check failed: 'This site can’t be reached\nThe webpage at https://httpbin.dev/anything might be '
proxy check failed: ''
```



The first one works and returns our IP, while the other two fail right away. The second line is Chrome's `ERR_NO_SUPPORTED_PROXIES` page, and the third one is the blank page.

What about the proxy's location? For this, we can open our [IP info tool](https://scrapfly.io/web-scraping-tools/ip-info) in the same proxied browser and see whether the country and network match what the proxy provider promised. The browser can also leak our real details around the proxy, which we can check with the [WebRTC leak test](https://scrapfly.io/web-scraping-tools/webrtc-leak) and the [timezone and Intl test](https://scrapfly.io/web-scraping-tools/timezone-intl).

For example, in our proxied run the timezone page still showed our machine's own timezone (`Africa/Cairo`) and an `en-US` locale. We couldn't test WebRTC this way, as our test proxy runs on the same machine and shares its IP, so make sure to run that one with your real proxy.



## How Do You Rotate Proxies in Selenium?

Unlike HTTP clients where we can pick a different proxy for every request, Selenium's proxy is set on the browser level. Meaning, the proxy is passed to Chrome through the `--proxy-server` flag on startup and it can't be changed for as long as the browser is running.

What about giving each tab a proxy of its own? The Chrome DevTools Protocol does have a `Target.createBrowserContext` command which accepts a `proxyServer` option, however when executed through Selenium Chrome simply refuses it with a `{"code":-32000,"message":"Not allowed"}` error.

So, to rotate proxies with Selenium we essentially have to restart the browser with a new proxy. For the rotation logic itself (weighting, health scores, cool-downs etc.) see our [proxy rotation strategy](https://scrapfly.io/blog/posts/how-to-rotate-proxies-in-web-scraping) guide.

### Relaunch per Proxy or Rotating Gateway: Which Pattern Fits?

When buying proxies we'll usually get one of two things: a rotating gateway or a plain list of proxy addresses.

Rotating gateways are the easiest option as it's a single proxy address and the IP rotation happens on the provider's side - we just use it like any other proxy! On the other hand, with a list of proxies the rotation is up to us, so let's start off with our proxy pool:

python

```python
import itertools
import os

from selenium import webdriver
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.common.by import By

from check_exit import check_exit

proxies = os.environ["PROXY_LIST"].split(",")  # host:port,host:port,...
pool = itertools.cycle(proxies)
bad = set()
```



Our proxy list comes from the `PROXY_LIST` environment variable and we wrap it in `itertools.cycle()`. In other words, the pool never runs out - after the last proxy it simply goes back to the first one. That being said, it would also keep handing out dead proxies, so we'll mark those in our `bad` set and skip them.

Let's put together what we've learned and build our rotating scraper:

python

```python
def make_driver(proxy):
    options = webdriver.ChromeOptions()
    options.add_argument("--headless=new")
    options.add_argument(f"--proxy-server=http://{proxy}")
    return webdriver.Chrome(options=options)


def scrape_with_rotation(url, attempts=3):
    for attempt in range(attempts):
        if len(bad) == len(set(proxies)):
            raise Exception("all proxies are marked as bad")
        proxy = next(pool)
        while proxy in bad:
            proxy = next(pool)
        driver = make_driver(proxy)
        try:
            check_exit(driver)
            driver.get(url)
            return proxy, driver.find_element(By.CSS_SELECTOR, "h3.product-title").text
        except (WebDriverException, RuntimeError) as error:
            print(f"  {proxy} failed: {str(error).splitlines()[0]}")
            bad.add(proxy)
        finally:
            driver.quit()
    raise Exception(f"couldn't scrape {url} in {attempts} attempts")
```



In the example above, our `make_driver()` function starts a headless Chrome browser with the given proxy, while `scrape_with_rotation()` makes up to 3 attempts per URL. For each attempt we pull proxies from the pool until we find one that isn't marked as bad, and check it with our `check_exit()` function before scraping. If anything fails along the way, the proxy goes into the `bad` set and we try the next one.

Finally, let's scrape the first three product pages with it:

python

```python
for i in range(1, 4):
    proxy, title = scrape_with_rotation(f"https://web-scraping.dev/product/{i}")
    print(f"product {i}: {title!r} via {proxy}")
```



For testing, our `PROXY_LIST` had a dead port (`8009`) between two working ones, and running the script gives us:

text

```text
product 1: 'Box of Chocolate Candy' via 127.0.0.1:8001
  127.0.0.1:8009 failed: Message: unknown error: net::ERR_PROXY_CONNECTION_FAILED
product 2: 'Dark Red Energy Potion' via 127.0.0.1:8002
product 3: 'Teal Energy Potion' via 127.0.0.1:8001
```



As you can see, the dead `8009` proxy was picked for product 2 first, so the scraper retried it through `8002`, and product 3 started the cycle again with `8001`. Our proxy logs agree too - `8001` handled 4 web-scraping.dev connections and `8002` handled 2.

That being said, each retry here costs us a whole new Chrome browser, which is why we recommend rotating proxies per scrape job rather than per page.

### When Should a Selenium Session Keep a Sticky IP?

Rotating proxies isn't always what we want though! Some scraping tasks like logging in, shopping carts or form submissions require the same IP to maintain the connection session.

This is especially important with rotating gateways as a single page load in a web browser is actually many connections. For example, when we loaded a single product page our browser opened tunnels not only to web-scraping.dev but to seven different Google hosts as well (more on this in the next section). If the gateway rotates the IP for every connection, these would all end up coming from different IPs!

For this, we'd enable the sticky session option of our proxy provider. If it's configured through the proxy username, any of the authentication methods we've covered above will work just fine.



## How Do You Use Residential Proxies with Selenium Without Wasting Bandwidth?

Residential proxies are used just like any other authenticated proxy, meaning in Selenium we can connect them through the CDP helper or the local forwarder we've built earlier. That being said, residential proxies are usually priced by bandwidth (per GB) and web browsers download a lot more than just the HTML page we're after, so we should be careful about what we send through them.

The first bandwidth sink is surprisingly Chrome itself! Even in headless mode Chrome talks to various Google services in the background and all of that traffic goes through our proxy as well. For example, this is how our proxy log looks after loading a single web-scraping.dev product page, counted by host:

text

```text
   3 http 127.0.0.1 -> web-scraping.dev:443
   2 http 127.0.0.1 -> accounts.google.com:443
   2 http 127.0.0.1 -> update.googleapis.com:443
   2 http 127.0.0.1 -> www.google.com:443
   1 http 127.0.0.1 -> android.clients.google.com:443
   1 http 127.0.0.1 -> clients2.google.com:80
   1 http 127.0.0.1 -> edgedl.me.gvt1.com:80
   1 http 127.0.0.1 -> mtalk.google.com:5228
```



As you can see, only 3 connections actually go to our target while the other 10 tunnels are Chrome's own background traffic - which we'd be paying for! To fix this, we can tell Chrome to connect to Google domains directly using the `--proxy-bypass-list` argument:

python

```python
import os

from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument(f"--proxy-server=http://{os.environ['PROXY_HOST']}:{os.environ['PROXY_PORT']}")
# Chrome's own background traffic goes direct instead of through the paid proxy
options.add_argument("--proxy-bypass-list=*.google.com;*.googleapis.com;*.gvt1.com")

driver = webdriver.Chrome(options=options)
try:
    driver.get("https://web-scraping.dev/product/1")
    print("title:", driver.title)
finally:
    driver.quit()
```



After this change our proxy log only had the three web-scraping.dev lines left. The bypass list applies to every page the browser loads, though, so if your target is a Google site itself, those pages skip the proxy and load from your real IP.

What about images, fonts and other media? Our scraper doesn't need them either, so we can [block resources in Selenium](https://scrapfly.io/blog/answers/how-to-block-resources-in-selenium) or use [image and CSS stubbing to save proxy bandwidth](https://scrapfly.io/blog/posts/optimize-proxy-bandwidth-with-image-and-css-stubbing) instead.

Alternatively, if you'd like to keep your current proxy provider, Scrapfly's [Proxy Saver](https://scrapfly.io/docs/proxy-saver/getting-started) can be placed in front of it to reduce the bandwidth (enable its "Rotating Proxy" setting if your provider rotates IPs). For our CDP helper or forwarder it's just a matter of switching the connection details to `proxy-saver.scrapfly.io:3333` with `proxyId-XXX` as the username and your Scrapfly API key as the password - the [Proxy Saver certificates](https://scrapfly.io/docs/proxy-saver/certificates) page covers the extra HTTPS setup.



Scrapfly

#### Need a cloud browser for scraping?

Run headless browsers at scale with Scrapfly Cloud Browser — no infrastructure to manage.

[Try Free →](https://scrapfly.io/register)## What Do Selenium Proxy Errors Like ERR\_NO\_SUPPORTED\_PROXIES and 407 Mean?

Unfortunately, not all proxy errors raise an exception in Selenium. For example, if we put `user:pass@` into the `--proxy-server` flag Chrome will just render its `ERR_NO_SUPPORTED_PROXIES` error page. If we leave the credentials out instead, the proxy responds with a 407 status code and we get a blank page. In both of these cases the `driver.get()` call finishes without errors.

Out of the broken proxy setups we tried, only the dead proxy raised a `WebDriverException` (`net::ERR_PROXY_CONNECTION_FAILED`), so that's why we call `check_exit()` and look for `origin` before scraping.



## Why Does Selenium Still Get Blocked Behind a Working Proxy?

Unfortunately, a proxy only changes the IP address of our scraper. Websites can still see the browser and a default headless Selenium Chrome doesn't hide much.

Let's take a look at what a default headless Chrome reports when it's connected through our proxy:

python

```python
import os

from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument(f"--proxy-server=http://{os.environ['PROXY_HOST']}:{os.environ['PROXY_PORT']}")
driver = webdriver.Chrome(options=options)
try:
    driver.get("https://web-scraping.dev/")
    for check in ["navigator.userAgent", "navigator.webdriver", "navigator.languages",
                  "Intl.DateTimeFormat().resolvedOptions().timeZone"]:
        print(f"{check}: {driver.execute_script(f'return {check}')}")
finally:
    driver.quit()
```



If we run this with branded Chrome 155, we'll see:

text

```text
navigator.userAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/155.0.0.0 Safari/537.36
navigator.webdriver: True
navigator.languages: ['en-US', 'en']
Intl.DateTimeFormat().resolvedOptions().timeZone: Africa/Cairo
```



As you can see, the user agent openly says `HeadlessChrome`, `navigator.webdriver` is set to `True` and the timezone is our own machine's (`Africa/Cairo`), no matter which country our proxy exits in. Chrome for Testing gave us the same output with only one difference - `navigator.languages` was `['en-US']`.

For example, if our proxy is a German residential IP, the website would see a German visitor with a US English browser set to Cairo time. Anti-bot systems check the IP together with the [browser fingerprint](https://scrapfly.io/blog/posts/how-browser-fingerprinting-works), TLS and automation markers, as we've explained in [how anti-bot detection works](https://scrapfly.io/blog/posts/how-anti-bot-detection-works). This article sticks to proxies, though if you'd like to patch the browser itself see our [undetected ChromeDriver](https://scrapfly.io/blog/posts/web-scraping-without-blocking-using-undetected-chromedriver) and SeleniumBase guides, and for specific anti-bot vendors our [anti-bot bypass guides](https://scrapfly.io/bypass).

### How Do You Match Timezone and Language to the Proxy Country?

So how do we fix these leaks? The easiest way is to set up each driver for a single country. Chrome has an `--accept-lang` argument for the browser language, while the timezone is a bit trickier as Chrome reads it from the `TZ` environment variable of the driver process. Let's give it a shot with a proxy that exits in Germany:

python

```python
import json
import os

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument(f"--proxy-server=http://{os.environ['PROXY_HOST']}:{os.environ['PROXY_PORT']}")
options.add_argument("--accept-lang=de-DE,de")  # german language for our german proxy
service = Service(env={**os.environ, "TZ": "Europe/Berlin"})  # chrome picks up the timezone from here
```



Now, to see whether it worked we'll load httpbin.dev's headers page and ask the browser itself for its languages, timezone and locale:

python

```python
driver = webdriver.Chrome(options=options, service=service)
try:
    driver.get("https://httpbin.dev/headers")
    headers = json.loads(driver.find_element(By.TAG_NAME, "pre").text)["headers"]
    print("Accept-Language:", headers["Accept-Language"])
    print("navigator.languages:", driver.execute_script("return navigator.languages"))
    print("timezone:", driver.execute_script("return Intl.DateTimeFormat().resolvedOptions().timeZone"))
    print("Intl locale:", driver.execute_script("return Intl.DateTimeFormat().resolvedOptions().locale"))
    driver.execute_cdp_cmd("Emulation.setTimezoneOverride", {"timezoneId": "America/New_York"})
    print("after override:", driver.execute_script("return Intl.DateTimeFormat().resolvedOptions().timeZone"))
finally:
    driver.quit()
```



At the end we also try the `Emulation.setTimezoneOverride` CDP command, which changes the timezone of the current tab:

text

```text
Accept-Language: ['de-DE,de;q=0.9']
navigator.languages: ['de-DE', 'de']
timezone: Europe/Berlin
Intl locale: en-US
after override: America/New_York
```



The language header, `navigator.languages` and the timezone now all point to Germany, however the `Intl` locale is still `en-US`. Meaning, our browser is only partially matched to the proxy country, and matching these values alone doesn't guarantee we won't get blocked.



## When Should a Blocked Selenium Job Move to a Cloud Browser?

That being said, a proxy only fixes the IP part of our scraper. If the website is detecting the browser itself then no proxy will help and this is where Scrapfly's [Cloud Browser](https://scrapfly.io/products/cloud-browser-api) comes in handy - let's take a quick look!

Cloud Browser runs a Chromium browser on Scrapfly's side with the proxy pool and country set for each session. Unfortunately, Selenium can't connect to it (see the [Cloud Browser Selenium integration](https://scrapfly.io/docs/cloud-browser-api/selenium) page for details) so we'll be using Playwright instead:

python

```python
import json
import os

from playwright.sync_api import sync_playwright
from scrapfly import BrowserConfig, ProxyPool, ScrapflyClient

client = ScrapflyClient(key=os.environ["SCRAPFLY_KEY"])
ws_url = client.cloud_browser(
    BrowserConfig(proxy_pool=ProxyPool.RESIDENTIAL, country="de")
    # ^ use German residential proxies for this browser session
)

with sync_playwright() as pw:
    browser = pw.chromium.connect_over_cdp(ws_url)
    page = browser.contexts[0].new_page()
    page.goto("https://httpbin.dev/anything")
    print(json.loads(page.inner_text("pre"))["origin"])
    print(page.evaluate("navigator.languages"))
    print(page.evaluate("Intl.DateTimeFormat().resolvedOptions().timeZone"))
    print(page.evaluate("navigator.webdriver"))
    browser.close()
```



In the example above, we're requesting a browser session with a German residential proxy and connecting to it through Playwright's `connect_over_cdp()`. If we run it, we can see that the browser's timezone and languages match the proxy location (see [Cloud Browser proxy and geo-targeting](https://scrapfly.io/docs/cloud-browser-api/proxy)):

text

```text
92.208.x.x
['de-DE', 'de', 'en-US', 'en']
Europe/Berlin
False
```



### When Is the Web Scraping API Enough Instead of Selenium?

What about scrapers that don't need a browser at all? For simple page scraping Scrapfly's [Web Scraping API](https://scrapfly.io/products/web-scraping-api) can render JavaScript and handle proxies and the [Unblocker (previously ASP)](https://scrapfly.io/products/unblocker) on its own:

python

```python
import os

from scrapfly import ScrapeConfig, ScrapflyClient

client = ScrapflyClient(key=os.environ["SCRAPFLY_KEY"])
result = client.scrape(ScrapeConfig(
    url="https://web-scraping.dev/product/1",
    render_js=True,
    unblocker=True,  # asp=True is the older alias
    country="de",
))
print("status:", result.status_code)
print("html length:", len(result.content))
```



If we run it, we'll get:

text

```text
status: 200
html length: 38699
```



By deferring the browser to Scrapfly we don't need to run Chrome on our own machine at all!



## Which Selenium Proxy Setup Should You Use?

Not all Selenium proxy setups are made equal, even if they all end up in the same `--proxy-server` flag. For IP-allowlisted proxies that flag is all we need, so the real choice starts when the proxy asks for a username and password.

For branded Chrome, our pick is the CDP helper. On the bright side, it lives right inside our Python script and worked in both regular Chrome and Chrome for Testing. That being said, every browser request has to wait for our script, and popups or new tabs need their own connection.

The local forwarder doesn't have these problems as Chrome talks to a plain local proxy, though it means running pproxy as a subprocess. Meaning, if your proxy is an authenticated SOCKS5 one, the forwarder is the only setup from this article that works!

So what about rotation and blocking? If you're rotating through a list, a new driver per job does the trick, while logins need a sticky session. To cut the proxy bill there's Proxy Saver, and if the target blocks the browser itself rather than the IP, we'd move on to Cloud Browser or the Web Scraping API.



### Power your scraping with Scrapfly

Forget about getting blocked. Scrapfly handles anti-bot bypasses, browser rendering, and proxy rotation so you can focus on the data.



[Try for FREE!](https://scrapfly.io/register)



## FAQ

Does Selenium Wire still work in 2026?Unfortunately, no! We gave it a shot in a fresh Python 3.13 environment with Selenium 4.51.0 and setuptools installed:

shell

```shell
$ pip list | grep -E "selenium|setuptools"
selenium          4.51.0
selenium-wire     5.1.0
setuptools        84.0.0
$ python -c "from seleniumwire import webdriver"
...
  File ".../site-packages/seleniumwire/webdriver.py", line 8, in <module>
    from pkg_resources import parse_version
ModuleNotFoundError: No module named 'pkg_resources'
```



Setuptools removed `pkg_resources` in version 82, so setuptools 84 can't provide it, and installing `packaging` on top only moves the crash to a missing `blinker._saferef` module. Since the project is archived, we wouldn't count on a fix either - for authenticated proxies, our CDP helper or local forwarder will do the job.







Should you use free proxies with Selenium?We'd rather not! A free proxy goes into the same `--proxy-server` flag as a paid one, but whoever runs it gets to see every host our browser visits. For example, here's part of our own test proxy's log from a single `check_exit()` call:

text

```text
http 127.0.0.1:53185 -> update.googleapis.com:443
http 127.0.0.1:53192 -> httpbin.dev:443
http 127.0.0.1:53193 -> httpbin.dev:443
http 127.0.0.1:53194 -> httpbin.dev:443
http 127.0.0.1:53195 -> accounts.google.com:443
```



On top of that, plain HTTP traffic can be read in full, so we wouldn't send logins or session cookies through a free proxy.







How do you use a proxy with Selenium Grid or Remote WebDriver?Grid takes the same Chrome options as a local driver, so the only change is `webdriver.Remote` instead of `webdriver.Chrome`. We tried it on a standalone Selenium Grid 4.51.0 with one of our test proxies:

python

```python
from selenium import webdriver

from check_exit import check_exit

proxy = "127.0.0.1:8001"

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument(f"--proxy-server=http://{proxy}")
driver = webdriver.Remote(command_executor="http://localhost:4444", options=options)
try:
    print("exit IP:", check_exit(driver))
finally:
    driver.quit()
```



Running it prints:

text

```text
exit IP: XXX.XXX.XXX.XXX
```



Our Grid ran on the same machine, so the proxy at `127.0.0.1` worked. On a real Grid the browser runs on the node, meaning the proxy has to be reachable from the node - a forwarder on our laptop's `127.0.0.1` won't be! See our [Selenium Grid](https://scrapfly.io/blog/posts/intro-to-web-scraping-using-selenium-grid) guide for the setup.







Should you use residential or datacenter proxies with Selenium?We usually recommend starting with datacenter proxies and only switching to residential ones when the target blocks datacenter IP ranges or shows different content by location. Residential proxies are usually priced per GB though, so the `--proxy-bypass-list` flag from earlier helps a lot there - it cut 10 of the 13 tunnels from our product page! For picking a provider, see our [datacenter proxies](https://scrapfly.io/blog/posts/the-best-datacenter-proxies) and [residential proxy providers](https://scrapfly.io/blog/posts/top-5-residential-proxy-providers) guides.







Can you use a VPN instead of a proxy with Selenium?A VPN moves the whole machine's traffic to one exit, so every driver shares it, and switching exits happens outside our Selenium code. Proxies give each driver its own exit. For more, see [VPNs as proxies](https://scrapfly.io/blog/answers/vpn-as-proxies-in-web-scraping).









## Summary

In this in-depth tutorial, we learned how proxies work in Selenium through real web-scraping.dev examples. We explored the `--proxy-server` flag, proxy authentication through CDP or a local forwarder and proxy rotation with a new driver per proxy. We've also taken a look at saving residential proxy bandwidth by keeping Chrome's background traffic off the proxy. Finally, we wrapped everything up by matching the browser's timezone and language to the proxy country, as a proxy only changes the IP address.

Selenium proxies can be a hassle to work with, so if you'd like to keep your code and provider, try putting Proxy Saver in front of your proxy. For websites that keep blocking the browser itself, Scrapfly Cloud Browser and the Web Scraping API can take it from there!



Legal Disclaimer and PrecautionsThis tutorial covers popular web scraping techniques for education. Interacting with public servers requires diligence and respect:

- Do not scrape at rates that could damage the website.
- Do not scrape data that's not available publicly.
- Do not store PII of EU citizens protected by GDPR.
- Do not repurpose *entire* public datasets which can be illegal in some countries.

Scrapfly does not offer legal advice but these are good general rules to follow. For more you should consult a lawyer.

 

   [  Add as a preferred source ](https://google.com/preferences/source?q=scrapfly.io) Table of Contents















 

  Table of Contents

- [Key Takeaways](#key-takeaways)
- [How Do You Set a Proxy in Selenium with --proxy-server?](#how-do-you-set-a-proxy-in-selenium-with-proxy-server)
- [How Do You Set a Proxy in Firefox with Selenium?](#how-do-you-set-a-proxy-in-firefox-with-selenium)
- [Why Does Chrome Ignore Proxy Credentials in Selenium?](#why-does-chrome-ignore-proxy-credentials-in-selenium)
- [How Do You Use an Authenticated Proxy in Selenium?](#how-do-you-use-an-authenticated-proxy-in-selenium)
- [How Do You Answer Proxy Authentication with CDP in Chrome?](#how-do-you-answer-proxy-authentication-with-cdp-in-chrome)
- [Does a Manifest V3 Proxy Auth Extension Still Work in Chrome?](#does-a-manifest-v3-proxy-auth-extension-still-work-in-chrome)
- [How Does a Local Forwarder Handle Proxy Credentials and SOCKS5?](#how-does-a-local-forwarder-handle-proxy-credentials-and-socks5)
- [What About Selenium's BiDi Auth Handler and Selenium Wire?](#what-about-selenium-s-bidi-auth-handler-and-selenium-wire)
- [How Do You Check That Selenium Is Using the Proxy?](#how-do-you-check-that-selenium-is-using-the-proxy)
- [How Do You Rotate Proxies in Selenium?](#how-do-you-rotate-proxies-in-selenium)
- [Relaunch per Proxy or Rotating Gateway: Which Pattern Fits?](#relaunch-per-proxy-or-rotating-gateway-which-pattern-fits)
- [When Should a Selenium Session Keep a Sticky IP?](#when-should-a-selenium-session-keep-a-sticky-ip)
- [How Do You Use Residential Proxies with Selenium Without Wasting Bandwidth?](#how-do-you-use-residential-proxies-with-selenium-without-wasting-bandwidth)
- [What Do Selenium Proxy Errors Like ERR\_NO\_SUPPORTED\_PROXIES and 407 Mean?](#what-do-selenium-proxy-errors-like-err-no-supported-proxies-and-407-mean)
- [Why Does Selenium Still Get Blocked Behind a Working Proxy?](#why-does-selenium-still-get-blocked-behind-a-working-proxy)
- [How Do You Match Timezone and Language to the Proxy Country?](#how-do-you-match-timezone-and-language-to-the-proxy-country)
- [When Should a Blocked Selenium Job Move to a Cloud Browser?](#when-should-a-blocked-selenium-job-move-to-a-cloud-browser)
- [When Is the Web Scraping API Enough Instead of Selenium?](#when-is-the-web-scraping-api-enough-instead-of-selenium)
- [Which Selenium Proxy Setup Should You Use?](#which-selenium-proxy-setup-should-you-use)
- [FAQ](#faq)
- [Summary](#summary)
 
    Join the Newsletter  Get monthly web scraping insights 

 

  



Scale Your Web Scraping

Anti-bot bypass, browser rendering, and rotating proxies, all in one API. Start with 1,000 free credits.

  No credit card required  1,000 free API credits  Anti-bot bypass included 

 [Start Free](https://scrapfly.io/register) [View Docs](https://scrapfly.io/docs/onboarding) 

 Not ready? Get our newsletter instead. 

 

 ## Related Articles

 [  

 python nodejs 

### How to use Headless Chrome Extensions for Web Scraping

In this article, we'll explore different useful Chrome extensions for web scraping. We'll also explain how to install Ch...

 

 ](https://scrapfly.io/blog/posts/how-to-use-browser-extensions-with-playwright-puppeteer-and-selenium) [  

 curl 

### How to Use cURL GET Requests

Here's everything you need to know about cURL GET requests and some common pitfalls you should avoid.

 

 ](https://scrapfly.io/blog/posts/how-to-use-curl-get-requests) [  

 python headless-browser 

### Web Scraping with Selenium and Python

Introduction to web scraping dynamic javascript powered websites and web apps using Selenium browser automation library ...

 

 ](https://scrapfly.io/blog/posts/web-scraping-with-selenium-and-python) 

  ## Related Questions

- [ Q How to Copy as cURL With Firefox? ](https://scrapfly.io/blog/answers/how-to-copy-as-curl-with-firefox)
- [ Q How to save and load cookies in Selenium? ](https://scrapfly.io/blog/answers/how-to-save-and-load-cookies-in-selenium)
- [ Q Selenium: chromedriver executable needs to be in PATH? ](https://scrapfly.io/blog/answers/selenium-chromedriver-in-path)
- [ Q What Python libraries support HTTP2? ](https://scrapfly.io/blog/answers/what-python-libraries-support-http2)
 
  



   



 Run headless browsers at scale, **1,000 free credits** [Start Free](https://scrapfly.io/register)