# 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.

Source: https://supahost.app/kb/realtime-timed-out · Updated 2026-11-11

## What you see

Your `subscribe` callback gets one of these instead of `SUBSCRIBED`:

```text
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:

```js
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](/kb/realtime-too-many-channels)) |
| `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](/docs/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](/kb/realtime-debugging).

## Related

- [Realtime authorization](/docs/realtime/authorization)
- [Debugging Realtime with logs and DevTools](/kb/realtime-debugging)
- [Silent disconnects when the app is in the background](/kb/realtime-background-disconnects)
- [Sessions, JWTs and refresh tokens](/docs/auth/sessions)
