HTTP/2 GOAWAY frames are the mechanism by which a server signals its intent to gracefully shut down an HTTP/2 connection, telling the client to stop sending new requests and that the server will finish processing existing ones.

Let’s watch this in action. Imagine a client making a few requests to a server.

-> PING frame (payload length 8, type 6)
<- SETTINGS frame (payload length 32, type 4)
<- HEADERS frame (stream 1, payload length 43, flags 0x01)
   :authority: example.com
   :method: GET
   :path: /
   :scheme: https
<- DATA frame (stream 1, payload length 1024)
<- HEADERS frame (stream 3, payload length 45, flags 0x01)
   :authority: example.com
   :method: GET
   :path: /items
   :scheme: https
<- DATA frame (stream 3, payload length 2048)
<- GOAWAY frame (stream 3, payload length 10, flags 0x00)
   Last-Stream-ID: 3
   Error-Code: NO_ERROR (0x00)

Here, the server sends a GOAWAY frame with Last-Stream-ID: 3 and Error-Code: NO_ERROR. This tells the client that it should not initiate any new streams with IDs greater than 3 (so, stream 5 and beyond are forbidden), and that streams 1 and 3 are still in progress and will be completed. The client, upon receiving this, will stop sending new requests and wait for streams 1 and 3 to finish.

The core problem HTTP/2 GOAWAY solves is preventing abrupt connection termination. Without it, a server wanting to shut down might just close the TCP connection, leaving active requests in limbo. This leads to incomplete operations, a poor user experience, and potential data loss. GOAWAY provides a structured handshake for this shutdown, ensuring all in-flight requests are either completed or explicitly canceled with an error code.

Internally, when a server decides to close a connection (e.g., during a rolling restart, load balancer rebalancing, or simply reaching a connection limit), it first determines the highest-numbered stream ID it has initiated or accepted on that connection. It then constructs a GOAWAY frame. The Last-Stream-ID field in this frame is crucial: it indicates the highest stream ID that the sender believes is still valid or could be processed. Any new streams initiated by the client with an ID greater than this Last-Stream-ID will be rejected. The Error-Code field communicates the reason for the shutdown. NO_ERROR is used for graceful shutdowns where no specific error occurred, while other codes like PROTOCOL_ERROR, INTERNAL_ERROR, or REQUEST_CANCELLED can be used to signal specific issues.

The client’s response to a GOAWAY is to cease opening new streams. It continues to process streams that were already established and for which it has received frames before the GOAWAY. If a stream ID in the GOAWAY frame is higher than any stream the client has initiated, it means the server might have lost track of streams it was tracking. The client will then close all streams it has initiated that have IDs greater than the Last-Stream-ID specified in the GOAWAY frame, and it will also close any streams it initiated with IDs less than or equal to the Last-Stream-ID if it hasn’t received a response for them yet. Crucially, the client should not immediately close the underlying TCP connection. It should wait for all active streams (those initiated before the GOAWAY and with IDs less than or equal to Last-Stream-ID) to be completed or explicitly terminated by the server. Only after all such streams are finished can the client safely close the TCP connection.

A common misconception is that GOAWAY means all streams are being terminated. In reality, it’s about preventing new streams and allowing in-progress streams to finish. The Last-Stream-ID is the key to understanding which streams are still considered "active" from the sender’s perspective. If a server sends GOAWAY with Last-Stream-ID: 0, it implies that no streams on this connection are considered valid, and the client should immediately stop all processing and close the connection.

The most surprising truth is that the Last-Stream-ID in a GOAWAY frame is determined by the sender’s highest active stream ID. If a server sends a GOAWAY with Last-Stream-ID: 5, it means it’s currently tracking or has initiated streams up to ID 5. The client should not initiate any new streams with IDs 7 or higher. It will continue processing streams 1, 3, and 5 (if they were initiated before the GOAWAY) and will close any streams it initiated with IDs greater than 5.

When a client receives a GOAWAY frame, it should initiate a process to close the connection gracefully. This involves finishing any ongoing requests that were initiated before the GOAWAY frame was received, and then closing the underlying TCP connection.

Want structured learning?

Take the full Http2 course →