Fixed: Laravel Echo + Pusher "Connection Failed" Error

The problem: Laravel Echo throws a connection failed state in the browser console, and no broadcast events reach the frontend even though the backend fires them successfully.

Environment: Laravel 11, Laravel Echo 1.x, Pusher JS 8.x, laravel-echo + pusher-js via npm

The Error

● ● ●  console
Pusher : : ["State changed", {"previous":"connecting","current":"failed"}]
Pusher is not defined
GET https://sockjs-us2.pusher.com/... net::ERR_CONNECTION_TIMED_OUT

Why It Happens

A "failed" state (not "unavailable", not "disconnected") specifically means the WebSocket handshake never even completed — Echo/Pusher couldn't establish a connection at all. This narrows down to a small set of causes:

1. Wrong or missing cluster value. Pusher requires the correct cluster (e.g. ap2, mt1) matching the region your Pusher app was created in — a mismatched or missing cluster means the JS client tries to connect to the wrong regional endpoint entirely.

2. window.Pusher never got attached. Since Laravel 11 scaffolding removed auto-imports for Echo, it's common to configure echo.js correctly but forget to explicitly assign window.Pusher = Pusher before instantiating Echo — without it, Echo's internal broadcaster can't find the Pusher client at all.

3. Firewall/corporate network blocking WebSocket ports. Pusher's WebSocket transport uses port 443 but falls back to SockJS over HTTP long-polling on restrictive networks — if that fallback is also blocked, the connection times out completely rather than degrading gracefully.

The Fix

First, confirm your resources/js/echo.js (or bootstrap.js) explicitly imports and attaches Pusher before creating the Echo instance:

resources/js/echo.js
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: import.meta.env.VITE_PUSHER_APP_KEY,
    cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
    forceTLS: true,
});

Then double-check your .env values match exactly what's shown in your Pusher dashboard — the cluster is the most commonly mismatched field:

.env
PUSHER_APP_ID=1234567
PUSHER_APP_KEY=abcdef123456
PUSHER_APP_SECRET=your-secret
PUSHER_APP_CLUSTER=ap2

VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

Rebuild after any .env change, since Vite bakes these values in at build time, not runtime:

npm run build

If the cluster and key are both confirmed correct but it still fails specifically on a corporate or restricted network, test on a different network (mobile hotspot) to rule out a firewall blocking WebSocket/SockJS traffic entirely.

Step-by-Step

  1. Open your Pusher dashboard and copy the exact App Key and Cluster values.
  2. Update .env with matching PUSHER_APP_CLUSTER and confirm the VITE_-prefixed mirror variables are also set.
  3. In echo.js/bootstrap.js, confirm window.Pusher = Pusher is set before the new Echo({...}) call.
  4. Run npm run build (env changes require a rebuild, not just a dev server restart).
  5. Open the browser console and watch for the Pusher connection state change from "connecting" to "connected" (not "failed").
  6. If it still fails, test from a different network to rule out a firewall or corporate proxy blocking WebSocket ports.

Edge case: If you're testing locally over plain HTTP (not HTTPS) with forceTLS: true set, some browsers will silently block the mixed-content WebSocket upgrade without a clear console error — set forceTLS: false only for local HTTP development, and always true in production.

Still stuck with a failed connection after checking all this? Reach out on the Contact page and I'll help debug your Pusher setup.

Advertisement
Advertisement