Networking

Fix WebSocket Handshake Timeout in Chrome on Ubuntu 22.04

Chromium WebSocket handshake timeouts on Ubuntu 22.04: 'websocket connection failed' or 'opening handshake timed out' in Chrome/Brave, works in Firefox. Causes HTTP/2 Extended CONNECT (RFC 8441), IPv6, systemd-resolved. Diagnose with chrome-net-export, fixes included.

1 answer 1 view

WebSocket Handshake Timeout in Chrome and Brave on Ubuntu 22.04 (Works in Firefox)

Problem Description

WebSocket connections fail with handshake timeouts in Chrome and Brave browsers on Ubuntu 22.04, but work perfectly in Firefox on the same system.

Error in Chrome DevTools:

WebSocket connection to 'wss://web.whatsapp.com/ws/chat' failed: WebSocket opening handshake timed out

Affected Sites: WhatsApp Web, Zoom Web, and any sites using WebSockets.

Environment

  • OS: Ubuntu 22.04 LTS
  • Chrome: 142.0.7444.162
  • Brave: Latest stable
  • Firefox: Works normally
  • Network: Dual-stack IPv4/IPv6, using systemd-resolved

Troubleshooting Steps Tried

  1. Browser-level:
  • Incognito mode: same issue
  • New Chrome profile: same issue
  • Disabled extensions: same issue
  • Cleared caches: no change
  • Default Chrome flags
  • --no-sandbox: same issue
  1. Network-level:
  • No proxy (checked env vars and settings)
  • IPv6 working (ping6 google.com succeeds)
  • Disabled IPv6: issue persists
  • DNS changed to 8.8.8.8: no change
  • Flushed systemd-resolved caches: no change
  • No iptables blocks
  1. System-level:
  • No VPN/proxy running
  • No AppArmor denials
  • MTU: 1500

Key Observations

  • Firefox works on the same system
  • HTTPS requests fine in Chrome
  • Affects wss:// connections
  • Persists in Chromium-based browsers
  • curl WebSocket test returns HTTP/2 200 (not 101 Switching Protocols)

System Details

bash
# DNS
resolvectl status
Link 2 (wlo1)
 Current DNS Server: 192.168.1.1

# IPv6
ip -6 addr show wlo1
inet6 2401:xxxx:xxxx:xxxx:eff6:2678:deca:3f82/64 scope global dynamic

Question

What causes WebSocket handshake timeouts specifically in Chromium browsers (Chrome/Brave) on Ubuntu 22.04 with dual-stack networking, while Firefox works fine? Is this related to Chrome’s Happy Eyeballs, systemd-resolved, or another Linux/Chrome-specific issue?

Has anyone experienced similar WebSocket problems on Ubuntu where only Chromium browsers fail?

Additional: Chrome-net-export log available for diagnosis.

Chromium browsers reporting “WebSocket opening handshake timed out” or “websocket connection failed” on Ubuntu 22.04 most often point to Chromium using HTTP/2 Extended CONNECT (RFC 8441) while a proxy or server in the chain doesn’t support Extended CONNECT semantics — or to Chromium-specific resolver/IPv6 behavior interacting with a flaky system resolver (systemd-resolved) or a transient socket/cache state. Check your chrome-net-export for an HTTP/2 CONNECT with :protocol: websocket; if present, force HTTP/1.1 (or disable HTTP/2 on the proxy) to confirm the root cause and use the diagnostic steps below to isolate resolver, TLS client-cert or Chromium-local issues.


Contents


Chromium WebSocket handshake timeout — quick summary

Short answer: Chromium-based browsers (Chrome/Brave) will prefer an HTTP/2 path when ALPN/HTTP2 is negotiated. Firefox and some clients follow a different path, so they may succeed even when Chromium fails. If a proxy or load‑balancer that terminates HTTP/2 does not implement RFC 8441 (HTTP/2 Extended CONNECT) correctly, the browser’s attempt to bootstrap a websocket over HTTP/2 can stall and time out, producing the WebSocket opening handshake timed out or websocket connection failed messages you see in DevTools. Other contributors are Chromium’s resolver/Happy Eyeballs behavior with IPv6 and transient browser socket state; systemd-resolved hangs have been reported to cause Chromium-only delays.


Why Chromium shows “WebSocket opening handshake timed out” (HTTP/2 Extended CONNECT and RFC 8441)

HTTP/1.1 websockets use an Upgrade request followed by a 101 Switching Protocols response. HTTP/2 cannot use that exact mechanism (no connection-wide Upgrade/Connection or 101), so RFC 8441 defines an Extended CONNECT model for bootstrapping websocket-like tunnels over HTTP/2. If Chromium negotiates HTTP/2 and sends an Extended CONNECT (look for a request like:)

:method: CONNECT
:authority: example.com
:scheme: https
:path: /api/v4/websocket
:protocol: websocket

— the server (or any proxy between browser and backend) must understand and accept Extended CONNECT. If a proxy terminates HTTP/2 but doesn’t support RFC 8441, it can either return a plain HTTP/2 response that doesn’t actually open a websocket tunnel, or simply fail to pass the tunnel through. That mismatch leads Chromium to wait for a handshake that never arrives and then time out. The same symptom was reported on a Mattermost issue where Chrome/Firefox used HTTP/2 CONNECT and failed while Safari worked [https://github.com/mattermost/mattermost/issues/30285]. RFC text: [https://www.rfc-editor.org/rfc/rfc8441].

Your curl observation — “HTTP/2 200 (not 101)” — is informative: an HTTP/2 response code (2xx) or unexpected 200 can indicate the server/proxy handled the request as a normal HTTP/2 request rather than as an Extended CONNECT tunnel. That fits the HTTP/2 / RFC8441 mismatch hypothesis. See the RFC for the expected Extended CONNECT semantics: [https://www.rfc-editor.org/rfc/rfc8441].


IPv6, systemd-resolved and Chromium resolver differences

Could Happy Eyeballs / systemd-resolved be the culprit? Sometimes — but only part of the story. Chromium’s name-resolution and connection-establishment behavior differs from Firefox’s internals (async-dns, Happy Eyeballs timing, internal resolver caching). If systemd-resolved is slow or hangs on AAAA lookups, Chromium may attempt an IPv6 path that stalls while Firefox falls back to IPv4 sooner or behaves differently.

Relevant examples:

What to test:

  • Force IPv4 for a quick check: curl -4 -v https://your-host/... and force IPv6: curl -6 -v .... If forcing IPv4 fixes the handshake, DNS/IPv6 timing is a likely contributor.
  • Check resolvectl query <host> and journalctl -u systemd-resolved -b for errors. Restarting systemd-resolved has resolved similar issues in the field: sudo systemctl restart systemd-resolved.

Chrome-specific quirks and transient socket/cache state

Chromium sometimes enters a transient state where connection attempts to a particular host or endpoint stall until the browser is fully restarted or socket pools are flushed. Reports and issue threads show cases where websockets wouldn’t open until restart or an incognito window was used [https://stackoverflow.com/questions/62755998/websocket-does-not-connect-to-a-specific-host-until-chromium-is-restarted] and where Chrome silently closed a handshake when a client‑certificate selection would be required [https://github.com/beyondcode/laravel-websockets/issues/266].

Useful quick actions:

  • Flush socket pools in Chromium: open chrome://net-internals/#sockets and click “Flush socket pools” / “Close idle sockets”.
  • Try a full browser restart (not just the profile). If incognito fixes it but normal profile doesn’t, suspect profile/state-related socket caching.
  • If the server requests TLS client certificates on the websocket path, Chrome may cancel the handshake because it can’t prompt during the upgrade; check the server TLS config.

How to diagnose — chrome-net-export, tcpdump and server tests

You already have a chrome-net-export — great. Steps to extract the most useful signals:

  1. Collect the net log:
  • In Chrome/Brave open chrome://net-export/, click “Start Logging to Disk”, reproduce the failure, then stop and save the JSON file.
  1. Search the net log for WebSocket/CONNECT:
  • Look for :method: CONNECT or :protocol: websocket in the exported log (or open it with Chrome’s netlog viewer). If you see those, Chromium is attempting an HTTP/2 Extended CONNECT.
  1. Check ALPN / HTTP version negotiation:
  • From the client machine: openssl s_client -connect web.whatsapp.com:443 -servername web.whatsapp.com -alpn h2 — look for ALPN protocol: h2 to confirm HTTP/2 negotiation.
  • Use curl to test both transports:
  • Force HTTP/2: curl -v --http2 https://your-host/path
  • Force HTTP/1.1: curl -v --http1.1 -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Sec-WebSocket-Key: aGVsbG8=" -H "Sec-WebSocket-Version: 13" https://your-host/path
  • Note: curl’s raw headers may differ; the point is to see whether you get a 101 Switching Protocols under HTTP/1.1 vs a 200 or other under HTTP/2.
  1. Packet capture / ALPN visibility:
  • sudo tcpdump -i wlo1 host <server> and port 443 -w ws.pcap
  • Open the capture in Wireshark to confirm TLS ALPN (h2 vs http/1.1) in the ClientHello and ServerHello. You won’t see decrypted HTTP/2 frames without TLS keys, but ALPN indicates which protocol was negotiated.
  1. Server/proxy inspection:
  • Inspect your proxy (nginx, Traefik, load balancer) config — does it terminate HTTP/2? Does it claim to support Extended CONNECT? Many proxies don’t implement RFC8441; see community reports where proxies mishandled HTTP/2 CONNECT resulting in Chrome failures [https://github.com/mattermost/mattermost/issues/30285].
  1. Resolver checks:
  • resolvectl status and resolvectl query <host>
  • journalctl -u systemd-resolved -b --no-pager for recent resolved errors.

Collect these artifacts for root-cause: net-export JSON, tcpdump pcap, openssl ALPN output, server/proxy config, and systemd-resolved logs.


Fixes and workarounds for websocket connection to wss failed in Chromium

Short-term workarounds to get sites working while you fix the underlying chain:

  • Force the endpoint to use HTTP/1.1 (server/proxy): disable HTTP/2 on the ingress or configure the websocket route to be proxied with HTTP/1.1 Upgrade semantics. For nginx, remove http2 from the listen 443 ssl http2; line (or make websocket path bypass HTTP/2 termination). This forces the classic 101 Switching Protocols flow that Chromium and Firefox handle in the same way.
  • Upgrade / configure your proxy to support RFC 8441 if you need websocket over HTTP/2. Some load-balancers and old proxy versions don’t implement Extended CONNECT correctly; update or change config to pass CONNECT through properly.
  • If systemd-resolved is suspected: restart it and test again: sudo systemctl restart systemd-resolved and resolvectl flush-caches.
  • Try flushing Chromium socket pools (chrome://net-internals/#sockets → “Flush socket pools”) or fully restart the browser (and try a completely fresh user-data-dir).
  • Test with a browser launched disabling async-dns (older Chromium builds supported --disable-async-dns) to isolate internal resolver behavior; note this flag may be deprecated in recent releases.
  • If the server requests a client certificate on the websocket path, stop requesting client certs for that endpoint — Chrome may abort the handshake rather than prompt during the websocket bootstrap [https://github.com/beyondcode/laravel-websockets/issues/266].

Which fix you pick depends on the diagnostic evidence: if net-export shows HTTP/2 CONNECT → change proxy behavior; if ALPN shows HTTP/2 but no CONNECT attempt → check HTTP/2 handling at the proxy; if DNS/IPv6 delays are visible → focus on systemd-resolved / resolver configuration.


Quick checklist (step-by-step)

  1. Collect Chrome net log: chrome://net-export/ — reproduce, save JSON.
  2. Search the netlog for :method: CONNECT / :protocol: websocket.
  3. Check ALPN: openssl s_client -connect web.whatsapp.com:443 -servername web.whatsapp.com -alpn h2.
  4. Force HTTP/1.1 test: curl -v --http1.1 https://<host>/<ws-path> (or use npx wscat -c wss://...).
  5. Capture handshake-level traffic: sudo tcpdump -i wlo1 host <host> and port 443 -w ws.pcap.
  6. Restart systemd-resolved: sudo systemctl restart systemd-resolved and re-test.
  7. Flush Chromium sockets: open chrome://net-internals/#sockets → “Flush socket pools”.
  8. If server/proxy is under your control: temporarily disable HTTP/2 on ingress and re-test.
  9. If still failing, gather artifacts and proceed to file bug / server ticket.

When to file bug reports and what to attach

File browser bug reports (Chromium/Brave) or service tickets when diagnostics show Chromium-specific behavior after you’ve isolated server/proxy/resolver actions. Attach:

  • chrome-net-export JSON (from chrome://net-export/) — highlight timestamps and any :method: CONNECT / :protocol: websocket lines.
  • tcpdump pcap showing TLS ALPN negotiation (ClientHello/ServerHello).
  • openssl s_client ALPN output.
  • Server/proxy config snippet for the websocket endpoint (listen lines, http2 flags, proxy pass rules).
  • resolvectl status and systemd-resolved journal output if you suspect DNS.
  • Reproduction steps and exact DevTools error strings (copy/paste the WebSocket opening handshake timed out lines).

Chromium/Brave engineers or proxy vendors will want to see the netlog plus server config to determine whether the issue is a client-side implementation, a proxy misconfiguration, or an RFC8441 gap. Example community reports that map to this workflow are at Mattermost (HTTP/2 CONNECT problem) [https://github.com/mattermost/mattermost/issues/30285] and a websockets/ws thread with handshake timeouts [https://github.com/websockets/ws/issues/560].


Sources


Conclusion

Chromium WebSocket handshake timeout (the “WebSocket opening handshake timed out” / “websocket connection failed” symptom) on Ubuntu 22.04 is commonly caused by Chromium trying an HTTP/2 Extended CONNECT while a terminating proxy or load‑balancer doesn’t implement RFC 8441 correctly, or by Chromium’s resolver/IPv6 timing interacting badly with a hung systemd-resolved or transient socket state. Use chrome-net-export to confirm an HTTP/2 CONNECT, force HTTP/1.1 to verify, and then either update/configure the proxy to support Extended CONNECT or force websocket endpoints to use HTTP/1.1; restart or flush the browser’s socket pools and check systemd-resolved as additional steps. If you want, paste your chrome-net-export JSON here and I’ll point to the exact netlog lines that prove which path Chromium chose and which fix to try first.

Authors
Verified by moderation
Moderation
Fix WebSocket Handshake Timeout in Chrome on Ubuntu 22.04