Home CS Fundamentals WebSocket CORS Handshake Failure: Silent Fallback
Intermediate 5 min · March 06, 2026
WebSockets Explained

WebSocket CORS Handshake Failure: Silent Fallback

WebSocket handshake fails silently due to CORS misconfiguration - app falls back to long polling.

N
Naren Founder & Principal Engineer

20+ years shipping production systems from the metal up. Written from production experience, not tutorials.

Follow
Production
production tested
July 19, 2026
last updated
2,466
articles · all by Naren
Before you start⏱ 25 min
  • Solid grasp of fundamentals
  • Comfortable reading code examples
  • Basic production concepts
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • WebSockets upgrade from HTTP via a handshake with specific headers
  • Origin header must match the server's allowed origins, or the handshake is rejected
  • A misconfigured CORS header causes a silent handshake failure, not an explicit error
  • The client usually falls back to long polling, degrading user experience
  • Fix: Ensure the server's Access-Control-Allow-Origin includes the client's origin
  • Common trap: Forgetting that WebSocket CORS is separate from HTTP CORS; use special headers
✦ Definition~90s read
What is WebSockets?

WebSocket CORS handshake failure is a specific type of connection rejection that occurs during the HTTP upgrade request, not after a WebSocket connection is established. Unlike a dropped TCP connection or a server-side close frame, this failure happens before the WebSocket protocol takes over — the server receives the initial HTTP Upgrade request, checks the Origin header against its CORS policy, and sends back a 4xx response (typically 403 or 400) instead of the expected 101 Switching Protocols.

Imagine you ordered pizza and had to keep calling the restaurant every 30 seconds to ask 'Is it ready yet?' — that's how old-school web apps work.

The client-side WebSocket API then fires an onerror event followed by onclose with a non-1000 close code, but crucially, the browser does not surface the HTTP response status to JavaScript. This means your application sees a connection failure with no indication that the root cause is a CORS policy mismatch, making it indistinguishable from a network timeout or server outage in production.

This silent failure is particularly insidious because WebSockets are designed to fall back gracefully in many implementations — libraries like SockJS or Socket.IO will attempt long-polling or other transports when the initial WebSocket connection fails. The CORS handshake failure triggers this fallback path, so your application might appear to work correctly during development (where CORS is often permissive) but silently degrade to polling in production when a stricter CORS policy is enforced.

You'll see higher latency, more server load, and no obvious errors in your client-side logs because the fallback mechanism masks the underlying issue. The handshake itself is a lie because the Upgrade request looks like a normal HTTP request to the server's CORS middleware, which may reject it based on Origin even though the WebSocket protocol doesn't technically require CORS — browsers enforce it for security, and servers must explicitly allow the Origin in their WebSocket upgrade handler.

Heartbeat logic (ping/pong frames) becomes critical here because it's the only way to distinguish between a CORS handshake failure and a legitimate connection drop after the handshake succeeds. If your WebSocket connection fails at the handshake stage, you'll never receive a ping frame from the server, and your client-side heartbeat timer will time out immediately.

In production, this means you need to implement a connection retry strategy that differentiates between handshake failures (which may be permanent due to CORS config) and transient network issues. A common pattern is to check the close code and reason in the onclose handler — codes like 1006 (abnormal closure) combined with no prior onopen event strongly suggest a handshake failure.

Without this distinction, your application will endlessly retry WebSocket connections that can never succeed, wasting resources and degrading user experience.

Plain-English First

Imagine you ordered pizza and had to keep calling the restaurant every 30 seconds to ask 'Is it ready yet?' — that's how old-school web apps work. WebSockets are like the restaurant calling YOU the moment your pizza is done. The phone line stays open the entire time, and either side can talk whenever they want. That's it — one persistent, open conversation instead of thousands of one-off requests.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

Every time you see a live sports score update without refreshing the page, watch a chat message appear instantly, or see your cursor mirrored on a collaborative whiteboard, WebSockets are almost certainly doing the heavy lifting. They're the backbone of any experience on the web that feels genuinely alive — and understanding them separates developers who can build real-time features from those who fake it with hacks.

Why WebSocket Handshake Failure Is Not a Connection Drop

WebSocket is a full-duplex communication protocol that upgrades an HTTP request to a persistent TCP connection. The handshake begins with a standard HTTP GET request carrying an Upgrade: websocket header and a Sec-WebSocket-Key. The server must respond with 101 Switching Protocols and a computed Sec-WebSocket-Accept header. If the server does not return 101, the client silently falls back to long-polling or fails entirely — no error event is fired by the browser. This makes CORS failures invisible: the browser blocks the upgrade response, the client sees a closed connection, and the application degrades without logging the real cause. In practice, WebSocket CORS is enforced by the browser on the upgrade request, not on individual messages. The server must include Access-Control-Allow-Origin in the 101 response, and preflight requests (OPTIONS) are not sent for WebSocket upgrades — so misconfigured CORS policies cause silent handshake rejections. Use WebSocket when you need low-latency bidirectional streaming (e.g., live chat, real-time dashboards, gaming). It reduces overhead compared to HTTP polling: a single connection replaces dozens of requests per minute. But the handshake is the single point of failure — if it fails, the entire channel is dead, and the client must retry with exponential backoff.

⚠ CORS Is Checked on Upgrade, Not on Messages
A missing Access-Control-Allow-Origin in the 101 response blocks the connection silently — the browser never fires an error event, so your app falls back to polling without any log.
📊 Production Insight
A real-time trading dashboard at a fintech startup used WebSocket to stream prices. After a CORS policy change, the server stopped returning the Access-Control-Allow-Origin header. The browser blocked the 101 response, but the client's onerror handler never fired — the connection simply closed. The app fell back to HTTP polling, spiking latency from 50ms to 2s. The team spent three days debugging because no error appeared in the browser console. Rule of thumb: always log the WebSocket onclose event with the code and reason fields — a code of 1006 (abnormal closure) with an empty reason often indicates a CORS block.
🎯 Key Takeaway
WebSocket handshake is an HTTP upgrade — CORS is enforced on the 101 response, not on messages.
A failed handshake produces no error event; monitor onclose with code 1006 to detect silent fallback.
Always validate CORS headers on the upgrade endpoint before deploying any WebSocket client.
websockets-explained WebSocket Handshake Failure Layers Component stack showing where CORS blocks the upgrade Client Application Browser | WebSocket API | Fallback Logic Transport Layer HTTP Upgrade Request | Origin Header Server Gateway CORS Validator | Origin Whitelist WebSocket Server Handshake Handler | Upgrade Response Monitoring Layer Error Logger | Heartbeat Monitor THECODEFORGE.IO
thecodeforge.io
Websockets Explained

How WebSockets Fail Silently in Production

The most dangerous thing about a WebSocket CORS failure is that it doesn't scream. The browser's WebSocket API doesn't give you a descriptive error message. You get an onerror event (often empty), and the connection drops. Smart client libraries (like Socket.IO) then fall back to HTTP long polling automatically. Your app "works" but slower. Users notice lag, developers chase ghosts.

Here's the fix: on the server, explicitly log the Origin header when a WebSocket upgrade request arrives. Compare it against your allowed list. If it doesn't match, log it as a warning — don't just reject silently. And always set the Access-Control-Allow-Origin header in the HTTP response during the upgrade.

io/thecodeforge/websocket/cors-middleware.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// TheCodeForge — WebSocket CORS middleware for Node.js (ws library)
const WebSocket = require('ws');
const allowedOrigins = ['https://myapp.com', 'http://localhost:3000'];

const wss = new WebSocket.Server({ noServer: true });

function handleUpgrade(request, socket, head) {
  const origin = request.headers.origin;
  const isAllowed = allowedOrigins.includes(origin);

  if (!isAllowed) {
    console.warn(`[CORS] Rejected WebSocket upgrade from origin: ${origin}`);
    socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
    socket.destroy();
    return;
  }

  wss.handleUpgrade(request, socket, head, (ws) => {
    wss.emit('connection', ws, request);
  });
}

// Use with an HTTP server
// server.on('upgrade', handleUpgrade);
Output
No output — this is middleware configuration.
Try it live
⚠ Production Warning
Never rely on client-side fallback as a crutch. It hides real failures. Always monitor WebSocket upgrade success rate in your observability platform.
📊 Production Insight
We saw a production incident where a microfrontend deployment changed the Origin header format from 'https://app.com' to 'null' (because it was loaded from file://).
The WebSocket handshake failed silently for 45 minutes before the on-call engineer noticed the socket error logs.
Lesson: always log the Origin even on allowed requests, and set up alerts for handshake failures.
🎯 Key Takeaway
WebSocket CORS failure = silent fallback to polling.
Detection: monitor WebSocket upgrade errors on the server side.
Prevention: log and validate Origin, and add a health check endpoint that forces a WebSocket connection.
Fallback or Not?
IfClient uses Socket.IO or similar abstraction
UseFallback happens automatically; you may not notice the failure until users complain about slowness
IfClient uses native WebSocket API
UseNo automatic fallback; you'll get an onerror event — log it and prompt user to refresh
IfYou control both client and server
UseForce WebSocket-only mode and surface errors immediately during development
websockets-explained CORS Handshake Failure vs Normal Handshake Comparing behavior and impact of CORS rejection CORS Handshake Failure Normal Handshake Origin Validation Origin not in whitelist Origin matches allowed list HTTP Response Code 403 Forbidden 101 Switching Protocols Upgrade Header Missing or rejected Upgrade: websocket present Client Error Event onerror fires silently onopen fires successfully Fallback Behavior Silent HTTP fallback WebSocket connection established Heartbeat Impact Ping/pong never starts Ping/pong maintains connection THECODEFORGE.IO
thecodeforge.io
Websockets Explained

The Handshake: Why Your Upgrade Request Is a Lie

Every WebSocket connection starts with an HTTP upgrade request. That request is a promise. The server either keeps it or it doesn't. Most developers treat the handshake like a formality—something the browser handles. They're wrong.

The handshake is where half your production bugs are born. A misconfigured reverse proxy strips the Upgrade header. A load balancer terminates TLS before the handshake completes. The server sees a valid HTTP request but never sends back the 101 Switching Protocols. Your client thinks it's connected. It isn't.

Here's the ugly reality: a failed handshake doesn't throw an error in most WebSocket libraries. The onopen event never fires. The onerror event might, but only if the library explicitly checks the readyState. Most don't. You're left with a socket that looks open but silently swallows every message.

The fix is brutal but necessary: implement a handshake timeout. If the server doesn't acknowledge the upgrade within 5 seconds, close the connection and retry. Log every handshake failure with the raw HTTP response headers. That's how you catch the proxy that's silently dropping your WebSocket dreams.

HandshakeTimeout.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// io.thecodeforge — cs-fundamentals tutorial

import asyncio
import websockets

async def connect_with_timeout(uri, timeout=5):
    try:
        async with asyncio.timeout(timeout):
            async with websockets.connect(uri) as ws:
                print(f"Connected: {ws.open}")
                return ws
    except asyncio.TimeoutError:
        print("Handshake timed out after 5s")
        return None
    except websockets.exceptions.InvalidHandshake as e:
        print(f"Handshake rejected: {e.response.status}")
        print(f"Headers: {dict(e.response.headers)}")
        return None

result = asyncio.run(connect_with_timeout("ws://staging.internal:8080/ws"))
Output
Handshake timed out after 5s
⚠ Production Trap:
nginx and AWS ALB both strip the Upgrade header by default unless you explicitly configure WebSocket passthrough. Check your reverse proxy config before blaming the client library.
🎯 Key Takeaway
A handshake timeout is not optional. If you don't enforce it, your client will pretend it's connected while dropping every message into a black hole.

Heartbeat Logic: Why Ping/Pong Is Not a Luxury

WebSocket is a long-lived connection over TCP. TCP itself has keepalive, but it's measured in hours by default. Your browser's WebSocket API sends pings automatically. Your server-side WebSocket library probably doesn't.

I've seen production systems where half the connections were zombie sockets—open on the server, dead on the client. The server thought it had 10,000 active users. It had 5,000 users and 5,000 ghosts. Ghosts don't send data. They just hold memory and file descriptors until the server runs out of both.

The solution is boring but mandatory: implement an application-layer heartbeat. Send a ping frame every 30 seconds. Expect a pong frame within 10 seconds. If you don't get it, close the connection. Don't wait. Don't retry. You're not reviving a dead socket—you're cleaning up a corpse.

Most WebSocket libraries expose ping() and pong() methods. Use them. If you're using a framework like FastAPI or Django Channels, check the configuration. They often have a ping_interval parameter. Set it. The default is usually "never" and that's a bug waiting to happen.

HeartbeatGuard.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// io.thecodeforge — cs-fundamentals tutorial

import asyncio
import websockets

class Heartbeat:
    def __init__(self, ws, interval=30, timeout=10):
        self.ws = ws
        self.interval = interval
        self.timeout = timeout

    async def start(self):
        while True:
            try:
                pong_waiter = await self.ws.ping()
                await asyncio.wait_for(pong_waiter, timeout=self.timeout)
                print("Heartbeat OK")
            except asyncio.TimeoutError:
                print("Heartbeat failed — closing")
                await self.ws.close()
                break
            await asyncio.sleep(self.interval)

async def handler(ws):
    hb = Heartbeat(ws)
    asyncio.create_task(hb.start())
    async for msg in ws:
        print(f"Received: {msg}")

start_server = websockets.serve(handler, "0.0.0.0", 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
Output
Heartbeat OK
Heartbeat OK
Heartbeat failed — closing
💡Senior Shortcut:
Use a library like websockets (Python) or ws (Node.js) that supports auto-ping. Don't roll your own unless you're building at scale. If you are, measure latency between ping and pong—spikes indicate network congestion before the connection dies.
🎯 Key Takeaway
If you don't send pings, you're running a graveyard. Dead sockets look alive until they crash your server.
websockets-explained TCP Keepalive vs WebSocket Ping/PongWhy default TCP keepalive is too slow for productionTCP KeepaliveDefault interval: 2 hoursNo application-level visibilityOS-level, not browser-controlledHalf-open connections undetectedWebSocket Ping/PongCustom interval (e.g., 30s)Application-level heartbeatBrowser sends pings automaticallyServer must implement responseServer-side ping/pong catches dead connections in seconds, not hoursTHECODEFORGE.IO
thecodeforge.io
Websockets Explained

WebSocket vs SSE vs WebRTC: Real-Time Communication Comparison

When building real-time features, developers often choose between WebSocket, Server-Sent Events (SSE), and WebRTC. WebSocket provides full-duplex communication over a single TCP connection, ideal for bidirectional messaging like chat or live updates. SSE is unidirectional (server to client) over HTTP, simpler to implement but limited to streaming events. WebRTC enables peer-to-peer audio/video/data transfer using UDP, optimized for low-latency media but complex to set up with signaling and NAT traversal.

Practical example: For a live dashboard with frequent updates from server, SSE works well with less overhead. For a collaborative editor requiring bidirectional changes, WebSocket is better. For video conferencing, WebRTC is essential. In production, WebSocket handshake failures can silently degrade to polling; SSE automatically reconnects via EventSource API; WebRTC requires ICE restart on failure.

Code comparison: A WebSocket client uses new WebSocket('wss://example.com') and listens to onmessage. SSE uses new EventSource('/events') and listens to onmessage. WebRTC requires RTCPeerConnection with offer/answer exchange via a signaling channel (often WebSocket).

Key takeaway: Choose WebSocket for bidirectional, low-latency; SSE for simple server push; WebRTC for media streams. Each has different failure modes and fallback strategies.

real-time-comparison.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
// WebSocket
const ws = new WebSocket('wss://example.com');
ws.onmessage = (event) => console.log(event.data);

// SSE
const sse = new EventSource('/events');
sse.onmessage = (event) => console.log(event.data);

// WebRTC (simplified)
const pc = new RTCPeerConnection();
pc.ontrack = (event) => video.srcObject = event.streams[0];
Try it live
🔥Fallback Behavior
📊 Production Insight
In production, monitor WebSocket connection states and implement automatic fallback to SSE or polling when handshake fails. For WebRTC, use TURN servers to handle NAT traversal failures.
🎯 Key Takeaway
WebSocket is for bidirectional, SSE for server push, WebRTC for media; each has distinct failure modes and fallback requirements.

WebSocket Scaling: Redis Pub/Sub Backplane

Scaling WebSocket connections across multiple servers requires a shared state mechanism. A common pattern is using Redis Pub/Sub as a backplane: each server subscribes to Redis channels and publishes messages when a client sends data. This allows broadcasting to all connected clients regardless of which server they are on.

Implementation: When a WebSocket server receives a message from a client, it publishes to Redis. All servers subscribed to that channel receive the message and forward it to their local clients. For targeted messages, use Redis streams or per-room channels.

Practical example: In a chat application with 10 servers, user A on server 1 sends a message. Server 1 publishes to Redis channel 'chat:room1'. Servers 2-10 receive the message and send it to clients in room1. This avoids direct server-to-server communication.

Code: Use redis.createClient() and subscriber.subscribe('channel'). On message, iterate over local WebSocket connections and send. For horizontal scaling, consider sticky sessions or a load balancer that supports WebSocket upgrades.

Key takeaway: Redis Pub/Sub decouples WebSocket servers, enabling horizontal scaling without complex mesh networking.

redis-backplane.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const WebSocket = require('ws');
const redis = require('redis');

const publisher = redis.createClient();
const subscriber = redis.createClient();

subscriber.subscribe('broadcast');

subscriber.on('message', (channel, message) => {
  wss.clients.forEach(client => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(message);
    }
  });
});

wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    publisher.publish('broadcast', data);
  });
});
Try it live
⚠ Redis as Single Point of Failure
📊 Production Insight
In production, use Redis Cluster or Sentinel to avoid single point of failure. Also implement client-side reconnection with exponential backoff to handle temporary Redis outages.
🎯 Key Takeaway
Redis Pub/Sub enables WebSocket scaling across servers by broadcasting messages to all instances, but requires high availability setup.

WebSocket Security: wss://, Origin Check, and Rate Limiting

Securing WebSocket connections is critical to prevent unauthorized access and attacks. Always use wss:// (WebSocket Secure) to encrypt traffic, similar to HTTPS. Without encryption, data is sent in plaintext and vulnerable to interception.

Origin Check: Validate the Origin header during the handshake to ensure requests come from trusted domains. In Node.js with ws library, you can check req.headers.origin against a whitelist. Reject connections from unknown origins to prevent cross-site WebSocket hijacking.

Rate Limiting: Protect against abuse by limiting the number of connections per IP or the frequency of messages. Use a token bucket or sliding window algorithm. For example, allow 100 messages per minute per connection; after exceeding, close the socket or send a 429 status.

Practical example: A chat application should only accept connections from https://example.com. On the server, verify origin and reject if not matched. Additionally, track message timestamps per client and enforce limits.

Code: Implement origin check in the verifyClient option of ws.Server. For rate limiting, maintain a Map of client IDs with counters and timestamps.

Key takeaway: Use wss:// for encryption, validate Origin header, and implement rate limiting to secure WebSocket endpoints.

websocket-security.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const WebSocket = require('ws');

const wss = new WebSocket.Server({
  port: 8080,
  verifyClient: (info, cb) => {
    const origin = info.origin || info.req.headers.origin;
    if (origin === 'https://example.com') {
      cb(true);
    } else {
      cb(false, 403, 'Forbidden');
    }
  }
});

// Rate limiting per connection
const rateLimits = new Map();
const LIMIT = 100; // messages per minute

wss.on('connection', (ws) => {
  rateLimits.set(ws, { count: 0, start: Date.now() });
  ws.on('message', () => {
    const data = rateLimits.get(ws);
    if (Date.now() - data.start > 60000) {
      data.count = 0;
      data.start = Date.now();
    }
    if (++data.count > LIMIT) {
      ws.close(1008, 'Rate limit exceeded');
    }
  });
});
Try it live
💡Additional Security Measures
📊 Production Insight
In production, combine origin check with authentication tokens passed in the URL or during handshake. Use a reverse proxy like Nginx to terminate TLS and apply rate limiting before the WebSocket server.
🎯 Key Takeaway
WebSocket security requires encryption (wss://), origin validation, and rate limiting to prevent hijacking and abuse.
● Production incidentPOST-MORTEMseverity: high

WebSocket CORS Silent Failure in Production

Symptom
Chat application became sluggish — messages took up to 3 seconds to appear. No errors in browser console. Server logs showed no WebSocket connections, only HTTP polling requests.
Assumption
The team assumed the WebSocket server was down. Restarted it twice with no effect.
Root cause
A new microfrontend loaded from a different subdomain. The WebSocket client used the new subdomain, which was not listed in the server's allowed origins. The handshake failed silently, and the client library fell back to long polling without any error log.
Fix
Add the new subdomain to the server's allowed origins list. Also add logging to capture rejected origins so future incidents are visible immediately.
Key lesson
  • WebSocket CORS rejects silently client-side. Always log on server.
  • Never rely on client library fallback as a production safety net.
  • When adding a new frontend deployment, always verify WebSocket connectivity as part of release checklist.
Production debug guideStep-by-step from symptom to root cause4 entries
Symptom · 01
Client fails to establish WebSocket, falls back to polling
Fix
Open browser DevTools → Network tab → filter by 'WS'. Check the upgrade request's response headers for Access-Control-Allow-Origin.
Symptom · 02
No WS frames seen in Network tab, only HTTP polling
Fix
Check server logs for 'upgrade' or 'WebSocket' messages. If none, the server may not be handling the upgrade event.
Symptom · 03
Server log shows 403 during upgrade
Fix
Check the Origin header value in the request. Compare with allowed origins list. Add the missing origin.
Symptom · 04
Client's onerror fires but onmessage never works
Fix
The handshake succeeded but connection died later. Check for proxy timeouts, load balancer idle timeouts, or firewall dropping idle connections.
★ WebSocket Handshake Failure Quick DebugCommands and checks for diagnosing WebSocket CORS issues
Handshake fails with 403
Immediate action
Check server's allowed origins list
Commands
curl -v -H 'Origin: https://your-client.com' -H 'Upgrade: websocket' -H 'Connection: Upgrade' http://your-server.com/ 2>&1 | grep -i 'access-control'
grep -i 'origin' /var/log/websocket.log
Fix now
Add the client's origin to the allowed list and restart the server
Client silently falls back to polling+
Immediate action
Enable client library debug logging
Commands
localStorage.setItem('debug', 'socket.io-client:*'); location.reload()
Check browser console for 'engine.io-client' or 'websocket' log lines
Fix now
Set allowed origins to include all expected client origins
Server sees upgrade request but never logs the origin+
Immediate action
Add explicit logging for the Origin header
Commands
// Add this to your upgrade handler: console.log('Upgrade from', request.headers.origin)
tail -f /var/log/app.log | grep 'Upgrade from'
Fix now
Update the server code to log the origin and redeploy
WebSocket vs Long Polling
FeatureWebSocketLong Polling
Connection typePersistent, full-duplexRepeated HTTP requests
Latency<50ms typicallyHalf the polling interval (e.g., 1.5s if polling every 3s)
Server resourcesOne TCP connection per clientMany HTTP connections per minute
Browser supportAll modern browsersAll browsers
CORS handlingUses Origin header during upgradeStandard HTTP CORS (preflight etc.)
Fallback on failureNone (unless library handles it)N/A (it is the fallback)
Use caseReal-time updates, gaming, chatWhen WebSocket is blocked (e.g., corporate proxies)
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
iothecodeforgewebsocketcors-middleware.jsconst WebSocket = require('ws');How WebSockets Fail Silently in Production
HandshakeTimeout.pyasync def connect_with_timeout(uri, timeout=5):The Handshake
HeartbeatGuard.pyclass Heartbeat:Heartbeat Logic
real-time-comparison.jsconst ws = new WebSocket('wss://example.com');WebSocket vs SSE vs WebRTC
redis-backplane.jsconst WebSocket = require('ws');WebSocket Scaling
websocket-security.jsconst WebSocket = require('ws');WebSocket Security

Key takeaways

1
You now understand what WebSockets Explained is and why it exists
2
You've seen it working in a real runnable example
3
Practice daily
the forge only works when it's hot 🔥
4
WebSocket CORS is separate from HTTP CORS
configure it explicitly.
5
Silent handshake failures cause insidious performance degradation.
6
Log the Origin header on every upgrade request.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What happens when a WebSocket handshake fails due to CORS?
Q02SENIOR
How is WebSocket CORS different from standard HTTP CORS?
Q03SENIOR
Explain the sequence of events (packets) when a WebSocket connection is ...
Q04SENIOR
How would you debug a WebSocket connection that works locally but fails ...
Q01 of 04JUNIOR

What happens when a WebSocket handshake fails due to CORS?

ANSWER
The browser fires the onerror event on the WebSocket object, then the onclose event with code 1006 (abnormal closure). The connection is terminated. If the client library (e.g., Socket.IO) detects the failure, it may fall back to HTTP long polling. The server sees the HTTP upgrade request and can either explicitly reject with a 403 or just close the connection. There's no 'CORS error' message exposed to the client — it's silent from the application's perspective.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
What is WebSockets Explained in simple terms?
02
Why does a WebSocket handshake fail silently?
03
What headers are involved in a WebSocket CORS handshake?
04
Can I use a wildcard origin for WebSocket CORS?
N
Naren Founder & Principal Engineer

20+ years shipping production systems from the metal up. Written from production experience, not tutorials.

Follow
Verified
production tested
July 19, 2026
last updated
2,466
articles · all by Naren
🔥

That's . Mark it forged?

5 min read · try the examples if you haven't

Previous
Routing Protocols
1 / 1 ·
Next
REST vs SOAP vs GraphQL