Packages

A Pusher-compatible WebSocket server for Elixir, inspired by Laravel Reverb. Run it as a Docker image or embed it in your Elixir app.

Current section

Files

Jump to
socketelx guides configuration.md
Raw

guides/configuration.md

# Configuration

Every setting can be passed as an option to `Socketelx.start_link/1` (or as
`{Socketelx, opts}` in a supervision tree) or set as an environment
variable. An explicit option wins over the environment variable.

Environment variables are named `SOCKETELX_*`. The older `PUSHER_*` spelling of the
same names (`PUSHER_APP_ID`, `PUSHER_REDIS_URL`, ...) is still accepted, so an
existing deployment keeps working; if both are set, `SOCKETELX_*` wins.

| Option                     | Env var                         | Default            | Notes |
|----------------------------|---------------------------------|--------------------|-------|
| `:app_id`                  | `SOCKETELX_APP_ID`                 | `"app-id"`         | Must match what your backend's Pusher library uses. Also namespaces Redis keys. |
| `:app_key`                 | `SOCKETELX_APP_KEY`                | `"app-key"`        | Public; used in the WebSocket URL `/app/:app_key`. |
| `:app_secret`              | `SOCKETELX_APP_SECRET`             | `"app-secret"`     | Keep private. The default is public and logs a warning. |
| `:port`                    | `PORT`                          | `6001`             | Plain HTTP/WS. Put TLS in front. |
| `:webhook_url`             | `SOCKETELX_WEBHOOK_URL`            | none               | Where webhooks are POSTed. |
| `:client_messages_enabled` | `SOCKETELX_ENABLE_CLIENT_MESSAGES` | `true`             | `false` rejects all `client-*` events. |
| `:redis_url`               | `SOCKETELX_REDIS_URL`              | none               | Enables multi-server mode. `rediss://` for TLS. |
| `:redis_opts`              | none                            | `[]`               | Extra [Redix](https://hexdocs.pm/redix) options, e.g. `ssl: true`. |
| `:webhook_retries`         | `SOCKETELX_WEBHOOK_RETRIES`        | `3`                | Retries after the first attempt. `0` turns retries off. |
| `:webhook_retry_base_ms`   | none                            | `1000`             | First retry delay; doubles each time. |
| `:rest_rate_limit`         | `SOCKETELX_REST_RATE_LIMIT`        | none               | Max REST API requests per second for the instance. |
| `:client_event_rate_limit` | `SOCKETELX_CLIENT_EVENT_RATE_LIMIT`| none               | Max client events per second, per connection. |
| `:max_connections`         | `SOCKETELX_MAX_CONNECTIONS`        | none               | Max WebSocket connections on this node. |
| `:batch_limit`             | `SOCKETELX_BATCH_LIMIT`            | `10`               | Max events in one `batch_events` request. Pusher's own limit is 10. |
| `:metrics`                 | `SOCKETELX_METRICS`                | `false`            | Serve Prometheus metrics at `GET /metrics`. |
| `:metrics_token`           | `SOCKETELX_METRICS_TOKEN`          | none               | Require `Authorization: Bearer <token>` on `/metrics`. |
| `:cluster`                 | `SOCKETELX_CLUSTER`                | none               | `erlang` turns on the experimental Redis-free cluster mode. Not combinable with `:redis_url`. See [Multi-server](multi-server.md). |
| `:cluster_nodes`           | `SOCKETELX_CLUSTER_NODES`          | none               | Comma-separated Erlang node names to connect to (`socketelx@host1,socketelx@host2`). Optional if your app already forms a cluster. |
| `:cluster_dns`             | `SOCKETELX_CLUSTER_DNS`            | none               | A hostname that resolves to every node's IP; nodes are named `<basename>@<ip>`. |
| `:cluster_node_basename`   | `SOCKETELX_CLUSTER_NODE_BASENAME`  | `socketelx`        | The part before the `@` used with `:cluster_dns`. |
| `:name`                    | none                            | `Socketelx`     | Embedded only. Unique per instance. |

In Docker, cluster mode also uses the release's own variables, which are not
Socketelx settings: `RELEASE_COOKIE` (shared secret, required in practice),
`RELEASE_NODE` and `RELEASE_DISTRIBUTION`. See [Multi-server](multi-server.md).

| `:allowed_origins`         | `SOCKETELX_ALLOWED_ORIGINS`        | all origins        | Which websites may open a WebSocket. See [Allowed origins](#allowed-origins). |
| `:max_connections_per_ip`  | `SOCKETELX_MAX_CONNECTIONS_PER_IP` | none               | Simultaneous WebSocket connections per client IP. See [Per-IP limit](#per-ip-limit). |
| `:client_ip_header`        | `SOCKETELX_CLIENT_IP_HEADER`       | none               | Take the client IP from this header (behind a proxy). |
| `:webhook_queue_path`      | `SOCKETELX_WEBHOOK_QUEUE_PATH`     | none               | Directory where failed webhooks wait, so they survive a restart. |
| `:tls_certfile`, `:tls_keyfile` | `SOCKETELX_TLS_CERTFILE`, `SOCKETELX_TLS_KEYFILE` | none | Serve HTTPS and WSS directly, without a proxy. |
| `:apps`                    | `SOCKETELX_APPS`                   | none               | Several apps in one server. See [Several apps](multi-app.md). |

## One app, or several

By default a server has one app (one id, key and secret). To serve several apps
from one port, see [Several apps in one server](multi-app.md).

## Allowed origins

Browsers send an `Origin` header when opening a WebSocket. By default every
origin is accepted. To accept only your own sites:

```bash
SOCKETELX_ALLOWED_ORIGINS=https://chat.example.com,*.example.org
```

Each entry is one of:

| Entry | Matches |
|---|---|
| `https://chat.example.com` | exactly that origin (scheme and port included) |
| `chat.example.com` | that host on any scheme or port |
| `*.example.org` | any subdomain of `example.org`, but not `example.org` itself |
| `*` | everything (the default) |

A refused connection receives `pusher:error` 4009 and is closed. Connections
without an `Origin` header (backend scripts, mobile apps) are not affected: this
stops other websites from using your visitors' browsers, it is not a login. In
Elixir, pass a list: `allowed_origins: ["https://chat.example.com"]`.

## Per-IP limit

`SOCKETELX_MAX_CONNECTIONS_PER_IP=20` allows each client address at most 20
simultaneous WebSocket connections (more get `pusher:error` 4100). Behind a
proxy or tunnel every connection looks like it comes from the proxy, so also set
`SOCKETELX_CLIENT_IP_HEADER` to the header your proxy fills in with the real
address (`cf-connecting-ip` on Cloudflare, `x-forwarded-for` for most others;
the first address in a list is used). Only set it when the proxy overwrites that
header, otherwise clients can fake it.

## Built-in TLS

Set `SOCKETELX_TLS_CERTFILE` and `SOCKETELX_TLS_KEYFILE` (PEM files) and the
server speaks HTTPS and WSS on its port. Most deployments terminate TLS at a
proxy or Cloudflare instead, which is fine; use this when nothing sits in front.
Certificates are read at start, so restart to renew.

## User sign-in and events to one user

`pusher.signin()` in `pusher-js` identifies the person behind a connection.
Point `userAuthentication.endpoint` at a route that calls
`Socketelx.authenticate_user/4` (or signs `socket_id::user::user_data` with
HMAC-SHA256 in any language). Once signed in, an event triggered on the channel
`#server-to-user-<id>` reaches every connection of that user, on any node.

## Client events

Clients can send `client-*` events on private and presence channels, only to
channels they are subscribed to, and never on public channels. Set
`SOCKETELX_ENABLE_CLIENT_MESSAGES=false` to refuse them all (the client receives
error 4301).

## Webhooks

With `SOCKETELX_WEBHOOK_URL` set, the server POSTs JSON for these events:

- `channel_occupied`, `channel_vacated`
- `member_added`, `member_removed` (presence channels)
- `client_event`

Each request carries `X-Pusher-Key` and `X-Pusher-Signature` (HMAC-SHA256 of the
raw body with your app secret), the same as Pusher's webhooks. Verify the
signature before trusting a request.

Delivery runs in the background and never blocks a connection. A failed
delivery (network error, timeout, HTTP 5xx, 408 or 429) is retried up to
`SOCKETELX_WEBHOOK_RETRIES` times (default 3) with a doubling delay: 1 s, 2 s,
4 s. Other 4xx responses are not retried, because the receiver rejected the
payload. Two things to design for:

- By default retries are held in memory, so a webhook still waiting for a retry
  is lost if the node stops. Set `SOCKETELX_WEBHOOK_QUEUE_PATH` to a directory
  (a mounted volume in Docker) and a webhook that failed its first attempt is
  saved there and sent again after a restart. It protects against the node
  restarting, not against losing the disk.
- Retried events can arrive after later events, so make your receiver
  idempotent and don't rely on order.

## Rate limits and capacity

All are off by default.

- `SOCKETELX_REST_RATE_LIMIT=100` allows 100 REST API requests per second for the
  whole instance (the health check and `/metrics` are exempt). Extra requests
  get `429` with `Retry-After: 1`. Most server libraries treat that as a failed
  publish, so set it above your real peak.
- `SOCKETELX_CLIENT_EVENT_RATE_LIMIT=10` allows each connection 10 client events
  per second. Extra events are dropped and the client gets `pusher:error` 4301.
- `SOCKETELX_MAX_CONNECTIONS=10000` refuses new WebSocket connections on this
  node beyond that count with `pusher:error` 4004. Clients reconnect, and with
  a load balancer they may land on another node.

The limits are per instance and per node. With several nodes, each node
applies its own limit.

## Metrics

Set `SOCKETELX_METRICS=true` and scrape `GET /metrics` (Prometheus text format).
Counters are per node and reset on restart; scrape every node.

| Metric | Type |
|---|---|
| `socketelx_connections` | gauge: open WebSocket connections on this node |
| `socketelx_channels` | gauge: channels with a subscriber on this node |
| `socketelx_connections_total`, `socketelx_connections_rejected_total` | counter |
| `socketelx_subscriptions_total` | counter |
| `socketelx_events_triggered_total` | counter: events delivered via the REST API, one per channel |
| `socketelx_client_events_total`, `socketelx_client_events_rate_limited_total` | counter |
| `socketelx_rest_requests_total`, `socketelx_rest_rate_limited_total` | counter |
| `socketelx_webhooks_sent_total`, `socketelx_webhook_retries_total`, `socketelx_webhooks_failed_total` | counter |

Set `SOCKETELX_METRICS_TOKEN` to require `Authorization: Bearer <token>`. Without
a token anyone who can reach the port can read `/metrics`, so block that path
at your proxy.

## Redis

See [Multi-server](multi-server.md). Redis is used only when `:redis_url` is set.