In this episode we'll upgrade the protocol from HTTP/1.1 to HTTP/2 and QUIC-based HTTP/3, understand multiplexing and fallback behavior, then explore two-way WebSocket communication with the WS client in curl.

So far, curl has been talking to servers over HTTP/1.1 — a protocol three decades old, designed before the web became as complex as it is now. In episode 14 you'll shift up a gear: HTTP/2, which lets many requests share one connection, HTTP/3, flying over UDP with lower latency, and WebSocket, which turns curl from a one-way caller into a participant in two-way conversations. Nothing needs to be relearned — URL syntax and headers stay the same. What changes is how data flows inside.
HTTP/1.1 has one structural weakness: a single TCP connection serves one request at a time. Every request waits for its response to complete before the next request departs — the so-called head-of-line blocking. Browsers work around this by opening many parallel connections (up to 6 per host), and curl mimics that by default. But that's just a patch: extra connections mean extra handshakes, and it doesn't solve the queue inside a single connection.
Imagine a restaurant with one waiter per table who can only carry one plate per trip. The HTTP/1.1 solution is adding waiters; the HTTP/2 solution is replacing that waiter with one who can carry a full trolley at once.
--http2HTTP/2 solves the problem above with multiplexing: one TCP connection carries many streams at once, and each stream can carry its own request-response in parallel. One slow packet no longer holds back others in different streams.
Since curl 8.x, HTTP/2 is already the default for HTTPS connections — no flag needed. But to assert it, or use it in an environment with ambiguous configuration, use --http2:
curl --http2 https://api.example.com/healthHow do you know a request is really using HTTP/2? Check with -v — lines starting with * will show the negotiation result:
* ALPN: offers h2,http/1.1
* ALPN: server accepted h2
> GET /health HTTP/2
> Host: api.example.comALPN: server accepted h2 and the request line mentioning HTTP/2 are proof that the protocol negotiation (which you saw in episode 12) chose HTTP/2. If the server doesn't support HTTP/2, curl falls back to HTTP/1.1 seamlessly — nothing fails, it's just slower.
Tip
To force a protocol for testing, you can lock it: curl --http2 https://... uses HTTP/2 and refuses downgrades, while curl --http1.1 https://... forces back to HTTP/1.1. Being able to compare the two is useful when debugging behavior that depends on the protocol version.
--http3HTTP/3 is the next generation, running on QUIC — a transport protocol built on UDP, not TCP. Its two main advantages:
curl --http3 https://example.comcurl --http3-only https://example.comNotice the difference in behavior: --http3 tries HTTP/3 then falls back to HTTP/2 or HTTP/1.1 if the server doesn't support it — the wisest choice for production. Meanwhile, --http3-only forces it and fails if the server is HTTP/3-blind — useful only for testing that a server truly supports it.
curl -v --http3 https://example.com 2>&1 | grep -i "QUIC\|HTTP/3"HTTP/3 support requires a curl build including the HTTP3 feature — check with curl --version. Servers behind modern CDNs (Cloudflare, Fastly, and similar) generally already support it. If you want to see the difference with your own eyes, compare the timing of --http3 against --http1.1 with -w from episode 6 — on healthy connections, HTTP/3 often wins slightly on time_connect.
--wsSo far all protocols are request-response: one request, one answer, connection done. WebSocket is different — after the initial handshake it opens a persistent two-way connection, and both parties can send messages to each other at any time without waiting their turn.
How it works: the initial request is regular HTTP with the Upgrade: websocket header. If the server agrees, the connection is upgraded and framed conversation begins — short messages exchanged in real time.
curl 8.x supports WebSocket with the --ws flag:
curl --ws wss://stream.example.com/liveRun that command and you enter interactive mode: whatever you type is sent as a message, and incoming messages appear on screen in real time until the connection closes. For automated testing, you can feed a single message through stdin — curl sends it, displays the reply, then closes the connection:
echo '{"type": "ping"}' | curl --ws wss://stream.example.com/liveNote the URL schemes: ws:// for unencrypted and wss:// for WebSocket over TLS — always choose wss:// except in a local lab.
Warning
WebSocket support in curl is still labeled experimental — make sure curl --version shows the websockets feature before using it, and test in a non-production environment. WebSocket connections are also persistent and can run long, so set --max-time so debugging sessions don't hang.
These three protocols aren't rivals — they complement each other:
A simple rule of thumb: use the default (--http2 via ALPN) for normal work, enable --http3 when both server and build support it, and don't touch --ws unless you truly need two-way. Forcing an unnecessary protocol is premature optimization — and using the wrong one is an exercise in waste.
Episode 14 shifts up your protocol gear: understanding HTTP/1.1's limitations and the birth of HTTP/2 with multiplexing (--http2), moving to QUIC-based HTTP/3 with lower latency (--http3 with automatic fallback, --http3-only for testing), verifying negotiation through -v, and opening two-way conversations with WebSocket (--ws) for real-time testing.
The core thing to remember: modern protocols are about reducing waiting time and enabling what was previously impossible. HTTP/2 and HTTP/3 shrink transfer overhead; WebSocket opens the door to real-time applications. It's all still curl — the same URLs, the same headers, only the flow changes.
In the next episode 15, we'll harness all these capabilities for one goal: speed — parallel transfers, multiplexing, compression, and how to measure performance for real. See you!