Proxy error · TLS

SSL certificate errors

With an HTTP proxy and an HTTPS site, your client asks the proxy for a tunnel and then does TLS directly with the site through it. The proxy never sees the certificate. So a certificate error is between you and the site, or it is your client mistaking the proxy for a TLS server.
Whose fault is it?

Yours or the site’s. The proxy does not touch TLS on a tunnelled HTTPS request.

Usually from
Your client config, or the site’s certificate
Try first
Write the proxy URL with http:// and update your CA bundle
With a proxy in the path

What a certificate error means through a proxy

Two kinds of error get lumped together here. Verification errors (CERTIFICATE_VERIFY_FAILED, ERR_CERT_AUTHORITY_INVALID, unable to get local issuer certificate) mean your client did not trust the site’s certificate. Handshake errors such as WRONG_VERSION_NUMBER usually mean your client tried to speak TLS to something speaking plain HTTP.

The classic handshake mistake is a proxy URL written as https://. That tells the client to open TLS to the proxy itself. The gateway expects plain HTTP on its port, and the tunnel it opens to the site is encrypted end to end anyway.

An error on one site only is that site’s certificate: expired, missing an intermediate, or issued for another name. Browsers can fetch a missing intermediate on their own, which is why a site can look fine in Chrome and fail in a script.

The key question

Your credentials, the proxy, or the target?

Yours or the site’s. The proxy does not touch TLS on a tunnelled HTTPS request.

  • Your credentials

    Not the cause. Credentials are checked on the CONNECT, before any TLS.

  • The proxy

    Not directly: it passes TLS through untouched. The one proxy-shaped cause is an https:// proxy URL, and that is a config fix on your side.

  • The target site

    An expired certificate, a missing intermediate or a name mismatch. It fails the same way without the proxy.

How to tell

  • WRONG_VERSION_NUMBER straight away: check the scheme in the proxy URL before anything else.
  • The same error with and without the proxy: it is the site or your CA bundle.
  • openssl s_client against the site, without the proxy, shows its chain and expiry directly.
  • Only one tool on the machine fails: that tool carries its own, older CA bundle.
Cheapest first

Fixes, in the order to try them

  1. Write the proxy URL with http://

    For both the http and https keys, in every tool. The site traffic is still HTTPS inside the tunnel.

    Costs nothing
  2. Update the CA bundle

    pip install -U certifi for Python, a current Node release for Node, and your OS updates or update-ca-certificates for curl.

    Costs nothing
  3. Look at the site’s certificate

    Check its expiry, the names it covers and whether the chain is complete. A broken chain is the site owner’s to fix; you can tell them.

    Costs nothing
  4. Disable verification for one test run only

    -k, verify=False and ignore_https_errors confirm a diagnosis. Left in a script, they remove the only check that the page came from the real site.

    Costs nothing
  5. Point your tool at your network’s CA

    If your machine sits behind a firewall that inspects TLS, your tool needs that firewall’s CA: REQUESTS_CA_BUNDLE, NODE_EXTRA_CA_CERTS or --cacert.

    Costs some time
Per tool

See the real status and headers

Each sample shows the handshake with the site, or the exact certificate error, with verification left on.

curl
terminal
# -v shows the tunnel opening, then the TLS handshake with the site
curl -sv -o /dev/null -x http://USER:[email protected]:8000 https://example.com/ 2>&1 \
  | grep -E 'CONNECT|HTTP/1.1 200|SSL|TLS|subject|expire|issuer'

# the site's certificate, checked without the proxy
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -enddate
Python Requests
tls.py
import certifi
import requests

PROXY = "http://USER:[email protected]:8000"

print("CA bundle:", certifi.where())
try:
    r = requests.get("https://example.com/", proxies={"http": PROXY, "https": PROXY}, timeout=30)
    print(r.status_code)
except requests.exceptions.SSLError as err:
    print(err)

Both keys use http://. An https:// proxy URL is the usual source of WRONG_VERSION_NUMBER.

Scrapy
settings.py + spider
# settings.py: pin one TLS version when a single old site fails the handshake
DOWNLOADER_CLIENT_TLS_METHOD = "TLSv1.2"


# in the spider
def start_requests(self):
    for url in self.start_urls:
        yield scrapy.Request(url, errback=self.on_error)

def on_error(self, failure):
    self.logger.error("%s: %r", failure.request.url, failure.value)

Scrapy does not verify certificates by default, so what reaches the errback here is a handshake failure with the site.

Playwright
tls.py
from playwright.sync_api import Error, sync_playwright

PROXY = {"server": "http://resi.proxymonkey.io:8000", "username": "USER", "password": "PASS"}

with sync_playwright() as p:
    browser = p.chromium.launch(proxy=PROXY)
    page = browser.new_page()
    try:
        page.goto("https://example.com/")
    except Error as err:
        print(err.message.splitlines()[0])
    browser.close()

# net::ERR_CERT_DATE_INVALID          the certificate has expired
# net::ERR_CERT_COMMON_NAME_INVALID   issued for a different name
# net::ERR_CERT_AUTHORITY_INVALID     not signed by a CA Chromium trusts
Node.js
tls.mjs
import { fetch, ProxyAgent } from "undici";

const dispatcher = new ProxyAgent("http://USER:[email protected]:8000");

try {
  await fetch("https://example.com/", { dispatcher });
} catch (err) {
  console.error(err.cause?.code, err.cause?.message);
}

// CERT_HAS_EXPIRED                   the certificate has expired
// ERR_TLS_CERT_ALTNAME_INVALID       issued for a different name
// UNABLE_TO_VERIFY_LEAF_SIGNATURE    the site's chain is incomplete
// behind a TLS-inspecting firewall:  NODE_EXTRA_CA_CERTS=./ca.pem node tls.mjs

The samples use the residential gateway, resi.proxymonkey.io:8000. For an ISP or datacenter IP, use USER:PASS@IP:PORT for the address you rented. Your dashboard lists the host and port for every order, and where it differs from this page, the dashboard is right.

Before you buy anything

Will a different proxy line fix it?

When switching helps

Switching line does not change which certificate your client checks. On an HTTPS site it is always the site’s own.

When it will not

Buying ISP or datacenter to get rid of a certificate error changes the address the site sees. The certificate check still happens on your machine, the same way.

The meter

Is a failed request billed?

We bill for request bytes and response bytes, including headers and protocol overhead on the tunnelled connection. Connections that fail before transferring data are not billed. Retries that you initiate are billed and appear as separate entries in your usage log.

From the metering section of our terms of service.

The terms count protocol overhead on the tunnelled connection as billed bytes, and say connections that fail before transferring data are not billed. A handshake that fails inside an open tunnel has moved some bytes, and the terms do not describe that case on its own. Check the row in your usage log and ask in Discord if it looks wrong.

Residential is billed per GB of that traffic. ISP and datacenter addresses are charged per IP for their term, and where a plan includes a traffic allowance, traffic past it is billed per GB under the same rule. The usage log in your dashboard has one row per request with bytes in, bytes out and cost, so you can look up the failed request yourself.

TLS, asked often

Questions people ask about a certificate error

Can the proxy see my HTTPS traffic?

No. For an HTTPS site the proxy opens a tunnel and passes encrypted bytes through. It sees the host and port you asked for and how many bytes moved, which is what the meter counts. It cannot read the pages or swap the certificate.

Why does the site work in my browser and fail in Python?

Browsers ship their own trust store and can fetch a missing intermediate certificate by themselves. Python uses the certifi bundle and fails where the site’s chain is incomplete or the bundle is old. Update certifi, then check the site’s chain.

Is verify=False safe?

For one test run to confirm the diagnosis, it does no harm. Left in a scraper, it means anyone in the path could hand you a fake page and your code would accept it.

What does WRONG_VERSION_NUMBER mean?

Your client started a TLS handshake and got plain HTTP back. Through a proxy that nearly always means the proxy URL starts with https://. Change it to http://; the site traffic stays encrypted inside the tunnel.

The community layer

Still stuck on a certificate error?

Paste your error in the Discord: the full message plus the command or the few lines that set up the proxy, with the password taken out. Someone there has seen it before.

Join the Discord

4,200+monkeys in the Discord

  • Help from humans

    Post your error, get an answer. Usually in minutes, usually from someone who has hit the same wall.

  • A status bot that tells on us

    Pool health, incidents and maintenance posted automatically. Including the bad days.

  • Deals and free traffic

    Bonus GB drops, early access to new pools, and the occasional giveaway for a good bug report.

Join the Discord4,200+ monkeys, free to lurk