Your proxies usually support WebSockets over wss://, which runs inside a CONNECT tunnel, while plain ws:// can fail at a forward proxy. A socket that closes at random usually means a rotating session moved you to a new exit. Hold a sticky session for the socket's lifetime, and test any provider with two commands. 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 bring the same confusing bug report. The connection opens. Everything works. Then, at a moment nobody can predict, the socket closes with no error worth reading, and the application reconnects into the same fate.
Proxy documentation is quiet about this. Providers list HTTP, HTTPS and SOCKS5, publish uptime figures and pool sizes, and say nothing about whether a long-lived 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.
Few people search for the question directly, and that is the point: the searches cluster on the symptoms instead.
Does the answer depend on which scheme I use?
Yes, and the WebSocket standard spells out why. A WebSocket starts as an HTTP request that carries Upgrade: websocket and Connection: Upgrade, the exact handshake RFC 6455 prints in section 1.3. What a proxy does with that request depends on whether it can see it.
Encrypted, wss://. The client asks the proxy for a CONNECT tunnel to the destination on port 443, then runs TLS and the upgrade handshake inside that tunnel. The proxy sees an opaque byte stream and cannot touch the upgrade. 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 a forward proxy fails, and the reason is in the HTTP standard. RFC 9110 section 7.6.1 says an intermediary must parse the Connection header before forwarding and, for each option named there, "remove any header or trailer field(s) from the message with the same name". A WebSocket sends Connection: Upgrade. So a proxy that does not know WebSockets may strip the Upgrade header, and the same section lists Upgrade among the fields a proxy "SHOULD remove or replace". A proxy that breaks a ws:// handshake this way is following the spec.
SOCKS5. No opinion at any layer. RFC 1928 defines SOCKS5 around a CONNECT request that opens a TCP stream, so upgrades, hop-by-hop headers and protocol semantics are not its business. RFC 6455 reaches the same conclusion. Clients should "use a SOCKS5 proxy for WebSocket connections, if available", it says, or else prefer the HTTPS proxy over the HTTP one. Our HTTP vs SOCKS5 explainer covers the wider difference.
The shortcut: prefer wss:// and prefer SOCKS5 where you have the choice, and treat a ws:// failure through an HTTP proxy as expected behavior 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
What is 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. The second uses the ws library (8.22.0) and https-proxy-agent (9.1.0), the current versions on October 23, 2026.
# 1. Will the proxy open a tunnel to the destination at all?
curl -sv -o /dev/null -x http://YOUR-LOGIN:YOUR-PASSWORD@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@8.22.0 https-proxy-agent@9.1.0
const WebSocket = require("ws");
const { HttpsProxyAgent } = require("https-proxy-agent");
const agent = new HttpsProxyAgent("http://YOUR-LOGIN:YOUR-PASSWORD@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. An echo endpoint proves the proxy carries WebSockets in general and tells you nothing about whether your target accepts this particular exit IP, which is often the real question.
Where does this actually bite?
WebSockets are easy to forget because most scraping is request-and-response. They matter 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 wasteful 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, so a proxy that quietly drops the connection does more than slow you down. The data stops arriving and the client spends its time reconnecting.
- Dashboards and admin surfaces. Many interfaces render their first page over HTTP and then stream every 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, which moves the whole reliability question from request success rate to connection lifetime.
The pattern across all four is one trap: the initial load succeeds, so every health check reports green, and the failure is invisible until someone notices the data is stale. It belongs to the same family as the 200-with-a-block-page problem, where the status code says success and the content says otherwise.
What if the socket will not survive?
Sometimes the path 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 fresh, and it is far easier to run through a rotating pool, retry, cache and parallelize. If your freshness requirement is measured in seconds rather than milliseconds, this is usually the better engineering decision.
Server-sent events or long polling. Both are plain HTTP, both survive a proxy that strips upgrades, and both are often offered by the same service as a documented fallback. The trade is that you lose the client-to-server direction, so they fit read-only feeds and not interactive protocols.
The choice comes down to a freshness budget. Decide how stale the data may 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.
How do I read the failure?
WebSocket-through-a-proxy failures, and what each one usually is
| Symptom | Almost always | Fix |
|---|---|---|
| Handshake fails at once 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 a hop | Ping frames 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 | A hop is buffering the stream | Prefer wss://; buffering cannot survive an opaque tunnel |
| Works alone, fails at volume | Per-IP or per-session connection limits | Spread sockets across sessions rather than reusing one |
HProxy, from the failure modes in RFC 6455, RFC 9110 and RFC 1928
The round-number rule is the most useful line here. Software timeouts are set by people, 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, and mixing the two up costs an afternoon.
Why does rotation catch people out?
A rotating residential proxy is built to give you a different exit for each request, and that is exactly wrong for a socket you mean to hold for an hour. The abstraction that makes rotation elegant for scraping, one gateway address hiding many exits, is the thing a long-lived TCP connection cannot tolerate. When the gateway moves you, the connection under 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 drops at the boundary. Build reconnection with backoff regardless, and treat the sticky window as the interval between planned reconnects. Second, one sticky session should carry one socket. Reusing one session for many connections puts all of them on one exit IP. That is both a rate-limit target and a single point of failure, and it undoes the reason you bought a pool.
What does a socket cost on a per-GB plan?
A WebSocket meters continuously for as long as it is open. Every ping 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 few sockets held all day against a chatty endpoint can move more traffic than thousands of deliberate requests. The usage graph will not look like anything your team expected. Measure a single socket for an hour before scaling to fifty. Set the ping interval as high as it can go while still surviving the shortest timeout in your path, which is safer than a short interval chosen by feel.
What should I ask a provider?
Four questions, and a vague answer is informative in itself.
- Are WebSockets supported on this product, as opposed to the company's products in general? The answer often differs between rotating residential, ISP and datacenter.
- What is the idle timeout on a tunneled 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 Premium runs HTTP, HTTPS and SOCKS5, with sessions that rotate or stay sticky from 3 minutes to 24 hours. That is the combination a long-lived socket needs. SOCKS5 is a transport with no opinion about upgrades, and the sticky window is 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 target accepts your exit IP, and that is a separate measurement you have to run. Our proxy checker shows the ASN and country a target will judge an exit by. To test the whole path before you commit anything, our residential proxies cost $0.50 for one GB, falling to $0.18/GB at 1,000 GB+, on a balance that does not expire. One socket held for an hour is a cheap experiment, and it answers the question no documentation page can.
Sources
- RFC 6455, December 2011: the WebSocket opening handshake (section 1.3), the proxy-usage note that recommends SOCKS5 (section 4.1), and ping frames (section 5.5.2).
- RFC 9110 section 7.6.1, June 2022: how an intermediary removes the header fields named in a Connection option, with Upgrade listed by name.
- RFC 1928, March 1996: the SOCKS5 CONNECT request that opens a TCP stream.
- ws 8.22.0 and https-proxy-agent 9.1.0, the current npm versions read October 23, 2026, used in the test script.
- Google Keyword Planner, worldwide monthly searches, read October 23, 2026.



