All guides
DEVELOPER GUIDES · 5 MIN READ

How to configure a proxy in Pydoll

Route Pydoll’s Chromium browser through a proxy, verify its reported exit address, and validate required page content.

How to configure a proxy in Pydoll

Pass --proxy-server to Pydoll’s ChromiumOptions, then check an IP response and a required target marker through the browser. Pydoll answers HTTP proxy authentication challenges from credentials in the proxy URL; authenticated SOCKS5 credentials require its documented local SOCKS5Forwarder because Chrome does not send them from that URL.

Prepare the browser

Use Python 3.12 and Google Chrome 154.0.8037.58 or a compatible Chrome build. This example was run with Python 3.12.0 and Chrome 154.0.8037.58 on macOS. Install Python from python.org and Chrome from the official Chrome downloads page.

Warning: Proxy credentials are sensitive, and a cleartext HTTP proxy hop can expose them on the local network. Use a trusted runtime secret store and an HTTPS proxy endpoint when supported. Run only against a permitted, read-only target; remove temporary credentials after testing.

Install the tested Pydoll release:

python3.12 -m venv .venv
. .venv/bin/activate
python -m pip install 'pydoll-python==2.27.0'

Set PROXY_SERVER, PROXY_USERNAME, PROXY_PASSWORD, IP_CHECK_URL, TARGET_URL, and EXPECTED_MARKER in the runtime environment. PROXY_SERVER contains the scheme, host, and port only. Pydoll’s proxy manager removes credentials from the Chromium startup argument before launch; avoid debug logging or printing the options object because configuration logging can reveal secrets.

Run one bounded check

Save the complete example as pydoll-check.py. It navigates to the IP endpoint and target in the browser, then reads each Document response event for the main-frame navigation using Pydoll’s network event API. A Chrome DevTools Page.getFrameTree command lookup ties each event to the top-level frame; a redirect to a different final URL fails explicitly. Browser navigation also avoids cross-origin fetch restrictions on the IP endpoint. The script inspects no more than 12,000 characters of rendered text.

import asyncio
import ipaddress
import json
import os
from urllib.parse import urlsplit

from pydoll.browser.chromium import Chrome
from pydoll.browser.options import ChromiumOptions
from pydoll.commands.page_commands import PageCommands
from pydoll.protocol.network.events import NetworkEvent


PROXY_SERVER = os.environ["PROXY_SERVER"]
PROXY_USERNAME = os.environ["PROXY_USERNAME"]
PROXY_PASSWORD = os.environ["PROXY_PASSWORD"]
IP_CHECK_URL = os.environ["IP_CHECK_URL"]
TARGET_URL = os.environ["TARGET_URL"]
EXPECTED_MARKER = os.environ["EXPECTED_MARKER"].strip()
MAX_TEXT_CHARS = 12_000

proxy_parts = urlsplit(PROXY_SERVER)
try:
    proxy_port = proxy_parts.port
except ValueError:
    proxy_port = None
if (
    proxy_parts.scheme not in {"http", "https"}
    or not proxy_parts.hostname
    or proxy_port is None
    or not 1 <= proxy_port <= 65_535
    or proxy_parts.username
    or proxy_parts.password
    or proxy_parts.path not in {"", "/"}
    or proxy_parts.query
    or proxy_parts.fragment
):
    raise SystemExit("Set PROXY_SERVER to an HTTP or HTTPS host and port without credentials")
if not EXPECTED_MARKER:
    raise SystemExit("EXPECTED_MARKER must not be empty")


async def main() -> None:
    if not PROXY_USERNAME or not PROXY_PASSWORD:
        raise SystemExit("Both proxy credential variables are required")
    if any(char in ":@" for char in PROXY_USERNAME + PROXY_PASSWORD):
        raise SystemExit("Proxy credentials cannot contain colon or at sign in this URL format")
    proxy_url = (
        f"{PROXY_SERVER.split('://', 1)[0]}://"
        f"{PROXY_USERNAME}:{PROXY_PASSWORD}@"
        f"{PROXY_SERVER.split('://', 1)[1]}"
    )
    options = ChromiumOptions()
    options.add_argument(f"--proxy-server={proxy_url}")

    async with Chrome(options=options) as browser:
        tab = await asyncio.wait_for(browser.start(), timeout=30)
        await tab.enable_network_events()
        document_response = asyncio.Event()
        document_responses = []

        async def capture_document(event: dict) -> None:
            params = event.get("params", {})
            response = params.get("response", {})
            if params.get("type") == "Document":
                document_responses.append(
                    (params.get("frameId"), response.get("url"), int(response.get("status", 0)))
                )
                document_response.set()

        await tab.on(NetworkEvent.RESPONSE_RECEIVED, capture_document)

        async def navigate_and_read(url: str) -> tuple[int, str]:
            document_responses.clear()
            document_response.clear()
            await asyncio.wait_for(tab.go_to(url, timeout=20), timeout=25)
            await asyncio.wait_for(document_response.wait(), timeout=5)
            frame_tree = await asyncio.wait_for(
                tab._execute_command(PageCommands.get_frame_tree()), timeout=5
            )
            main_frame_id = frame_tree["result"]["frameTree"]["frame"]["id"]
            main_responses = [item for item in document_responses if item[0] == main_frame_id]
            if not main_responses:
                raise SystemExit("No main-frame document response was observed")
            _, response_url, status = main_responses[-1]
            if response_url != url:
                raise SystemExit("Main-frame navigation ended at a different URL")
            evaluation = await asyncio.wait_for(
                tab.execute_script(
                    f"return document.body?.innerText.slice(0, {MAX_TEXT_CHARS}) || ''",
                    return_by_value=True,
                    timeout=5_000,
                ),
                timeout=10,
            )
            result = evaluation.get("result", {})
            if "error" in evaluation or "exceptionDetails" in result:
                raise SystemExit("Browser text extraction failed")
            text = result.get("result", {}).get("value")
            if not isinstance(text, str):
                raise SystemExit("Browser returned no document text")
            return status, text

        ip_status, ip_text = await navigate_and_read(IP_CHECK_URL)
        if ip_status != 200:
            raise SystemExit("IP check did not return HTTP 200")
        ip_payload = json.loads(ip_text)
        observed_ip = ip_payload.get("origin")
        if not isinstance(observed_ip, str):
            raise SystemExit("IP check returned no origin value")
        ipaddress.ip_address(observed_ip.strip())

        target_status, text = await navigate_and_read(TARGET_URL)
        if target_status != 200:
            raise SystemExit("Target did not return HTTP 200")
        if not isinstance(text, str) or EXPECTED_MARKER not in text:
            raise SystemExit("Required target marker was not found")

        print({"exit_ip_present": True, "target_status": target_status, "content_valid": True})


try:
    asyncio.run(main())
except Exception as error:
    raise SystemExit(f"Browser check failed ({type(error).__name__})") from None

Run the saved script with the environment variables set: python pydoll-check.py.

Expected output

The script prints this only after the IP echo returned HTTP 200 with a valid IPv4 or IPv6 address and the target returned HTTP 200 with the required marker. exit_ip_present means the echo value passed IP parsing; the address is not printed. target_status is the target document’s HTTP status, and content_valid means the marker was found in the inspected content. This line matches the successful task-owned local-fixture run; it is an example result, not a claim about a proxy provider or an external target.

{'exit_ip_present': True, 'target_status': 200, 'content_valid': True}

This sample calls tab._execute_command(PageCommands.get_frame_tree()) to send Page.getFrameTree through Pydoll’s private command helper and associate the response with the current top-level frame. That helper is an implementation detail verified against the pinned Pydoll 2.27.0 API, not a public compatibility promise; recheck it when upgrading Pydoll.

The script makes two explicit browser navigations: one to the IP check and one to the target. Browser startup can make its own network calls, the browser loads subresources, and this example does not establish a global request budget or disable every browser or proxy retry. asyncio.wait_for bounds startup, navigation, response-event waits, frame lookup, and text extraction; context-manager shutdown still needs an outer worker deadline for a hard whole-job limit. The character cap applies after navigation, not to bytes downloaded or browser subresources.

Choose the supported proxy route

Proxy type Authentication Pydoll setup
HTTP or HTTPS Username and password challenge Put credentials in the proxy URL at runtime; Pydoll handles the 407 challenge
SOCKS5 without authentication No credentials Pass a socks5://host:port proxy URL
SOCKS5 with authentication Username and password Run SOCKS5Forwarder locally and point Chrome to its unauthenticated loopback endpoint

Chrome ignores credentials embedded in an authenticated SOCKS5 URL and does not issue the HTTP 407 challenge that Pydoll handles. For the forwarder’s parameters and lifecycle, use the official Pydoll proxy guide.

Diagnose a failed request

Observation Check next What it establishes
Browser fails to start Confirm Chrome is installed and discoverable No network check ran
HTTP proxy returns 407 Verify credentials and proxy auth method The proxy rejected or did not receive credentials
SOCKS5 authentication fails Use SOCKS5Forwarder or an unauthenticated endpoint Chrome does not send SOCKS5 URL credentials
IP check passes, target is not 200 Inspect the target response and access policy Egress reachability does not establish target access
Target returns 200, marker is absent Inspect rendered text and parser requirements The page did not meet the content condition

For an HTTPS target, target TLS protects the browser-to-target connection. An http:// proxy leaves the browser-to-proxy hop unencrypted; an https:// proxy adds TLS on that separate hop when supported. A successful echo reports the address seen by that request. It does not promise a stable identity on the target request or acceptance by the site.

Use the sticky-session guide when a permitted multi-request browser flow needs one provider session.

Sources and further reading

ProxyLaneTraffic never expires

Premium residential proxies
From $2/GB

Scale your workflows with the tools you already use. Rotating or sticky sessions up to 72 hours.

Try 350 MB for $1.95 View plans

Keep reading

Amazon Scraper API vs Proxy: Choose a Product Data Source

Compare seller APIs, licensed Amazon product data and proxy page checks by record, access scope and reuse rights.

Read guide

Antidetect browser proxy setup: choose a profile guide

Choose the right anti-detect browser proxy guide, map the shared verification steps, and keep account access separate from the network route.

Read guide

Best Proxies for Market Research: Choose by Sample and Cost

Our picks for market research: ProxyLane for small, intermittent projects, plus options for regional tracking, monthly collection and managed extraction.

Read guide