skip to content

In the WebSocket protocol, which close status codes must never appear in a Close frame on the wire, and why do they exist?

level: middleimportance: should knowfreq 50%

answer

  1. two namespaces, not one list
  2. some codes never travel
  3. reported locally, never sent
  4. an empty body versus no frame
  5. 1005, 1006 and 1015

basics

~20 s

Codes 1005, 1006 and 1015 are reserved and must never be sent in a Close frame. They exist only so a local client API can report a Close frame with no status, a connection that ended with no Close frame at all, or a failed TLS handshake.

solid answer

~50 s

Close status codes live in two namespaces that look like one. Most are *wire* codes an endpoint puts in a Close frame — `1000`, `1001`, `1002`, `1011` and friends. Three are **reserved** and must never be set in a Close frame: `1005` (no status code was present), `1006` (the connection closed abnormally, with no Close frame sent or received) and `1015` (the TLS handshake failed). They exist so that a receiving application has a value to report when there was nothing on the wire to report. That is why "what did the peer send as `1006`?" is a trick question: nothing. `1006` is synthesised locally from an *absence*, which is also why an abnormal closure carries no reason text — the reason would have lived in the Close frame body, and no Close frame arrived.

go deeper

for a junior

Know that a handful of close codes are reserved and never travel on the wire; if you see one, no peer chose it.

for a middle

Explain the split precisely: 1005 means a Close frame arrived with an empty body, 1006 means no Close frame arrived at all, and neither may ever be sent.

for a senior

Use the distinction operationally — a rising rate of abnormal closures against steady clean ones is the alertable signal, and a reserved code is the start of an investigation, not its conclusion.

for a principal

Decide what your fleet records: close code plus which endpoint sent it, so clean closes stay attributable and abnormal ones can be separated from deliberate drains across teams.

## Two namespaces that look like one WebSocket close status codes are 2-byte unsigned integers, and every example you meet is in the 1000s, so they read like one flat list. They are not. The range splits into codes that travel and codes that never do. - **Wire codes** are put in a Close frame's body by an endpoint that is choosing to close and wants to say why. `1000` normal closure, `1001` going away, `1002` protocol error and `1011` unexpected condition on the server are the general-purpose ones. - **Reserved codes** must not be set as a status code in a Close control frame by any endpoint. They are *report* values: something a local API hands the application to describe a situation in which no status arrived. An endpoint that put a reserved code into a frame would be violating the specification, and a peer would be entitled to treat it as a protocol error. ## The three reserved codes | Code | What it reports | What was on the wire | |---|---|---| | `1005` | No status code was present | A Close frame **did** arrive, with an empty body | | `1006` | The connection closed abnormally | **No Close frame** was sent or received at all | | `1015` | The TLS handshake failed | No WebSocket connection was ever established | The distinction between the first two is the one that separates a candidate who has read the specification from one who has only seen the codes in a log. `1005` means a Close frame arrived and simply said nothing. `1006` means the closing handshake never happened. ## Why 1006 is a trick question Ask "the peer closed with `1006` — what reason did it give?" and a weak answer starts guessing at causes. The correct answer is that the peer gave nothing, because the peer sent no Close frame. `1006` is the application's own record of an absence: 1. Something ended the connection without a closing handshake — a transport reset, a process that died mid-write, an intermediary that dropped the flow's state, a path that went away. 2. There is no Close frame, so there is no body. 3. There is no body, so there is no status code and no reason string. 4. The receiving side still has to tell the application *something*, so it reports the reserved value that means exactly "this ended abnormally". The absence of a reason string is not a gap in the API. It is the honest consequence of there being no frame to read one from. ## What you actually learn from an abnormal close A `1006` is a fact about the *ending*, not a diagnosis of the cause. It tells you the closing handshake did not happen and nothing more, so treat it as the start of an investigation: - It does **not** distinguish a peer that crashed from a path that was cut; both produce the same value. - It does **not** mean the peer is at fault — an intermediary between you may have dropped a connection that both endpoints believed was healthy. - A rising rate of `1006` against a steady rate of `1000` and `1001` is the signal worth alerting on, because clean closes are the ones you can attribute. - If your own endpoint can end connections cleanly during shutdown, every `1006` left in the data is one you did not cause, which makes the number worth far more. ## Reading a close code correctly When you see a close code in a log, ask two questions in order. First: **is this a wire code or a reserved one?** If it is reserved, no peer chose it and there is no point looking for intent behind it. Second: **if it is a wire code, which side sent it?** `1011` from your server and `1011` from the peer are opposite findings, and the code alone does not say which endpoint put it in the frame. Record the direction alongside the code, or half of your close-code data will be unattributable.

  • Why does an abnormal WebSocket closure never carry a reason string?
    Because the reason lives in the Close frame's body, after the two status bytes, and in an abnormal closure no Close frame was received. The code is synthesised locally from the absence of a frame, so there is nothing to read a reason out of. Any explanatory text you see beside it came from the application, not the peer.
  • What is the difference between reporting 1005 and reporting 1006?
    `1005` means a Close frame did arrive but its body was empty, so no status was supplied — the closing handshake happened. `1006` means no Close frame arrived at all, so the handshake never happened. One is a clean close with nothing said; the other is an unclean ending.

saying these in an interview costs you the question

  • Says the peer sent 1006 to signal that it dropped the connection.
  • Puts a reserved code into an outgoing Close frame.
  • Reads 1006 as proof the peer crashed rather than that no Close arrived.
  • Expects a reason string alongside an abnormal closure.
  • Treats 1005 and 1006 as the same situation.