Back to Blog

Python Requests GET Timeout With a Proxy: Diagnosis and Fixes

Chloe Sun

Aug 21, 2026 · Troubleshooting · 10 min read

Quick answer

When a requests.get() call times out through a proxy, do not start by increasing the timeout. Run the same authorized GET request once directly and once through the proxy, with the same URL, headers, and (connect, read) timeout. Then classify the outcome:

  • Direct succeeds and proxy raises ConnectTimeout: check the proxy scheme, host, port, authentication, allowlist, local firewall, and route availability.
  • Direct succeeds and proxy raises ReadTimeout: the connection was established, but the target response stopped arriving within the read window. Compare exit location, target behavior, payload size, and endpoint load.
  • Proxy returns 407, 403, or 429: you received an HTTP response. Fix authentication, authorization, or request rate instead of treating it as a timeout.
  • Both routes time out: investigate the target, DNS, local network, or timeout budget before blaming the proxy.

Need the baseline timeout behavior first? The existing Python Requests timeout guide covers scalar and connect/read values, exception classes, Session defaults, POST considerations, and bounded retry syntax. This tutorial begins where that guide ends: diagnosing a requests GET timeout that appears on a proxy route.

Use proxies only for accounts, systems, and public resources you are authorized to access. A different exit IP does not override a website’s terms, rate limits, authentication, or access controls.

What a requests GET timeout tells you in a proxy workflow

A proxied GET request has more stages than a direct request:

  1. Resolve and reach the proxy endpoint.
  2. Authenticate to the proxy when required.
  3. Ask the proxy to connect to the destination.
  4. Establish TLS with the destination for HTTPS traffic.
  5. Wait for response bytes and receive the body.

The word “timeout” identifies a waiting phase, not the responsible component. A ConnectTimeout can involve the path to the proxy or the proxy’s attempt to open the destination connection. A ReadTimeout means a connection existed but response data did not arrive within the configured read interval. A ProxyError, SSLError, or completed HTTP response points to a different class of failure.

That distinction matters because raising every value from 10 seconds to 120 seconds can hide a broken route while tying up workers for longer. Diagnose the stage first; change the budget only when measurements show that a healthy operation legitimately needs more time.

02-proxy-timeout-failure-stages

Step 1: Run a controlled direct-versus-proxy test

Use the same process, target, request headers, and timeout tuple for both routes. Do not compare a command-line request on one network with a Python request on another machine; that adds variables before the test begins.

The following diagnostic script reads the proxy URL from an environment variable so credentials are not placed in source code. In production, prefer your organization’s secret manager. Do not print the proxy URL or exception text if it could expose credentials.

import os
from time import perf_counter
from urllib.parse import urlsplit

import requests


TARGET_URL = "https://example.com/"
PROXY_URL = os.environ["PROXY_URL"]
TIMEOUT = (5, 20)
HEADERS = {"User-Agent": "authorized-timeout-check/1.0"}


def probe(route_name, proxies):
    started = perf_counter()

    # Disable ambient HTTP_PROXY/HTTPS_PROXY values for a controlled A/B test.
    with requests.Session() as session:
        session.trust_env = False

        try:
            response = session.get(
                TARGET_URL,
                headers=HEADERS,
                proxies=proxies,
                timeout=TIMEOUT,
            )
            response.raise_for_status()
            result = f"http_{response.status_code}"
        except requests.exceptions.ConnectTimeout:
            result = "connect_timeout"
        except requests.exceptions.ReadTimeout:
            result = "read_timeout"
        except requests.exceptions.ProxyError:
            result = "proxy_error"
        except requests.exceptions.SSLError:
            result = "tls_error"
        except requests.exceptions.HTTPError as error:
            result = f"http_{error.response.status_code}"
        except requests.exceptions.RequestException:
            result = "other_request_error"

    elapsed = perf_counter() - started
    return {
        "route": route_name,
        "host": urlsplit(TARGET_URL).netloc,
        "result": result,
        "seconds": round(elapsed, 3),
    }


proxy_map = {
    "http": PROXY_URL,
    "https": PROXY_URL,
}

print(probe("direct", proxies=None))
print(probe("proxy", proxies=proxy_map))

Replace https://example.com/ with an endpoint you are permitted to test. Run several low-frequency samples instead of drawing a conclusion from one request. perf_counter() is appropriate for elapsed-duration measurements; only the difference between the start and end readings matters.

For this diagnostic, session.trust_env = False prevents system proxy variables from silently routing the supposed direct request through another proxy. It also ignores other environment-derived settings, including certificate bundle variables, so use it only when that behavior is understood. In an enterprise TLS environment, document any required CA bundle explicitly rather than disabling certificate verification.

Step 2: Interpret the comparison before changing code

Record the exception class, HTTP status when available, elapsed time, target host, route label, and proxy region. Never log the proxy password or a complete credential-bearing URL.

Direct result Proxy result Most useful next check
Success ConnectTimeout Proxy scheme, host, port, credentials, source-IP allowlist, firewall, and endpoint availability
Success ReadTimeout Exit-to-target latency, target processing, response size, proxy load, and read budget
Success ProxyError Credential format, unsupported protocol, closed endpoint, or failed proxy handshake
Success 407 Proxy authentication; do not retry unchanged credentials
Success 403 or 429 Target authorization, policy, and rate limits; this is not a timeout
ConnectTimeout ConnectTimeout Local network, DNS, destination reachability, or an unrealistically short connect value
ReadTimeout ReadTimeout Target performance, response delivery, or an unrealistically short read value
Success Success, but proxy is consistently slower Exit distance, route quality, target region, and whether the selected proxy type fits the task

Keep the target request identical during the comparison. Changing the User-Agent, cookies, payload, redirect behavior, and proxy at the same time makes the result impossible to attribute.

Step 3: Fix connection-stage proxy failures

Confirm the proxy URL scheme and port

Requests requires a scheme in each proxy URL. The proxy scheme describes how the client connects to the proxy; it is not automatically the same as the destination scheme. An HTTPS target can still use an HTTP proxy URL when that endpoint supports HTTPS tunneling.

proxies = {
    "http": "http://user:password@proxy.example:2000",
    "https": "http://user:password@proxy.example:2000",
}

Copy the provider-generated host, port, username, password, and protocol instead of rebuilding them from memory. Rola IP’s proxy quick start shows where those values are generated and how location or session parameters are represented.

If the username or password contains reserved URL characters such as @, :, /, or #, use the exact provider-supported format or percent-encode the credential component. A malformed credential URL can look like a network failure because the parser sends the connection to the wrong host or supplies the wrong authentication value.

Check authentication and source-IP allowlisting

A credential-based endpoint and an IP-allowlisted endpoint fail differently. With allowlisting, the public source IP of the machine running the script must match the provider record. Office failover, VPN changes, cloud redeployment, and dynamic residential internet can change that source IP without changing your Python code.

An explicit 407 Proxy Authentication Required is not a timeout. Stop retrying and correct the username, password, account state, or allowlist. Repeatedly sending the same invalid credentials only adds noise and may trigger security controls.

Eliminate hidden environment proxy settings

Requests recognizes standard variables such as HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and NO_PROXY. The official documentation also warns that environment proxy settings can override values placed only in session.proxies.

For a controlled test, either pass proxies= on the individual request or set session.trust_env = False. Do not permanently disable the environment without checking whether the application depends on a corporate proxy or custom certificate bundle.

Choose socks5 or socks5h deliberately

SOCKS support is an optional Requests feature. Install its extra dependencies with the same Python interpreter or virtual environment that runs the application:

python -m pip install "requests[socks]"

Also declare requests[socks] in the project’s dependency file so CI and production install the same extra. Use it only when the proxy provider supplies a SOCKS endpoint and you configure a socks5:// or socks5h:// proxy URL. Do not select SOCKS merely because the destination URL is HTTPS: the destination scheme and the proxy protocol are separate, and an HTTPS destination can still use an http:// proxy endpoint when that endpoint supports HTTPS tunneling.

With socks5://, DNS resolution occurs on the client. With socks5h://, the proxy resolves the destination hostname. If local DNS cannot resolve the target, or if the test needs name resolution from the proxy’s region, socks5h may fix that specific failure.

It will not fix invalid credentials, a closed port, an unavailable exit, or a slow target. Do not change an HTTP proxy URL to socks5:// unless the provider documents that host and port as a SOCKS endpoint.

03-socks5-vs-socks5h-dns-resolution

Step 4: Fix read-stage failures

A ReadTimeout means the connection stage completed. Focus on what happens after the request reaches the route.

Compare a nearby exit with a distant exit

Long geographic paths add latency and variability. Test a country-level exit near the destination before narrowing to a city. If the nearby route is stable and the distant route repeatedly times out, choose the region that meets the business requirement with the lowest measured latency rather than globally raising the read value.

When a workflow needs location diversity, rotating residential proxies can provide regional exits. Rotation is not automatically a timeout fix: stateful requests need a sticky session long enough to preserve continuity, while independent public GET requests can rotate more freely.

Separate target delay from proxy delay

Compare at least three low-frequency samples per route and keep the request identical. If both routes slow down at the same time, inspect the target service, payload, and rate limits. If only one proxy endpoint degrades, test another endpoint in the same region before changing the application timeout.

Use a provider health check or a proxy checker to verify that the endpoint is reachable and that the exit matches the intended region. A successful checker result proves basic connectivity; it does not prove that every target will respond at the same speed or grant access.

Close streamed responses

With stream=True, Requests does not release the connection to the Session pool until the body is consumed or the response is closed. Repeatedly leaving responses open can exhaust reusable connections and make later requests appear unstable.

with session.get(
    url,
    proxies=proxies,
    timeout=(5, 30),
    stream=True,
) as response:
    response.raise_for_status()
    for chunk in response.iter_content(chunk_size=64 * 1024):
        if not chunk:
            continue
        process(chunk)

The context manager closes the response even when processing fails. Keep a separate application limit for total bytes or total elapsed time when large downloads must stay within a strict resource budget.

Step 5: Build a production-friendly proxy GET wrapper

Once the route is understood, centralize the proxy GET behavior so every caller records the same safe fields and handles failure classes consistently.

from dataclasses import dataclass
from time import perf_counter
from urllib.parse import urlsplit

import requests


@dataclass(frozen=True)
class GetOutcome:
    ok: bool
    host: str
    status: int | None
    error_type: str | None
    elapsed_seconds: float


def get_through_proxy(session, url, proxy_url, timeout=(5, 25)):
    proxies = {"http": proxy_url, "https": proxy_url}
    started = perf_counter()

    try:
        response = session.get(
            url,
            proxies=proxies,
            timeout=timeout,
        )
        response.raise_for_status()
        return response, GetOutcome(
            ok=True,
            host=urlsplit(url).netloc,
            status=response.status_code,
            error_type=None,
            elapsed_seconds=round(perf_counter() - started, 3),
        )
    except requests.exceptions.ConnectTimeout:
        error_type = "connect_timeout"
    except requests.exceptions.ReadTimeout:
        error_type = "read_timeout"
    except requests.exceptions.ProxyError:
        error_type = "proxy_error"
    except requests.exceptions.SSLError:
        error_type = "tls_error"
    except requests.exceptions.HTTPError as error:
        return None, GetOutcome(
            ok=False,
            host=urlsplit(url).netloc,
            status=error.response.status_code,
            error_type="http_error",
            elapsed_seconds=round(perf_counter() - started, 3),
        )
    except requests.exceptions.RequestException:
        error_type = "request_error"

    return None, GetOutcome(
        ok=False,
        host=urlsplit(url).netloc,
        status=None,
        error_type=error_type,
        elapsed_seconds=round(perf_counter() - started, 3),
    )

The wrapper intentionally does not log the full URL, proxy URL, headers, cookies, or response body. Adjust that policy to your security requirements. If query strings can contain secrets, log only the hostname and a separately assigned operation ID.

The returned Response has a downloaded body under the default stream=False behavior. Call response.close() when finished, or manage repeated requests inside a Session context:

with requests.Session() as session:
    response, outcome = get_through_proxy(
        session,
        "https://example.com/",
        proxy_url,
    )
    try:
        print(outcome)
        if response is not None:
            print(f"status={response.status_code}")
    finally:
        if response is not None:
            response.close()

For failure outcomes, response is None. For a successful request, the finally block closes the response after use even if later result handling raises an exception.

Step 6: Decide whether to retry, rotate, or quarantine

Retry policy should follow the failure class instead of treating every failed request the same way.

Failure Default action Reason
First transient ConnectTimeout on a normally healthy route Retry once after a short backoff if the GET is safe and the operation budget allows it A short network interruption may clear
Repeated ConnectTimeout on one endpoint Quarantine that endpoint and verify its health More waiting does not repair a dead or unreachable route
Occasional ReadTimeout Compare target and route timing; retry only within a bounded budget The target or response path may be temporarily slow
407 Fix authentication; do not retry unchanged values The proxy rejected the credentials or source identity
429 Honor Retry-After when supplied and reduce request rate Rotation should not be used to evade a target’s rate limit
403 Check authorization, target policy, and request validity A different IP does not create permission
TLS error Correct trust configuration or endpoint interception verify=False hides certificate validation rather than solving the cause
One exit repeatedly fails while peers succeed Remove or quarantine that route and report it to the provider The evidence points to endpoint-specific health

urllib3’s Retry supports method restrictions, status-code lists, exponential backoff, jitter, and Retry-After. Keep the allowed methods limited to operations that are safe to repeat, set a small total, and fit all attempts inside the application’s larger deadline. Do not create an unlimited while True loop around requests.get().

04-retry-rotate-quarantine-flow

Problems that increasing the timeout will not fix

Increasing a timeout is appropriate only when a healthy operation legitimately needs more waiting time. It will not repair:

  • an incorrect proxy scheme, hostname, or port;
  • invalid credentials or a stale IP allowlist;
  • a 407 Proxy Authentication Required response;
  • a 403 Forbidden or 429 Too Many Requests response;
  • a TLS certificate or interception problem;
  • local DNS failure when the selected SOCKS mode resolves locally;
  • an environment variable that silently selects a different proxy;
  • an unclosed streamed response that prevents efficient connection reuse;
  • a target that blocks, throttles, or does not authorize the request;
  • a strict end-to-end deadline that Requests’ connect/read tuple was never designed to enforce.

If increasing the value appears to help only by making workers wait longer before failing, it is not a fix. Return to the direct-versus-proxy comparison and identify the failing stage.

Requests GET timeout troubleshooting checklist

  1. Reproduce the failure with one authorized URL and one proxy endpoint.
  2. Run the same request directly with identical headers and timeout values.
  3. Record the exception class, status code, elapsed time, destination host, and route label.
  4. Confirm the proxy scheme, host, port, username, password, and account state.
  5. Check whether the calling machine’s public IP matches the allowlist.
  6. Inspect HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and NO_PROXY behavior.
  7. For SOCKS, decide whether DNS should resolve locally (socks5) or through the proxy (socks5h).
  8. Test a second endpoint in the same region, then a nearby region.
  9. Close or fully consume every streamed response.
  10. Retry only safe GET operations, keep attempts bounded, and respect target rate limits.
  11. Quarantine repeatedly failing endpoints instead of increasing every timeout globally.
  12. Review the timeout values again when the target, payload, region, proxy type, or concurrency changes.

Conclusion

The reliable response to a requests GET timeout is classification, not guesswork. Compare direct and proxied calls, preserve all other request variables, and record whether the failure occurs during connection, reading, proxy negotiation, TLS, or after an HTTP response.

Fix configuration and authentication errors directly. Measure healthy regional latency before changing the read budget. Retry safe GET requests only within a bounded operation budget, and quarantine endpoints that repeatedly fail while comparable routes succeed. That workflow makes timeout handling faster to debug and safer to operate without duplicating a general Requests timeout tutorial.

Frequently asked questions