๐ŸŽ‰ Premium Proxies ยท 24-Hour Free TrialClaim Now
Guides

Proxy Not Working on Certain Websites? How to Fix It

Proxy not working on certain websites but fine everywhere else? Tell a block from a port, TLS/SNI or DNS fault with one command each, then apply the right fix.

S SparkProxy 5 19 min read
Share

A proxy not working on certain websites while every other site loads is almost never a dead proxy. If the gateway carries your traffic to ten sites and fails on the eleventh, the connection to the proxy, your credentials and your client config have already been proven by the ten. Something specific to that one target is saying no. This guide gives you one command per suspect (a block, a port restriction, a TLS/SNI fault, a DNS mismatch), shows the exact error text each one produces, and tells you plainly which of them a proxy can fix.

The Error Text, Hop by Hop

Before changing anything, capture the literal error. The wording tells you which hop failed, and different hops have different owners. We reproduced each failure below on 2026-10-30 against a local test proxy, using curl 8.15.0 (Windows, Schannel build), OpenSSL 3.2.4, and Python Requests 2.32.5 with urllib3 2.6.3. Strings from OpenSSL-linked curl builds on Linux word the TLS case differently, but the exit codes match.

What you seeToolHop that failedLikely owner
`curl: (56) CONNECT tunnel failed, response 403`curlProxy refused the tunnelProxy policy (often the port)
`curl: (56) CONNECT tunnel failed, response 502`curlProxy could not reach or resolve the hostDNS or upstream path
`OSError('Tunnel connection failed: 403 Forbidden')`RequestsSame as the curl 403 aboveProxy policy
`s_client: HTTP CONNECT failed, reason=403 Forbidden`opensslSame, seen by OpenSSLProxy policy
`curl: (35) schannel: failed to receive handshake, SSL/TLS connection failed`curlTLS inside the tunnelTarget edge or a middlebox
`SSLEOFError(8, '[SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1081)')`RequestsTLS inside the tunnelTarget edge or a middlebox
`curl: (97) Could not resolve proxy: 127.0.0.1` with a `socks5://` URLcurlYour local DNS lookup of the targetYour machine's resolver
`curl: (97) cannot complete SOCKS5 connection to . (4)`curlSOCKS server could not reach the hostDNS or upstream path
HTTP `403`, `429`, `503` with a vendor pageanyNone. The site answeredThe target

Look at the 97 row twice. In our test the SOCKS proxy at 127.0.0.1 was up and reachable. With socks5://, curl 8.15.0 tried to resolve the target locally, failed, and printed a message naming the proxy. People read that line and start debugging a proxy that is fine.

Why a Proxy Not Working on Certain Websites Means Rejection

An HTTPS request through an HTTP proxy passes four gates in order: TCP to the proxy, a CONNECT host:port request (with your auth), a TLS handshake with the target carried inside the tunnel, and finally the HTTP request and response. A failure is only informative once you know which gate it happened at.

When other sites work through the same proxy, gates one and two are proven for your account. Bad credentials would fail everywhere with a 407. A wrong port on the gateway would fail everywhere with a connect error. So the remaining suspects are all things that differ per destination: the port in the URL, the hostname the proxy has to resolve, the TLS edge that sits in front of the site, and the site's own decision about your request.

That last one is by far the most common. Sites that run bot management judge the exit IP, its ASN, its country, your request rate and your client fingerprint. A news site with no protection serves anyone; a ticketing or retail site behind an anti-bot vendor refuses the same request. That isn't a proxy outage. It's a target with a different policy.

If you haven't confirmed the proxy works at all, do that first with the basic checks in how to test if your proxy is working. Everything below assumes it passes for other sites.

Free trial

Scraping at scale? Skip the blocks.

Fast, unblockable datacentre proxies with unlimited bandwidth.

Four Commands, Run in This Order

Run these in order. Each takes seconds and either clears a suspect or names it.

OrderSuspectCommand (one each)Clears it when
1Block by the site`curl -w "%{http_connect} %{http_code}"`You get a CONNECT 200 and a normal page
2Port restriction`curl -v` and read the CONNECT replyThe proxy answers the CONNECT with 200
3TLS or SNI fault`openssl s_client -proxy ... -servername`The handshake completes and verification is OK
4DNS mismatchswap `socks5://` for `socks5h://`Results are identical with both schemes

The order isn't arbitrary. The block test reads two numbers that already split the problem in half: if the proxy's CONNECT reply is 200 and the site's status is a 403, you're done diagnosing and can skip to the fix.

Check 1: Did the Site Answer? (Block Test)

curl's --write-out exposes both the proxy's answer to CONNECT and the target's HTTP status in one line.

curl -sS -o page.html \
  -x "http://USER:PASS@gateway.sparkproxy.io:11000" \
  -w "connect=%{http_connect} status=%{http_code} ip=%{remote_ip}\n" \
  https://shop.sparkproxy.io/product/123

Read the result like this:

  • connect=200 status=200: the request worked. If your scraper still "fails", the problem is in parsing or in a JavaScript-rendered page your HTTP client never executes.
  • connect=200 status=403 (or 429, 503): the proxy did its job and the site refused you. This is a block.
  • connect=403 or connect=502 with status=000: the proxy itself refused or failed the tunnel, so the site never saw you. Go to check 2 or 4.
  • A curl error with no numbers: the tunnel opened and something broke afterwards, usually TLS. Go to check 3.

Once it's a block, open page.html. The vendor's page, cookie names and headers identify who blocked you, and the full method for reading them is in how to identify which anti-bot a site uses. You need that name before choosing a fix, because an IP-reputation block and a JavaScript challenge need different things.

Check 2: Did the Proxy Refuse the Port?

Most proxies only open tunnels to a short list of destination ports. Squid's documented default rules include http_access deny CONNECT !SSL_ports, under the comment "Deny CONNECT to other than secure SSL ports" (squid-cache.org, http_access directive, read 2026-10-30). Commercial gateways apply their own lists for the same reason: an open CONNECT to port 25 is a spam relay.

So if the one failing site lives on :8443, :2083, :9443 or any non-standard port, look at the CONNECT exchange:

curl -v -o /dev/null \
  -x "http://USER:PASS@gateway.sparkproxy.io:11000" \
  https://panel.sparkproxy.io:8443/ 2>&1 | grep -E "^[<>] |CONNECT"

In our reproduction the refusal looked like this:

> CONNECT panel.sparkproxy.io:8443 HTTP/1.1
> Host: panel.sparkproxy.io:8443
< HTTP/1.1 403 Forbidden
* CONNECT tunnel failed, response 403

The giveaway is that the same host on port 443 works. A 403 on the CONNECT line comes from the proxy, never from the website, because the website hasn't been contacted yet. Changing IPs won't help, rotation won't help, and nothing in your code will help. Ask the provider which ports are permitted (we don't publish SparkProxy's port allowlist here because we couldn't verify it on a public page today). For how ports map to protocols in general, see proxy ports explained.

Check 3: Did the TLS Handshake Die? (SNI Test)

If CONNECT returns 200 and the connection then dies, the failure is in the TLS handshake inside the tunnel. OpenSSL can open the tunnel and run the handshake for you, so you see exactly where it stops:

openssl s_client -brief \
  -proxy gateway.sparkproxy.io:11000 \
  -proxy_user USER -proxy_pass pass:PASS \
  -connect shop.sparkproxy.io:443 \
  -servername shop.sparkproxy.io </dev/null

The -proxy_user and -proxy_pass flags are documented in the OpenSSL 3.0 s_client manual and present in our 3.2.4 build; older builds may lack them, in which case use IP whitelisting for this test. A healthy result prints CONNECTION ESTABLISHED, the protocol (we got TLSv1.3), the peer certificate and Verification: OK.

Three failure shapes matter:

  1. EOF right after the ClientHello. We got error:0A000126:SSL routines::unexpected eof while reading when our test proxy dropped the tunnel after the first TLS bytes. Something between you and the origin read the SNI and hung up. That can be the site's edge refusing that exit range at the TLS layer, or a filter on your own network that inspects SNI. Run the same command without -proxy: if it succeeds direct and fails through the proxy, the edge dislikes the exit; if it fails both ways, it's your network.
  2. A handshake alert. Dropping SNI entirely with -noservername against a Cloudflare-fronted site gave us ssl/tls alert handshake failure ... SSL alert number 40. Edges that host thousands of names need SNI to pick a certificate. If your client is sending an IP address, an empty name, or a name the edge doesn't host, you'll see this. The cure is in your code: connect by hostname, not by a hard-coded IP.
  3. Verification failure (unable to get local issuer certificate, curl exit 60). Someone presented a certificate your trust store doesn't accept. Through a plain CONNECT proxy, the certificate comes from the target, so this usually points to inspection by antivirus or a corporate gateway on your side, not the proxy. Fix the trust chain; never ship verify=False.

A fourth TLS cause doesn't show up as an error at all: TLS fingerprinting. The handshake completes and the site then serves a 403 or a challenge because your client's ClientHello doesn't look like a browser. That lands in check 1 as a block, and no proxy changes it, since the fingerprint is produced by your client library. Background is in what is TLS fingerprinting.

Check 4: Who Resolved the Hostname? (DNS Test)

With an HTTP proxy, your client puts the hostname in the CONNECT line and the proxy resolves it. With SOCKS5 it depends on the URL scheme. Requests' documentation says socks5 "causes the DNS resolution to happen on the client, rather than on the proxy server" and that socks5h resolves on the proxy; curl uses the same convention.

We confirmed it on the wire with a logging SOCKS server: socks5:// delivered ATYP 1 104.21.76.82 (an IPv4 address your machine looked up), while socks5h:// delivered ATYP 3 www.sparkproxy.io (the name). The one command for this check is a scheme swap:

curl -sS -o /dev/null -w "%{http_code}\n" -x "socks5://USER:PASS@gateway.sparkproxy.io:13000"  https://shop.sparkproxy.io/
curl -sS -o /dev/null -w "%{http_code}\n" -x "socks5h://USER:PASS@gateway.sparkproxy.io:13000" https://shop.sparkproxy.io/

If socks5h works and socks5 fails, your local resolver is the problem. Three things cause it for a single site:

  • Local filtering. A corporate DNS, Pi-hole or hosts-file entry blocks or redirects that one domain. In Python the failure surfaces before the proxy is involved; on Windows we saw [Errno 11001] getaddrinfo failed inside a NewConnectionError.
  • Geo-steered answers. CDNs often answer DNS with an edge near the resolver. With local resolution you send the proxy an IP chosen for your location, then exit from another country. Some sites tolerate that, some don't, and some hand back the wrong regional content.
  • Leaks. Local lookups for a target also reveal it to your ISP's resolver, which is the reason many people chose a proxy in the first place.

The fix is one character: use socks5h://. With the HTTP port, a resolution failure on the proxy side shows up as the CONNECT tunnel failed, response 502 line from the table, and in that case check the hostname for typos and whether the domain resolves publicly at all. More background: how proxies handle DNS resolution.

Who Owns Each Cause: You, the Provider, or the Site

CauseHow it shows upCan a proxy fix it?What does fix it
Exit IP or ASN reputation403 or challenge, CONNECT 200YesA different exit, or a pool the site trusts
Geo restriction403, 451, or a redirect to a regional pageYesAn exit in the right country
Per-IP rate limit429, often after N requestsYes, partlyRotation plus slower pacing
Port not allowedCONNECT 403No (provider policy)Ask the provider; use a permitted port
Local DNS filter or geo mismatchgetaddrinfo error, curl 97Yes, by config`socks5h://` or the HTTP port
Missing or wrong SNITLS alert 40NoConnect by hostname in your client
TLS interception on your networkExit 60, unknown issuerNoFix trust chain or bypass the inspector
TLS or HTTP/2 fingerprint403 after a clean handshakeNoA browser-like client, or a rendering API
JavaScript challenge200 or 403 with a challenge pageNoReal browser rendering
Site down or geo-fenced to a network you can't reachTimeouts from every exitNoNothing on your side

The honest summary: a proxy changes where you come from. It doesn't change what your client looks like, which ports your provider permits, or whether your own network tampers with TLS.

Fixing a Real Block: Rotation, Sessions, Rendering

If check 1 showed CONNECT 200 plus a refusal, start with the cheapest lever: a different exit. SparkProxy's gateway offers Random Rotation and Sticky Sessions (a 5-minute session). Use Random Rotation for independent page fetches. Use Sticky Sessions when the site ties state to an IP, such as a login, a cart or a paginated search; rotating mid-flow is a classic reason a site "works for one page then fails".

A pitfall in Requests: its docs warn that session.proxies can be overwritten by environment proxy variables, so pass proxies= on each call. Open a fresh connection per retry, otherwise keep-alive reuses the tunnel and the exit IP:

import os
import time

import requests

GATEWAY = "http://{}:{}@gateway.sparkproxy.io:11000".format(
    os.environ["SP_USER"], os.environ["SP_PASS"]
)
PROXIES = {"http": GATEWAY, "https": GATEWAY}
BLOCK_CODES = {403, 429, 503}


def get_with_new_exit(url, attempts=4):
    last = None
    for attempt in range(attempts):
        # A new Session means a new CONNECT, so Random Rotation can hand out a new exit.
        with requests.Session() as s:
            try:
                r = s.get(url, proxies=PROXIES, timeout=(10, 30))
            except requests.exceptions.ProxyError:
                raise  # CONNECT refused: a port or DNS problem, retrying burns time
            except requests.exceptions.SSLError:
                raise  # TLS fault: see check 3, a new IP rarely helps
        if r.status_code not in BLOCK_CODES:
            return r
        last = r
        time.sleep(2 ** attempt)
    return last


resp = get_with_new_exit("https://shop.sparkproxy.io/product/123")
print(resp.status_code, len(resp.text))

If four fresh exits all get refused, the block is probably not about one IP. It's about the exit type, the country, or your fingerprint, and the anti-bot identification step tells you which. At that point a rendering API is usually cheaper than building a browser stack. The SparkProxy Scraping API (base https://scrape.sparkproxy.io/api/v1, header X-API-Key, 1,000 free credits) fetches the page for you. Per the Scraping API docs read 2026-10-30: render_js defaults to true (5 credits), render_js=false is a plain fetch at 1 credit, country_code adds 5, and proxy_type=premium is listed at 10 credits without JS or 25 with it.

Escalate only when the cheap call fails. json_response=true returns the envelope (status_code, credits_used, body), and body is base64, so decode it before looking for block markers:

import base64
import os

import requests

API = "https://scrape.sparkproxy.io/api/v1"
HEADERS = {"X-API-Key": os.environ["SPARKPROXY_API_KEY"]}
# Replace with strings from the target's own block page.
MARKERS = ("access denied", "captcha", "verify you are human")


def scrape(url, **extra):
    params = {"url": url, "render_js": "false", "json_response": "true"}
    params.update(extra)
    r = requests.get(API, params=params, headers=HEADERS, timeout=120)
    r.raise_for_status()
    env = r.json()
    html = base64.b64decode(env.get("body") or "").decode("utf-8", "replace")
    return env.get("status_code"), env.get("credits_used"), html


def blocked(status, html):
    if status in (401, 403, 429, 503):
        return True
    low = html.lower()
    return any(m in low for m in MARKERS)


target = "https://shop.sparkproxy.io/product/123"
status, cost, html = scrape(target)
if blocked(status, html):
    status, cost, html = scrape(
        target, render_js="true", proxy_type="premium", country_code="us"
    )
print(status, cost, len(html), "blocked" if blocked(status, html) else "ok")

Don't use format=json for this. format only converts page content (html, md, mdx, json, screenshot, pdf); it's not the envelope, and code that reads status_code from it gets nothing. When we tried a live call on 2026-10-30 the API edge returned Cloudflare error code: 521, so the envelope fields above come from the docs, not from a response we captured.

On the gateway side, plans start from $75/month (USD, sparkproxy.io, read 2026-10-30) with unlimited bandwidth, so retries through the proxy cost concurrency rather than traffic. API escalations cost credits, which is why the code tries the 1-credit fetch first.

A Python Classifier That Labels the Failure

When a job touches hundreds of domains, you want each failure labelled automatically rather than read by hand. This maps the exception types and strings we measured to the four suspects. Order matters: ProxyError and SSLError are both subclasses of ConnectionError in Requests, so they must be caught first.

import sys

import requests
from requests.exceptions import (
    ConnectionError,
    ConnectTimeout,
    ProxyError,
    ReadTimeout,
    SSLError,
)


def classify(url, proxy):
    proxies = {"http": proxy, "https": proxy}
    try:
        r = requests.get(url, proxies=proxies, timeout=(10, 30), allow_redirects=False)
    except ProxyError as e:
        msg = str(e)
        if "Tunnel connection failed: 407" in msg:
            return "AUTH: proxy rejected credentials (would fail on every site)"
        if "Tunnel connection failed: 403" in msg:
            return "PORT/POLICY: proxy refused the CONNECT, check the port"
        if "Tunnel connection failed: 50" in msg:
            return "DNS/UPSTREAM: proxy could not resolve or reach the host"
        return "PROXY: " + msg[:120]
    except SSLError as e:
        return "TLS: handshake failed inside the tunnel (" + type(e).__name__ + ")"
    except ConnectTimeout:
        return "TIMEOUT: connecting took too long"
    except ReadTimeout:
        return "SLOW: tunnel open, site never answered (possible tarpit)"
    except ConnectionError as e:
        if "getaddrinfo" in str(e) or "NameResolution" in str(e):
            return "DNS/LOCAL: your resolver failed, use socks5h:// or the HTTP port"
        return "CONNECTION: " + str(e)[:120]
    if r.status_code in (401, 403, 429, 451, 503):
        return "BLOCK: site answered {}".format(r.status_code)
    return "OK: {}".format(r.status_code)


if __name__ == "__main__":
    proxy_url = sys.argv[1]
    for target in sys.argv[2:]:
        print(target, "->", classify(target, proxy_url))

Run it with the proxy URL first and any number of target URLs after it. Mixed results across domains are normal; a single label repeated across every domain points at your setup instead.

When the Problem Belongs to Your Provider

Two outcomes from the checks above are the provider's to solve: a CONNECT 403 on a port you need, and a CONNECT 502 for a hostname that resolves fine publicly. A third is shared: if fresh exits from the whole pool are refused by one site while a different network reaches it with the identical client, that's evidence about the pool's reputation on that target.

Send the raw outputs, not a summary. The curl -w line, the -v CONNECT exchange and the s_client result answer most of a support engineer's first questions. How to package that into a ticket that gets actioned quickly is covered in how to escalate proxy blocks with your provider, and the meaning of each status code a gateway can return is in proxy error codes explained.

If you want to run all four checks against your own target with a clean pool, SparkProxy's 24-hour free trial needs no credit card.

Frequently asked questions

FAQ

Because the parts every site shares, the connection to the proxy and your credentials, are already proven by the sites that load. The failing site differs in its port, its hostname resolution, its TLS edge, or its decision to block your request, and one command per suspect tells you which.

The proxy refused to open a tunnel to that host and port, so the website was never contacted. It most often means the destination port isn't on the proxy's allowlist, so compare the same host on port 443 before blaming the site.

Run curl with -w "%{http_connect} %{http_code}". A CONNECT value of 200 with a site status of 403, 429 or 503 means the proxy delivered your request and the website refused it, which is a block rather than a proxy fault.

Use socks5h. With plain socks5 your machine resolves the hostname, so local DNS filters, geo-steered CDN answers and DNS leaks all apply, while socks5h lets the proxy resolve the name from its own location.

Sometimes. If the handshake works direct but fails through one exit, a different exit can help; but missing SNI, TLS interception on your own network, and TLS fingerprint blocks come from your side, and no proxy changes them.

When several fresh exits in a row are refused by the same site, the block is about fingerprint, exit type or a JavaScript challenge rather than one IP. A rendering API with country targeting handles those, and it's worth paying the higher credit cost only for that target.

Special Discount ยท 20% off

Get 20% off your first month

Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.

Save up to 15% more on quarterly, half-yearly and yearly plans

Claim Discount

About the Author

The SparkProxy Technical Team builds and runs SparkProxy's proxy gateway and Scraping API, and writes these guides from the support questions customers send about single-site failures. The error strings in this guide were reproduced by the team on 2026-10-30 rather than copied from other write-ups, and every product figure links to SparkProxy's own pages.

Keep reading

Related articles