Client policy runbook

Python Requests Proxy Setup: Sessions, Retries, and Errors

Configure HTTP or SOCKS proxies in Python Requests, keep environment settings predictable, reuse connections safely, and diagnose 407, DNS, TLS, timeout and retry behavior with locally reproduced evidence.

Python Requests

HTTP client library

T-07
T-07 / Client policyMake proxy policy explicit at the request boundaryserver-rendered route map

Session

cookies + connection pool

Request policy

proxy | timeout | Retry

Evidence ladder

proxy -> TLS -> HTTP status

Keep routing, pooling, timeouts, retries and TLS trust as separate controls so one failure does not trigger an unsafe change to every layer.

By Robert SmithReviewed by Michael Weber18 min runbook

Operating principle

Keep routing, pooling, timeouts, retries and TLS trust as separate controls so one failure does not trigger an unsafe change to every layer.

Start With an Explicit Per-Request Proxy Mapping

For an application-owned request, pass the proxy mapping on the request, use separate connect and read timeouts, and check the HTTP result explicitly. The mapping keys describe the destination URL scheme; the values describe the proxy connection. An HTTPS destination through an http:// proxy is normal: Requests asks the proxy for a CONNECT tunnel, then performs destination TLS through that tunnel.

import os
from urllib.parse import quote

import requests

user = quote(os.environ["DATABAY_PROXY_USER"], safe="")
password = quote(os.environ["DATABAY_PROXY_PASS"], safe="")
proxy_url = f"http://{user}:{password}@gw.databay.co:8888"
proxies = {"http": proxy_url, "https": proxy_url}

response = requests.get(
    "https://api.ipify.org?format=json",
    proxies=proxies,
    timeout=(5, 20),
)
response.raise_for_status()
print(response.json())

quote(..., safe="") percent-encodes reserved characters before the username and password enter a URL. Environment variables are convenient inputs, not a universal secret store: load them from the deployment platform's secret manager, never print proxy_url or proxies, and keep exception and debug logging from serializing them. Use a harmless endpoint you control when possible. An IP response describes one observed request; it does not prove privacy, future routing, trustworthiness, or destination permission.

Read the Two Schemes Correctly

The most common Requests configuration error is assuming the dictionary key names the proxy protocol. In {"https": "http://proxy:8080"}, https means “use this entry when the destination URL begins with HTTPS.” The value begins with http:// because the client speaks HTTP proxy protocol to the gateway.

DestinationMapping entryFirst proxy operationWhere destination TLS begins
http://service.example/"http": "http://proxy:8080"Absolute-form request such as GET http://service.example/...No destination TLS
https://service.example/"https": "http://proxy:8080"CONNECT service.example:443After the proxy accepts the tunnel
HTTP or HTTPS through SOCKS5Both keys point to socks5:// or socks5h://SOCKS method negotiation and CONNECTAfter the SOCKS connection succeeds

Using an https:// proxy value is a different feature: it requests TLS on the client-to-proxy hop. Do not change the proxy scheme merely because the destination is HTTPS. First confirm the gateway's documented protocol, then configure the destination key and proxy value independently.

What the Local Capture Actually Observed

We ran a version-pinned localhost harness on 2026-07-27 with Python 3.13.14, Requests 2.34.2, urllib3 2.7.0, PySocks 1.7.1 and certifi 2026.7.22. It bound synthetic HTTP and SOCKS5 listeners to 127.0.0.1, used example.test or localhost as the named destination, and did not contact that destination, Databay's gateway, or a public proxy. Lab credentials were redacted before output.

CaseCaptured evidenceOperational meaning
HTTP destination with encoded Basic credentialsGET http://example.test/basic; Basic username and password length matched the fixtureRequests decoded the percent-encoded user info and sent proxy authorization on the proxy hop.
HTTPS destination through HTTP proxyCONNECT example.test:443 with proxy authorizationThe destination request had not begun; the lab stopped before destination TLS by design.
Explicit mapping plus conflicting HTTP_PROXYThe per-request proxy received the requestAn explicit proxies= argument won for that request.
Session.proxies plus conflicting HTTP_PROXYThe environment proxy received the requestEnvironment discovery can replace a session-level proxy default.
Two GETs in one SessionBoth requests used the same local proxy socketThe Session reused its connection pool; “one request” does not imply “one new proxy connection.”
Read timeoutA silent local proxy produced ReadTimeoutThe second timeout value bounded inactivity while waiting for response bytes, not total job duration.
Retry policyGET received two 503s then 204 in three attempts; POST received one 503The allowlist protected the non-idempotent method from an automatic replay.
SOCKS DNS modessocks5h sent domain localhost; socks5 sent 127.0.0.1The scheme selected whether the client or proxy received the hostname.

This is protocol-shape and client-behavior evidence, not a speed benchmark or availability claim. Rerun the harness after changing Python, Requests, urllib3, PySocks, certifi, or the retry and proxy code.

Use Sessions for Pooling, Not Hidden Routing Policy

A requests.Session persists cookies and default headers and uses urllib3 connection pools. That is useful for repeated calls to the same authorized service, but it also means a rotating gateway may stay on one underlying proxy connection longer than a naive “one request, one exit” model suggests. The localhost capture sent two GETs through the same socket when both ran inside one Session.

import requests

with requests.Session() as session:
    for endpoint in approved_endpoints:
        response = session.get(
            endpoint,
            proxies=proxies,       # explicit on each request
            timeout=(5, 20),
        )
        response.raise_for_status()

Requests' official advanced guide warns that session.proxies values can be overwritten by environment proxies. Our capture reproduced that behavior: with one proxy stored on the Session and a different HTTP_PROXY in the process environment, the environment listener received the request. Passing proxies= on the request selected the explicit listener instead.

For a tightly controlled worker, session.trust_env = False disables environment-derived proxy behavior, but it is broader than a proxy switch: it also affects environment authentication and CA-bundle discovery. If you disable it, set every required proxy, authentication and trust input explicitly and test the deployed container rather than only a laptop shell.

Treat 407 as a Proxy-Hop Result

HTTP 407 means a proxy is challenging proxy authentication. RFC 9110 requires a 407 response to include Proxy-Authenticate; the corresponding request field is Proxy-Authorization. This is different from a destination's 401, 403, or 429. Requests normally derives Basic proxy authorization from the user info in the proxy URL, so percent-encoding and redaction are part of the setup rather than cosmetic details.

Requests returns an HTTP response object for a plain HTTP 407. It becomes an HTTPError only when code calls raise_for_status(). The local harness recorded exactly one request, status 407, and then HTTPError. An HTTPS CONNECT rejection can surface as a ProxyError because the tunnel was never established, so preserve both the exception type and any proxy status in the message.

  1. Confirm which host and port accepted the TCP connection.
  2. Check whether the failure occurred on a plain proxy response or during CONNECT.
  3. Verify that the username and password were encoded as URL user info and were not accidentally assigned to destination auth=.
  4. Check documented pool, country and session flags one at a time.
  5. Run one permitted destination without concurrency or retries.

Do not retry a 407 with new identities or random credentials. Correct the configuration or stop. Never place a complete credential-bearing proxy URL in a ticket, traceback, screenshot, metrics label, or structured log.

Choose SOCKS5 DNS Ownership Deliberately

SOCKS support is an optional Requests extra. Pin it with the rest of the application dependencies, then choose the scheme from the DNS requirement:

# requirements.txt
requests[socks]==2.34.2

# Client resolves the destination name, then sends an IP address.
local_dns = "socks5://user:password@proxy.example:1080"

# Client sends the hostname and asks the SOCKS proxy to handle it.
proxy_dns = "socks5h://user:password@proxy.example:1080"

response = requests.get(
    "https://service.example/health",
    proxies={"http": proxy_dns, "https": proxy_dns},
    timeout=(5, 20),
)

RFC 1928 allows a SOCKS5 CONNECT request to contain an IPv4 address, IPv6 address, or domain name. In the local capture, Requests with socks5h:// used address type 3 and sent localhost; socks5:// resolved locally and used address type 1 with 127.0.0.1. That observation applies to the tested client versions. It does not prove that every lookup made by a larger application follows the same route: other libraries, service discovery, prefetching, telemetry, and direct fallbacks need their own review.

Set Connect and Read Timeouts, Then Add a Job Deadline

Requests does not time out by default. A tuple such as timeout=(5, 20) supplies a connect timeout and a read timeout. The read value is not a wall-clock cap on the entire download; it bounds how long the client waits for bytes on the socket. A peer that keeps sending data can keep a response alive beyond that number.

from requests.exceptions import ConnectTimeout, ReadTimeout

try:
    response = session.get(
        approved_url,
        proxies=proxies,
        timeout=(5, 20),
    )
    response.raise_for_status()
except ConnectTimeout:
    record_failure("proxy-or-destination-connect-timeout")
except ReadTimeout:
    record_failure("response-read-timeout")

Choose numbers from the worker's latency distribution and user-visible time budget. If the entire job has a hard deadline, enforce it in the job runner or task system as well as in Requests; do not mislabel a socket-inactivity timeout as a total deadline. Streaming downloads require their own byte limit, content-type validation, and deadline because every new chunk resets the read-wait calculation.

Make Retries an Explicit HTTP Policy

Requests does not provide a broad automatic retry policy for application responses. Configure urllib3's Retry through an HTTPAdapter, allow only methods whose replay is safe for the specific job, choose statuses deliberately, honor Retry-After, and keep one total attempt budget across every proxy exit.

import requests
from requests.adapters import HTTPAdapter
from urllib3.util import Retry

retry = Retry(
    total=2,
    connect=2,
    read=0,
    status=2,
    allowed_methods={"GET", "HEAD", "OPTIONS"},
    status_forcelist={429, 502, 503, 504},
    backoff_factor=0.5,
    respect_retry_after_header=True,
    raise_on_status=False,
)

with requests.Session() as session:
    session.mount("https://", HTTPAdapter(max_retries=retry))
    response = session.get(
        approved_url,
        proxies=proxies,
        timeout=(5, 20),
    )
    response.raise_for_status()

The example is a starting contract, not a universal list. A 429 may require a pause longer than the job can accept; a source's terms or response may prohibit further calls. Do not include 401, 403, or 407 in a retry status list. Do not add POST merely because a service supports idempotency keys unless the complete server-side contract has been verified.

In the capture, a GET policy with total=2 made three attempts and reached a synthetic 204 after two 503 responses. A POST through the same adapter received one 503 and was not replayed. This is why a method allowlist is a safety boundary, not a performance tweak.

Keep TLS Verification Enabled

Requests verifies destination certificates by default. Keep verify=True. A plain HTTP proxy carrying an HTTPS destination does not require a fake destination certificate: it opens a tunnel, then Requests validates the destination inside that tunnel. If an approved corporate environment intentionally intercepts TLS, use its managed CA bundle and document the trust boundary; do not use verify=False as a proxy fix.

with requests.Session() as session:
    session.verify = "/run/secrets/approved-ca-bundle.pem"
    response = session.get(
        approved_url,
        proxies=proxies,
        timeout=(5, 20),
    )

Requests documents REQUESTS_CA_BUNDLE and a CURL_CA_BUNDLE fallback when environment trust is enabled. If code uses trust_env=False, set the approved bundle directly as shown and keep its update process separate from application releases. Classify SSLError only after identifying whether the failed TLS connection was to an HTTPS proxy or to the destination through a tunnel.

Use an Exception and Status Ladder

Catch the narrow Requests exceptions your code can act on, log a redacted classification, and let an unexpected RequestException reach the job's normal failure path. Do not turn every exception into a retry.

EvidenceLikely layerNext bounded check
ProxyErrorProxy connection, CONNECT rejection, or SOCKS negotiationConfirm gateway host, port, protocol and one redacted proxy status; do not assume a destination block.
ConnectTimeoutConnection phase did not finishCheck DNS, egress firewall and service reachability from the same worker.
ReadTimeoutNo response bytes within the configured intervalPreserve elapsed time and upstream state; retry only if the operation is authorized and replay-safe.
SSLErrorTLS trust, hostname, protocol, or interceptionIdentify the TLS peer and fix the trust path; keep verification enabled.
407 / HTTPErrorProxy authenticationCheck encoded proxy user info and documented routing flags.
401, 403, 429 / HTTPErrorDestination authentication, authorization, or rate controlStop, honor policy and Retry-After, or use an official API, export, feed, or license.
TooManyRedirectsRedirect policy or loopInspect a bounded redirect history and approved host allowlist before following another location.

A proxy can succeed while the destination rejects a request, and a destination can be healthy while the proxy authentication fails. Keep those layers separate in metrics so a rise in 407 does not look like a rise in destination 403.

Add Databay Routing Controls Only After the Base Request Works

First establish one request with the base pool username. Then add one documented control at a time so each test introduces only one variable:

# Base pool
DATABAY_PROXY_USER="USER-zone-residential"

# Request a US exit
DATABAY_PROXY_USER="USER-zone-residential-countryCode-us"

# Request short-lived continuity for one authorized workflow
DATABAY_PROXY_USER="USER-zone-residential-countryCode-us-sessionId-qa-us-01"

A country selector changes one network-location signal; it does not recreate a local account, language, device, cookies, GPS, or platform state. A session identifier requests route continuity; it is not a dedicated identity or a guarantee that a destination will accept the traffic. Choose datacenter proxies for permitted jobs that prioritize infrastructure stability and throughput, or residential proxies when the approved test genuinely requires a consumer-network route or country sample.

Use the browser-only format converter when the problem is encoding one endpoint safely, the proxy checker methodology when the problem is point-in-time liveness evidence, and the HTTP versus SOCKS5 guide when the unresolved question is protocol or DNS ownership. None of those tools establishes permission to access a destination.

Production Preflight

  1. Pin Python, Requests, urllib3, PySocks when used, certifi and the application lockfile; record the tested build date.
  2. Use a controlled destination and one request with no redirects, retries, concurrency, or session flags.
  3. Pass the proxy mapping explicitly, or document every environment variable and NO_PROXY rule the worker inherits.
  4. Percent-encode user info and confirm logs, traces, exception serializers and metrics never emit the credential-bearing URL.
  5. Set connect and read timeouts plus an outer job deadline and maximum response size.
  6. Call raise_for_status() or evaluate expected statuses before parsing a response body.
  7. Classify proxy connection, proxy authentication, tunnel or SOCKS, destination TLS, and destination HTTP separately.
  8. Allow retries only for approved replay-safe operations under a source-level attempt budget.
  9. Stop on unclear permission, authentication boundaries, persistent 403 or 429, CAPTCHA, quota, or account enforcement.
  10. Rerun the localhost harness after a dependency or routing change and investigate changed wire behavior before updating the tested date.

This guide solves the client setup without requiring a purchase. A Databay account is the commercial next step only when the workload is authorized and a managed residential or datacenter route fits the documented requirement.

Troubleshooting desk

Questions specific to Python Requests

How do I set a proxy in Python Requests?
Pass a proxies mapping whose keys match destination schemes and whose values are complete proxy URLs. Use both http and https keys when both destination schemes should use the gateway, and set explicit connect and read timeouts.
Why does the https key use an http proxy URL?
The dictionary key selects requests whose destination URL is HTTPS. The value describes the client-to-proxy protocol. Requests normally uses HTTP CONNECT through an http proxy value, then performs destination TLS inside the tunnel.
Why did HTTP_PROXY override session.proxies?
Requests documents that environment proxy values can overwrite session-level proxy defaults. Pass proxies on the individual request when the application must select the route, or disable trust_env only in a controlled worker that explicitly configures proxy, authentication and CA trust inputs.
Does timeout=(5, 20) limit the whole request to 25 seconds?
No. The values are connect and read-inactivity timeouts, not a total wall-clock deadline. Enforce a separate job deadline and response-size limit when the complete operation must finish within a fixed budget.
What is the difference between socks5 and socks5h in Requests?
With socks5, the client resolves the destination hostname and sends an IP address to the SOCKS proxy. With socks5h, the client sends the hostname in the SOCKS request and asks the proxy to handle resolution.
Should Requests retry 403, 407, or every ProxyError?
No. A 407 requires a proxy-authentication fix, while 403 and many 429 responses are destination decisions or rate controls. Retry only understood transient failures for authorized replay-safe operations, with bounded attempts and Retry-After handling.
Should I set verify=False when a proxy causes an SSL error?
No. Identify whether TLS failed to an HTTPS proxy or to the destination through a tunnel, then fix the trust or hostname configuration. Use an approved CA bundle for intentional managed interception and keep certificate verification enabled.
Adjacent runbooks

Same gateway, different control surface

  • Axios

    HTTP client library

    Axios request -> route selector -> absolute-form or CONNECT -> hop evidence

  • cURL

    Command-line client

    curl -> proxy auth -> CONNECT or SOCKS -> approved destination

  • Node.js fetch

    HTTP client library

    fetch -> env opt-in or same-package dispatcher -> proxy evidence

  • Incogniton

    Multi-profile browser

    Profile editor -> Databay gateway -> pinned residential exit

  • Playwright

    Browser automation

    Browser process -> isolated contexts -> independent exit sessions

  • Puppeteer

    Browser automation

    --proxy-server -> page.authenticate() -> page.goto()

  • Scrapy

    Crawler framework

    Request queue -> downloader middleware -> rotating exit pool

  • Selenium

    Browser automation

    WebDriver -> auth decision -> Databay gateway

Choose the exit pool after the control path works. Use residential for reputation-sensitive targets or datacenter for throughput.

Residential →Datacenter →

Ship Python Requests to production

Create an account, drop in your gateway credentials, and route your first Python Requests request in minutes.

Pricing, order minimums, and traffic validity vary by product.