Guide

Do Your Proxies Support WebSockets? How to Test It in Two Minutes

WebSockets fail through proxies in three specific ways. The two-minute test and how to read the failure.

HProxy Team··9 min read
HProxy.Guide

Free proxies won't hold up here.

Shared datacenter IPs get flagged and dropped fast. When it has to hold, gaming, streaming, accounts, you need mobile and residential IPs that read as a real device, from $0.44/GB, pay as you go.

See plans & pricing

A scraper that reads a live feed, a bot that keeps a session open, a dashboard that streams updates: all three run on WebSockets, and all three produce the same confusing bug report. The connection opens. Everything works. Then, at some point nobody can predict, the socket closes with no error worth reading, and the application reconnects into the same fate.

Proxy documentation is unusually quiet about this. Providers list HTTP, HTTPS and SOCKS5, publish uptime figures and pool sizes, and say nothing about whether a persistent upgraded connection survives the path. The answer is not the same for every product they sell, which is probably why.

Two commands settle it for any provider, and the failure modes are specific enough that the symptom names the cause.

The answer depends on which scheme you are using

A WebSocket starts life as an HTTP request carrying Upgrade: websocket, and what a proxy does with that request depends entirely on whether it can see it.

Encrypted, wss://. The client asks the proxy for a CONNECT tunnel to the destination on port 443, then performs TLS and the upgrade handshake inside that tunnel. The proxy sees an opaque byte stream and cannot interfere with the upgrade even if it wanted to. If the tunnel opens, the WebSocket almost always works.

Plaintext, ws://. The proxy sees the whole handshake and has to forward it correctly. This is where forward proxies fail, because Connection and Upgrade are hop-by-hop headers, meaning a strictly conforming intermediary is entitled to consume them rather than pass them on. A proxy that does exactly what the spec permits will break a plaintext WebSocket, and it is not misbehaving when it does.

SOCKS5. No opinion at any layer. SOCKS5 negotiates a destination and then forwards a TCP stream, so upgrades, hop-by-hop headers and protocol semantics are simply not its business. Our HTTP vs SOCKS5 explainer covers the wider difference.

The practical shortcut: prefer wss:// and prefer SOCKS5 where you have the choice, and treat a ws:// failure through an HTTP proxy as expected behaviour rather than a bug to escalate.

What each proxy type sees of a WebSocket handshake
  1. SOCKS5

    a TCP stream, no visibility, nothing to break

  2. HTTP proxy, wss://

    a CONNECT tunnel, contents opaque

  3. HTTP proxy, ws://

    the full Upgrade request, which it may strip

Source: HProxy, from RFC 6455 and RFC 7230 hop-by-hop header rules

The two-minute test

Test one proves the path exists. Test two proves the connection survives, which is a different claim and the one people skip.

# 1. Will the proxy open a tunnel to the destination at all?
curl -sv -o /dev/null -x http://USER:PASS@HOST:PORT https://your-target.example 2>&1 \
  | grep -iE 'CONNECT|established|407|403|502'

A 200 Connection established line means the tunnel is open and an encrypted WebSocket has a path. A 407 is authentication, a 403 usually means the destination port is not permitted, and a 502 means the proxy could not reach upstream. All three are covered in the proxy error guide.

// 2. Open a real socket through the proxy and hold it.
//    npm i ws https-proxy-agent
const WebSocket = require("ws");
const { HttpsProxyAgent } = require("https-proxy-agent");

const agent = new HttpsProxyAgent("http://USER:PASS@HOST:PORT");
const ws = new WebSocket("wss://your-target.example/socket", { agent });

const opened = Date.now();
ws.on("open", () => console.log("open"));
ws.on("message", (m) => console.log("msg", m.toString().slice(0, 80)));
ws.on("close", (c, r) => console.log(`closed after ${(Date.now() - opened) / 1000}s`, c, String(r)));
ws.on("error", (e) => console.log("error", e.message));

// Hold it. The number that matters is how long it lives, not that it opened.
setInterval(() => ws.readyState === 1 && ws.ping(), 25000);

Let it run for five minutes. An immediate failure is a handshake problem. A clean open followed by a death at a suspiciously round number is a timeout. A death at an unpredictable moment is usually rotation.

Use your real target rather than a public echo service where you can. Echo endpoints prove the proxy carries WebSockets in general and tell you nothing about whether your target accepts this particular exit IP, which is frequently the actual question.

Where this actually bites

WebSockets are easy to forget about because most scraping is request-and-response. The work where they matter is the work where the data is a stream rather than a page.

Live market and odds data. Exchanges, brokers and sportsbooks push updates over sockets because polling at that frequency would be absurd for both sides. A price you can only get by holding a connection is a price you can only get if your proxy holds connections.

Chat and gateway protocols. Platform gateways keep a persistent socket per client and treat a reconnect storm as a signal in itself, so a proxy that quietly drops connections does not merely slow you down, it makes you look like something the platform is designed to notice.

Dashboards and admin surfaces. A surprising number of the interfaces people automate render their first page over HTTP and then stream every subsequent update over a socket. The scrape appears to work, the page loads, and the numbers never change, because the update channel died at the proxy and nothing raised an error.

Real-time inventory and availability. Restock monitoring increasingly reads a stream rather than polling an endpoint, which moves the whole reliability question from request success rate to connection lifetime.

The pattern across all four is the same trap: the initial load succeeds, so every health check reports green, and the failure is invisible until somebody notices the data is stale. It belongs in the same family as the 200-with-a-block-page problem, where a status code says success and the content says otherwise.

When the socket will not survive, fall back deliberately

Sometimes the path genuinely cannot hold a long-lived connection, or the cost of holding it is not worth the freshness. Two alternatives are worth knowing before you spend a week fighting the transport.

The REST endpoint underneath. Many streaming interfaces are a convenience layer over an ordinary HTTP API that returns the same state. It is less elegant and less fresh, and it is dramatically easier to run through a rotating pool, retry, cache and parallelise. If your freshness requirement is measured in seconds rather than milliseconds, this is usually the better engineering decision, not a compromise.

Server-sent events or long polling. Both are plain HTTP, both survive proxies that mangle upgrades, and both are frequently offered by the same service as a documented fallback for exactly this reason. The trade is that you lose the client-to-server direction, so they fit read-only feeds and not interactive protocols.

The choice is a freshness budget, not a purity contest. Decide how stale the data is allowed to be, then pick the cheapest transport that meets it, because a five-second poll that never breaks beats a real-time socket that drops every four minutes and reconnects into a rate limit.

Reading the failure

SymptomAlmost alwaysFix
Handshake fails immediately on ws://Hop-by-hop headers stripped by the proxySwitch to wss://, or use SOCKS5
CONNECT returns 403 or 502Destination port blocked, or upstream unreachableConfirm the port is permitted; test port 443 first
Opens, dies at a round 30, 60 or 120 secondsIdle timeout on some hopApplication-level pings every 20 to 30 seconds
Opens, dies at unpredictable momentsExit IP rotated under the connectionSticky session for the socket's whole life
Opens, no messages arriveAn intermediary is buffering the streamPrefer wss://; buffering cannot survive an opaque tunnel
Works alone, fails at concurrencyPer-IP or per-session connection limitsSpread sockets across sessions rather than reusing one

The round-number rule is the most useful diagnostic on this page. Software timeouts are configured by humans, so they land on 30, 60, 120 and 300. A close at 61 seconds is a configuration somewhere in the path. A close at 47 seconds is something else entirely, and mixing the two up costs an afternoon.

Rotation is the one that surprises people

A rotating residential proxy is built to give you a different exit for each request, and that is exactly wrong for a socket you intend to hold for an hour. The abstraction that makes rotation elegant for scraping, one gateway address hiding many exits, is precisely what a long-lived TCP connection cannot tolerate: when the gateway moves you, the connection underneath the socket is gone, and your client sees a close with no explanation because nothing at the application layer went wrong.

The fix is a sticky session held for at least the intended lifetime of the socket, which our sticky versus rotating explainer covers in full. Two details are worth carrying into the design.

First, the sticky window is a maximum, not a promise, and a socket that outlives it will drop at the boundary. Build reconnection with backoff regardless, and treat the sticky window as the interval between planned reconnects rather than as permanence.

Second, one sticky session should carry one socket. Reusing a single session for many concurrent connections concentrates all of them on one exit IP, which is both a rate-limit target and a single point of failure, and it undoes the reason you bought a pool.

The billing detail nobody mentions

On a per-gigabyte product, a WebSocket meters continuously for as long as it is open. Every keepalive frame, every server push and every message you ignore is transferred traffic, and unlike request-based scraping the meter does not stop when your code is idle.

That changes the arithmetic on a live feed. A handful of sockets held all day against a chatty endpoint can move more traffic than a scraper making thousands of deliberate requests, and the usage graph will not look like anything your team expected. Measure a single socket for an hour before scaling to fifty, and set the keepalive interval to the largest value that survives the shortest timeout in your path rather than the smallest value that feels safe.

What to ask a provider

Four questions, and vague answers are informative in themselves.

  1. Are WebSockets supported on this product, as opposed to on the company's products generally? The answer often differs between rotating residential, ISP and datacenter.
  2. What is the idle timeout on a tunnelled connection? A provider that knows the number has thought about long-lived connections. One that has never been asked will say there is no limit, which is rarely true of the whole path.
  3. What is the maximum sticky session duration, and what happens at the boundary: a clean close or a silent swap?
  4. Which destination ports are permitted? Port restrictions surface as a 403 on CONNECT and get misdiagnosed as authentication for hours.

Where we stand

Our residential products run HTTP, HTTPS and SOCKS5, with sessions that can be rotating or sticky for up to 24 hours, which is the combination a long-lived socket needs: SOCKS5 for a transport that has no opinion about upgrades, and a sticky window long enough to make reconnects a scheduled event rather than a surprise.

The honest part is the same as everywhere else on this blog: none of that guarantees your specific target accepts your specific exit IP, and that is a separate measurement. Run the proxy checker on an exit to see the ASN and country a target will judge, and if you want to test the whole path before committing anything, residential starts at $0.50/GB for one gigabyte on a balance that does not expire. One socket held for an hour is a cheap experiment, and it answers the question that no documentation page can.

Frequently asked questions

Do proxies support WebSockets?
SOCKS5 proxies do by design, because they forward a TCP stream and never inspect what travels inside it. HTTP proxies depend on the scheme: an encrypted wss:// connection rides inside a CONNECT tunnel that the proxy cannot interfere with, while a plaintext ws:// connection needs the proxy to forward the Upgrade handshake, which not every forward proxy does correctly.
Why does my WebSocket connect and then drop after about a minute?
That is almost always an idle timeout on an intermediary. A WebSocket can sit silent for long periods, and any hop that measures idleness in bytes rather than in protocol liveness will close it. The fix is application-level keepalive frames at an interval shorter than the shortest timeout in the path, typically every twenty to thirty seconds.
Why does a WebSocket break on a rotating residential proxy?
Because rotation changes the exit IP and a WebSocket is a single long-lived TCP connection. The moment the gateway moves you to a different exit, the connection underneath the socket is gone, so the client sees an abrupt close with no error from the application. Long-lived sockets need a sticky session, not per-request rotation.
How do I test whether a proxy passes WebSockets?
Two commands. First, confirm the proxy will open a CONNECT tunnel to port 443 on your target, which is what an encrypted WebSocket needs. Second, open a real socket through the proxy with a small client script and hold it for a couple of minutes. The first proves reachability, the second is the only thing that proves the connection survives.
Does a WebSocket cost more bandwidth on a per-GB proxy?
It costs what it transfers, including every keepalive frame and every server push, and it keeps costing while the socket is open even when your code is idle. That is different from request-based scraping, where usage tracks your own activity, so a handful of long-lived sockets can meter far more than expected over a day.
Should I use SOCKS5 for WebSockets?
It is the simplest choice, because SOCKS5 sits below HTTP and forwards bytes without opinions about upgrades or hop-by-hop headers. If a provider offers both and you are debugging a WebSocket that fails on HTTP, switching to SOCKS5 is a fast way to find out whether the proxy's HTTP handling is the problem.

Proxies that don't die mid-job

Residential, ISP, datacenter and mobile, verified by the same engine that runs tens of millions of checks. They read as a real device and hold up under load. Pay as you go, and your balance never expires. $0.44/GB is the 2,000 GB+ rate; a single gigabyte is $0.50/GB, with no minimum order.

129M+ proxy checks run · 100+ countries · HTTP / HTTPS / SOCKS · re-checked every few minutes · no signup