Back to all posts
Debugging Proxy TLS Failures: SNI, ALPN, and Certificates

Debugging Proxy TLS Failures: SNI, ALPN, and Certificates

September 14, 2026

Locate the failure layer

A failed request through a proxy can look like a generic timeout, a 502 response, or a browser certificate warning. Those symptoms often come from different layers. Before changing rotation settings or headers, identify where the request stops.

Check the layers in this order:

  1. TCP connection: Can the client reach the proxy and its port?
  2. Proxy tunneling: Did the proxy accept the CONNECT request for an HTTPS URL?
  3. TLS handshake: Did the client and server agree on a protocol, version, and cipher?
  4. Certificate validation: Is the presented certificate trusted and intended for the destination?
  5. ALPN and application protocol: Did the connection select h2, http/1.1, or another negotiated protocol?
  6. HTTP response: Is the problem now an origin response, authentication issue, or rate limit?

This distinction matters. A certificate error is not an IP ban, and a 502 caused by a failed TLS termination is not necessarily an origin server outage.

Establish direct and proxy baselines

Test the same URL directly and through the proxy. Replace the proxy address with the endpoint supplied by your provider.

curl -v --max-time 20 'https://example.com/'
curl -v --proxy 'http://127.0.0.1:8080' --max-time 20 'https://example.com/'

Look for these signals:

  • Trying... Connected confirms that the client reached the proxy.
  • Connected to identifies the proxy host that handled the request.
  • CONNECT example.com:443 shows that the HTTP proxy opened a tunnel to the destination.
  • SSL connection using reveals the negotiated TLS version and cipher.
  • ALPN protocol shows the selected application protocol.
  • subject: and issuer: identify the certificate presented to the client.

If the direct request succeeds but the proxied request fails, focus on the proxy path, its TLS policy, and its certificate trust chain. If both fail, investigate the destination, local DNS, firewall rules, or the client environment before rotating proxies.

Do not use curl --insecure as a permanent fix. It disables certificate validation and can expose credentials, cookies, and request data to an attacker.

Verify CONNECT, SNI, and ALPN

For an HTTPS request through an HTTP proxy, the sequence is:

  1. Open a TCP connection to the proxy.
  2. Send CONNECT destination:443.
  3. Receive a successful tunnel response.
  4. Start TLS inside that tunnel.
  5. Send the TLS Server Name Indication, or SNI, value.
  6. Negotiate ALPN and HTTP/2 or HTTP/1.1.

SNI is not the same thing as the HTTP Host header. It is sent during the TLS handshake and helps the server select the correct certificate when one IP hosts multiple domains. Most clients set it automatically from the URL. Do not manually add Host, X-Forwarded-Host, or custom SNI headers unless your proxy product specifically requires it.

Compare a direct TLS handshake with a proxied one:

openssl s_client -connect example.com:443 -servername example.com -alpn h2
openssl s_client -proxy 127.0.0.1:8080 -connect example.com:443 -servername example.com -alpn h2

If the proxied command shows a different certificate, a handshake alert, or no expected ALPN result, inspect the proxy configuration and logs. Confirm that the proxy is receiving the destination hostname rather than an IP address or a generic placeholder. Also verify that the proxy is configured for HTTPS tunneling rather than only plain HTTP forwarding.

If the proxy terminates TLS, it becomes the TLS peer from the client's perspective. In that design, the proxy must have the correct certificate, support the required TLS versions and ALPN values, and present a chain trusted by the client. If the proxy only tunnels traffic, it does not need to decrypt or understand the encrypted HTTP/2 frames.

Fix certificate trust without weakening production

A message such as certificate verify failed or a browser warning beginning with NET::ERR_CERT_AUTHORITY_INVALID usually indicates a trust-chain problem. Compare the certificate issuer with the direct request. If the direct certificate is issued by the website owner but the proxied certificate is issued by a gateway or interception appliance, the proxy is probably performing TLS inspection.

The correct fix is to install the interception organization's root or intermediate certificate in the operating system and browser trust stores. Use the certificate supplied by your security or proxy administrator, and verify its source before installing it.

For a controlled test, point curl at a known certificate authority file:

curl -v --proxy 'http://127.0.0.1:8080' --cacert '/path/to/company-ca.pem' 'https://example.com/'

The same principle applies to Python. The verify option should point to the trusted CA bundle, not be disabled globally.

import requests

url = 'https://example.com/'
proxies = {
    'http': 'http://127.0.0.1:8080',
    'https': 'http://127.0.0.1:8080',
}

response = requests.get(
    url,
    proxies=proxies,
    timeout=(5, 20),
    verify='/etc/ssl/certs/company-ca.pem',
)
print(response.status_code, response.url)

Disabling verification can be useful for a short, isolated diagnostic in a lab, but it is not a production configuration. It makes man-in-the-middle attacks possible and can cause secrets in headers, cookies, and query strings to be exposed. Never copy that setting into a shared scraper, CI job, or customer-facing service.

Test ALPN and protocol fallback

ALPN lets the client and server agree on the application protocol during the TLS handshake. A server may prefer HTTP/2 while the client or proxy negotiates HTTP/1.1. This is often a compatibility issue rather than a failure of the proxy itself.

Try explicit protocol tests:

curl -v --proxy 'http://127.0.0.1:8080' --http1.1 'https://example.com/'
curl -v --proxy 'http://127.0.0.1:8080' --http2 'https://example.com/'

If HTTP/1.1 succeeds while the default request fails, check whether the proxy or TLS-terminating gateway supports the protocol expected by the origin. A transparent CONNECT tunnel can carry HTTP/2 without the proxy decrypting it. A terminating proxy must negotiate ALPN correctly and either forward the selected protocol or emulate the origin's behavior.

Do not force an obsolete protocol merely to make a test pass. Prefer enabling a supported modern protocol, updating the proxy software, or selecting a compatible proxy endpoint.

Check TLS versions and cipher policy

An outdated proxy may reject a modern client, or a security policy may disable a cipher required by an older origin. Test the minimum and maximum versions deliberately:

curl -v --proxy 'http://127.0.0.1:8080' --tlsv1.2 --tls-max 1.3 'https://example.com/'
openssl s_client -proxy 127.0.0.1:8080 -connect example.com:443 -servername example.com -tls1_2

Use these commands to gather evidence, not to weaken security. If the origin only supports TLS 1.0 or 1.1, the durable solution is to update the origin or proxy rather than enabling insecure protocols across an entire application.

Collect browser evidence

Browser Developer Tools provide useful evidence without changing application code. Open the Network panel, reproduce the request, and inspect:

  • the failure stage and status code;
  • the request and response headers;
  • the certificate issuer, validity period, and SAN values;
  • whether the request used the expected proxy;
  • whether another tab or clean browser profile produces the same result.

Extensions can inject certificates, alter headers, or terminate TLS. Test with extensions disabled and with a clean profile before changing the proxy. Browser certificate caches can also preserve an old result, so restart the browser after installing a trusted CA or changing proxy settings.

Use a small Python diagnostic loop

A diagnostic loop makes it easy to compare direct and proxied behavior while keeping the test repeatable. Keep logs limited to the stage, status, final URL, and elapsed time; do not log authorization headers or tokens.

import time
import requests

url = 'https://example.com/'
proxy = 'http://127.0.0.1:8080'

cases = [
    ('direct', {}),
    ('proxy', {'http': proxy, 'https': proxy}),
]

for name, proxies in cases:
    started = time.monotonic()
    try:
        response = requests.get(
            url,
            proxies=proxies,
            timeout=(5, 20),
            verify=True,
        )
        print(name, response.status_code, response.url, f'{time.monotonic() - started:.2f}s')
    except Exception as error:
        print(name, type(error).__name__, str(error))

If the direct case succeeds and the proxy case raises SSLError, inspect the certificate chain. If it raises a connection exception, check the proxy port and firewall. If it returns an HTTP response, move the investigation to the proxy access policy, origin response, and application-level rate limits.

Apply fixes one variable at a time

Use this evidence-based checklist:

  • No TCP connection: Verify the proxy hostname, port, firewall, and proxy type.
  • CONNECT returns 403 or 407: Check proxy authorization and access rules.
  • Handshake alert: Compare TLS versions, cipher suites, and proxy software.
  • Certificate authority error: Install the correct trusted CA or remove unnecessary TLS interception.
  • Unexpected certificate: Confirm SNI, hostname verification, and the proxy's certificate mapping.
  • ALPN mismatch: Enable the required protocol or use a compatible proxy endpoint.
  • HTTP response problem: Treat it separately from TLS and investigate authentication, origin rules, and rate limits.

Record the proxy version, endpoint, destination, timestamp, and sanitized response code. Change one setting per test, rerun the baseline, and keep the direct comparison. This approach finds the actual failure layer without turning a TLS problem into a broader scraper or networking incident.