Realtime TIMED_OUT and CHANNEL_ERROR

What the TIMED_OUT and CHANNEL_ERROR subscribe statuses mean, how to read the error reason, and how to fix token, private channel, network and rate limit causes.

What you see

Your subscribe callback gets one of these instead of SUBSCRIBED:

TIMED_OUT
CHANNEL_ERROR Error: <ErrorCode>: <message>

TIMED_OUT means the client sent a join request and got no reply in time (10 seconds by default). CHANNEL_ERROR means the server answered and refused the join, or the channel crashed afterwards. Always log the second argument; it carries the reason:

supabase.channel('invoice-1042').subscribe((status, err) => {
  if (status === 'CHANNEL_ERROR' || status === 'TIMED_OUT') {
    console.error(status, err)
  }
})

Why it happens

TIMED_OUT is about reachability: the WebSocket couldn't open, or it opened but the join never got through. Typical causes are a wrong project URL, a corporate proxy or browser extension blocking WebSockets, a laptop waking from sleep, or the server restarting.

CHANNEL_ERROR is about permission or limits. The reason string starts with a code, for example:

Reason starts with Meaning
InvalidJWTExpiration The access token expired before the join
MalformedJWT, JwtSignatureError The token isn't a valid JWT for this project
Unauthorized A private channel and no RLS policy lets this user read it
ChannelRateLimitReached Too many channels on one connection (see TooManyChannels)
ClientJoinRateLimitReached, ConnectionRateLimitReached Too many joins or connections at once
RealtimeRestarting The server is restarting; retry with backoff

How to fix it

  1. Check the URL and key. The client should point at your project URL, for example https://app-one.acme.supahost.space, with that project's anon key. A key from another project fails with a signature error.
  2. Refresh expired tokens. If the user's session expired while the tab slept, let supabase-js refresh it, then remove and recreate the channel. On a server, call supabase.realtime.setAuth(newToken) before joining.
  3. Add policies for private channels. A channel with config: { private: true } needs a select policy on realtime.messages for the user's role. See Realtime authorization.
  4. Back off on rate limit errors. Don't retry in a tight loop. Wait a few seconds, then try again, and fix the code that creates too many channels.
  5. Test the network. Open DevTools → Network → WS and look for the realtime/v1/websocket request. A failed handshake (no 101 status) points to a proxy, VPN or wrong host.
  6. Handle reconnects. supabase-js reconnects on its own after drops. After it does, refetch any data that may have changed while you were offline.

On SupaHost

Realtime runs on your instance, so it's briefly unavailable during a plan change (the VM resizes, about a minute or two) and possibly during software upgrades in your maintenance window, 03:00-05:00 UTC. Clients reconnect afterwards. If timeouts persist, check whether the other APIs on the same project still answer (for example a quick select through the Data API). SupaHost health-checks every instance each minute and emails the owner if it goes down.

The dashboard doesn't show Realtime server logs, so the client error reason is your main signal. See Debugging Realtime with logs and DevTools.

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us