# Connectivity and security (/connectors/stomp/how-to/connectivity-and-security)



The KubeMQ STOMP connector is an &#x2A;*embedded STOMP server inside `kubemq-server`** with its own
dedicated listeners — plain TCP **61613** (default) and TLS **61614** (default). An existing STOMP
application connects to it by changing only the broker address. This guide covers the connection URL
convention, the CONNECT handshake, heartbeats, TLS, and the connection limit.

## `KUBEMQ_STOMP_URL` — the connection convention [#kubemq_stomp_url--the-connection-convention]

The connector reads `KUBEMQ_STOMP_URL` (default `tcp://localhost:61613`) and dials it. &#x2A;*The URL
scheme selects the transport:**

```bash
export KUBEMQ_STOMP_URL="tcp://localhost:61613"        # default — plain TCP listener (port 61613)
# export KUBEMQ_STOMP_URL="tls://kubemq.example:61614" # TLS listener (port 61614, see below)
```

| Scheme   | Transport | Listener (default port)                                            |
| -------- | --------- | ------------------------------------------------------------------ |
| `tcp://` | plain TCP | 61613                                                              |
| `tls://` | TLS       | 61614 (active only when the server Security block resolves to TLS) |

The connector **binds all interfaces** (`:<port>`), not just localhost. Both ports are configurable
via the connector configuration.

<Callout type="info">
  **Verify the listener — do not infer it from a successful server boot.** The connector loader is
  availability-first: a bind failure logs a warning and the server keeps running **without** STOMP.
  Confirm the listener is up via the `/stomp` web dashboard, the `kubemq_stomp_connections` Prometheus
  gauge, or `GET /api/stomp/connections`.
</Callout>

## The CONNECT handshake [#the-connect-handshake]

Every connection opens with the same CONNECT frame and reads the negotiated `CONNECTED` headers
back:

```text
CONNECT
accept-version:1.2          # highest common of 1.0/1.1/1.2; absent/empty → 1.0
heart-beat:10000,10000      # client cx,cy in ms; server advertises sx=sy=HeartbeatMs (default 10000)
login:my-app                # freeform (session id fallback when no JWT claims)
passcode:                   # empty/any in the default no-auth mode

^@
```

The `CONNECTED` reply carries:

| Header       | Value                                                                                               |
| ------------ | --------------------------------------------------------------------------------------------------- |
| `version`    | the negotiated version (`1.0` / `1.1` / `1.2`)                                                      |
| `heart-beat` | the **server's advertised** `sx,sy` (both = `HeartbeatMs`), **not** the negotiated effective values |
| `session`    | the derived client id (see [Authentication](/connectors/stomp/how-to/authentication))               |
| `server`     | `KubeMQ/<version>`                                                                                  |

`CONNECTED` headers are **never escaped** (handshake exemption). The handshake order is: version
negotiation → broker-ready gate → max-connections check → auth → derive client id → heartbeat
negotiation → send `CONNECTED`.

<Callout type="info">
  **`accept-version:1.2` is recommended.** See [Protocol versions](/connectors/stomp/how-to/protocol-versions)
  for the negotiation rules and the per-version feature matrix.
</Callout>

## Heartbeats and the 2x dead-peer cutoff [#heartbeats-and-the-2x-dead-peer-cutoff]

Heartbeats keep the TCP connection alive and let the connector detect a dead peer.

* The client sends `heart-beat:cx,cy` (ms). The server advertises `sx = sy = HeartbeatMs` (default
  **10000**; `0` disables the server side).
* **Effective client→server interval = `maxNonZero(cx, sy)`**; &#x2A;*server→client = `maxNonZero(sx,
  cy)`** — where `maxNonZero(a, b)` is `0` if **either** side is `0` (that direction is disabled),
  otherwise `max(a, b)`.
* Parsing is lenient: a missing, non-numeric, negative, or 3-field value becomes `0,0`.

<Callout type="warn">
  **The dead-peer cutoff is 2x the negotiated client→server interval.** The watchdog force-closes the
  connection (audited as `client.timeout`) if no inbound bytes arrive within that window. &#x2A;*Any inbound
  byte — a real frame or a bare `\n` heartbeat — refreshes liveness.** The server sends a heartbeat LF
  only when it has written nothing for at least half the server→client interval.
</Callout>

**Recommendation:** send `heart-beat:10000,10000` (matches the server default). With a 10 s
client→server interval the cutoff is **20 s** — a client must send a frame or a bare `\n` before that
window elapses.

## TLS [#tls]

<Callout type="info">
  **TLS is a transport swap — the STOMP protocol on top is unchanged.** Point `KUBEMQ_STOMP_URL` at
  `tls://host:61614` and configure the server Security block; the STOMP frames are identical.
</Callout>

```bash
export KUBEMQ_STOMP_URL="tls://kubemq.example.com:61614"
```

* The TLS listener is the default **port 61614** (`tls://...`).
* It is active **only** when the TLS port is configured **and** the **server-wide `Security` block**
  resolves to non-nil TLS. If the Security mode is `none`, the TLS port is **silently skipped**.
* **TLS has no STOMP-specific config.** The certificate material, mTLS, and minimum version all come
  from the server-wide `Security` block — the connector owns only *whether the TLS port is open*.
  mTLS requires and verifies the client certificate; the minimum is TLS 1.2.

For the shared KubeMQ JWT model and TLS/mTLS concepts across connectors, see
[Auth & security](/connectors/reference/auth-and-security).

## WebSocket is not supported in V1 [#websocket-is-not-supported-in-v1]

<Callout type="warn">
  **The connector is raw TCP only.** STOMP-over-WebSocket is a documented **future** listener path, not
  a current feature. The popular browser/Node client `@stomp/stompjs` speaks STOMP **only over
  WebSocket**, so it **cannot** connect to the raw-TCP listener directly. The JavaScript/TypeScript
  examples therefore use `stompit` (raw TCP).
</Callout>

## Connection limit [#connection-limit]

The connector enforces a maximum connection count (default **1000**; `0` = unlimited). The slot is
**counted at accept** — a raw TCP socket that never sends CONNECT still consumes a slot — but the
over-limit `ERROR "connection limit reached"` is **deferred to the handshake** so the client receives
a proper frame.

## Errors are terminal [#errors-are-terminal]

Every protocol error produces an `ERROR` frame followed by a socket close. The message is
**sanitized** — internal error text never crosses the wire. Treat every `ERROR` frame as terminal in
client code, and reconnect if appropriate.

## Related [#related]

<Cards>
  <Card title="Authentication" href="/connectors/stomp/how-to/authentication" description="The login/passcode part of the CONNECT frame — the KubeMQ JWT, derived session ids, and Casbin authorization." />

  <Card title="Protocol versions" href="/connectors/stomp/how-to/protocol-versions" description="Version negotiation and the 1.0/1.1/1.2 feature matrix — escaping, ack tokens, and subscription ids." />

  <Card title="Auth & security" href="/connectors/reference/auth-and-security" description="The shared KubeMQ JWT model and TLS/mTLS concepts across connectors." />
</Cards>
