seanmonstar/warp · error · MissingConnectionUpgrade

MissingConnectionUpgrade

Error message

MissingConnectionUpgrade

What it means

warp::ws() (src/filters/ws.rs:50) requires the request to include a Connection header whose value contains "upgrade"; otherwise it rejects with MissingConnectionUpgrade. WebSocket handshakes per RFC 6455 must carry Connection: Upgrade alongside Upgrade: websocket, so a request lacking this header cannot begin a WS session.

Solutions

  1. Connect using a real WebSocket client (browser WebSocket API, tokio-tungstenite, etc.) rather than plain HTTP — it sets Connection: Upgrade and Upgrade: websocket automatically
  2. Configure reverse proxies to forward Upgrade and Connection headers (e.g. nginx: proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade")
  3. Ensure the request uses GET, since ws() composes warp::get()
  4. Recover the rejection and return a clear 426 Upgrade Required response for misdirected plain-HTTP requests

Example fix

// before (nginx)
location /ws {
    proxy_pass http://backend;
}
// -> Connection header stripped -> MissingConnectionUpgrade

// after (nginx)
location /ws {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}
Defensive patterns

Strategy: validation

Validate before calling

// client-side: only open WS endpoints with a WebSocket client that sends upgrade headers
const isWsUrl = url.startsWith('ws://') || url.startsWith('wss://');
if (!isWsUrl) throw new Error(`use a WebSocket client for ${url}, not plain HTTP`);

Type guard

function isWebSocketReady(req) {
  return req.method === 'GET' &&
    /upgrade/i.test(req.headers.connection || '') &&
    /websocket/i.test(req.headers.upgrade || '');
}

Try / catch

let resp = ws_routes.recover(|rej: warp::Rejection| async move {
    if !rej.is_not_found() && rej.find::<warp::ws::MissingConnectionUpgrade>().is_some() {
        Ok(warp::reply::with_status("Upgrade Required", warp::http::StatusCode::UPGRADE_REQUIRED))
    } else { Err(rej) }
});

Prevention

When it happens

Trigger: A request to a warp::ws() route that omits the Connection: upgrade header — typically a plain HTTP request, a client library not setting the header, or an intermediary proxy stripping hop-by-hop Connection headers. Also triggered if the request is not a GET (ws() composes warp::get()).

Common situations: Testing the WS endpoint with curl or Postman without WS mode; reverse proxies (older nginx/HAProxy configs) dropping the Connection/Upgrade headers; misconfigured clients hitting the WS route with ordinary fetch/axios instead of a WebSocket client; missing proxy_read_headers / Upgrade forwarding settings.

Related errors


AI-assisted analysis of seanmonstar/warp@ff34d7213e (2026-09-09). Data as JSON: /api/errors/000997b3b10d949f. Report an issue: GitHub.

Appendix: source

Thrown at src/filters/ws.rs:50

/// - Header `connection` must be `upgrade`
/// - Header `upgrade` must be `websocket`
/// - Header `sec-websocket-version` must be `13`
/// - Header `sec-websocket-key` must be set.
///
/// If the filters are met, yields a `Ws`. Calling `Ws::on_upgrade` will
/// return a reply with:
///
/// - Status of `101 Switching Protocols`
/// - Header `connection: upgrade`
/// - Header `upgrade: websocket`
/// - Header `sec-websocket-accept` with the hash value of the received key.
pub fn ws() -> impl Filter<Extract = One<Ws>, Error = Rejection> + Copy {
    let connection_has_upgrade = header::header2()
        .and_then(|conn: ::headers::Connection| {
            if conn.contains("upgrade") {
                future::ok(())
            } else {
                future::err(crate::reject::known(MissingConnectionUpgrade))
            }
        })
        .untuple_one();

    crate::get()
        .and(connection_has_upgrade)
        .and(header::exact_ignore_case("upgrade", "websocket"))
        .and(header::exact("sec-websocket-version", "13"))
        //.and(header::exact2(Upgrade::websocket()))
        //.and(header::exact2(SecWebsocketVersion::V13))
        .and(header::header2::<SecWebsocketKey>())
        .and(on_upgrade())
        .map(
            move |key: SecWebsocketKey, on_upgrade: Option<OnUpgrade>| Ws {
                config: None,
                key,
                on_upgrade,
            },

View on GitHub (pinned to ff34d7213e)