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
- Check the URL and key. The client should point at your project URL, for example
https://app-one.acme.supahost.space, with that project'sanonkey. A key from another project fails with a signature error. - 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. - Add policies for private channels. A channel with
config: { private: true }needs aselectpolicy onrealtime.messagesfor the user's role. See Realtime authorization. - 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.
- Test the network. Open DevTools → Network → WS and look for the
realtime/v1/websocketrequest. A failed handshake (no101status) points to a proxy, VPN or wrong host. - 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.