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.
SOCKS5
a TCP stream, no visibility, nothing to break
HTTP proxy, wss://
a CONNECT tunnel, contents opaque
HTTP proxy, ws://
the full Upgrade request, which it may strip
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
| Symptom | Almost always | Fix |
|---|---|---|
Handshake fails immediately on ws:// | Hop-by-hop headers stripped by the proxy | Switch to wss://, or use SOCKS5 |
CONNECT returns 403 or 502 | Destination port blocked, or upstream unreachable | Confirm the port is permitted; test port 443 first |
| Opens, dies at a round 30, 60 or 120 seconds | Idle timeout on some hop | Application-level pings every 20 to 30 seconds |
| Opens, dies at unpredictable moments | Exit IP rotated under the connection | Sticky session for the socket's whole life |
| Opens, no messages arrive | An intermediary is buffering the stream | Prefer wss://; buffering cannot survive an opaque tunnel |
| Works alone, fails at concurrency | Per-IP or per-session connection limits | Spread 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.
- 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.
- 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.
- What is the maximum sticky session duration, and what happens at the boundary: a clean close or a silent swap?
- 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.