-
Notifications
You must be signed in to change notification settings - Fork 0
HTTP QUIC a WebSocket
Verze HTTP jsou téma, kde je hodně marketingu a málo rozdílu v tom, co musíš doopravdy nastavit. Tahle stránka se drží toho druhého: co se mezi verzemi změnilo, co z toho poznáš na svém serveru a co je dneska mrtvé, i když to návody pořád doporučují.
Navazuje na porty a spojení a TLS, nastavení konkrétních proxy je v reverse proxy.
| HTTP/1.1 | HTTP/2 | HTTP/3 | |
|---|---|---|---|
| Rok | 1997 | 2015 | 2022 |
| Transport | TCP | TCP | QUIC nad UDP |
| Formát | text | binární | binární |
| Souběžné požadavky | ~6 spojení | jedno spojení, mnoho proudů | jedno spojení, mnoho proudů |
| Blokování při ztrátě paketu | částečné | horší než u 1.1 | vyřešené |
| Šifrování | volitelné | v praxi povinné | povinné, součást protokolu |
| Přežije přechod z WiFi na mobil | ne | ne | ano |
Aktuální specifikace jsou RFC 9110–9114 z roku 2022. Když návod cituje RFC 2616 nebo 7540, je zastaralý — což je docela slušný test kvality.
Textový protokol, jeden požadavek po druhém.
Původní HTTP zavíralo spojení po každém souboru, takže se pro každý obrázek dělal nový TCP handshake. HTTP/1.1 přineslo keep-alive: spojení zůstane otevřené a pošle se po něm víc požadavků za sebou.
Pořád ale platí, že po jednom spojení běží jeden požadavek naráz. Než dorazí odpověď, další se neposílá. Řešením mělo být pipelining — poslat víc dotazů bez čekání — jenže odpovědi se musely vracet ve stejném pořadí, takže jedna pomalá zablokovala všechny za sebou. Prohlížeče ho postupně vypnuly a dnes ho neumí ani curl.
Místo toho prohlížeče otevírají zhruba šest spojení na doménu. Odtud pochází dvojice praktik, které v roce 2026 už jen škodí:
-
Domain sharding — rozházet obrázky na
img1.example.cz,img2.example.cz, aby se limit obešel. Dnes to znamená DNS dotazy a TLS handshaky navíc. - Sprite sheets a obří balíky — slepit dvacet ikon do jednoho souboru. Rozbíjí to keš: změníš jednu ikonu a klient stahuje celý plát znovu.
Obojí existovalo jen kvůli tomu limitu. S HTTP/2 limit zmizel a zůstala jen ta nevýhoda.
Stejné HTTP, jiný přenos. Hlavičky i těla se posílají v binárních rámcích a rámce z různých požadavků se prokládají po jednom TCP spojení. Tomu se říká multiplexování a je to celý smysl verze 2.
K tomu HPACK, komprese hlaviček. Když posíláš třicet požadavků na stejný web, opakují se v nich stejné cookies a User-Agent — HPACK je pošle jednou.
Souběžných proudů není neomezeně. Server ohlásí SETTINGS_MAX_CONCURRENT_STREAMS, v nginxu je výchozí hodnota 128. Nad ni se čeká úplně stejně jako dřív. Je to vysoký strop, ne žádný strop.
Specifikace HTTP/2 bez šifrování povoluje (říká se tomu h2c), ale žádný prohlížeč to nikdy neimplementoval. Pro provoz z prohlížeče je tedy HTTP/2 vždycky přes HTTPS.
h2c přesto potkáš — mezi proxy a aplikací, hlavně u gRPC.
O tom, jestli se použije HTTP/2, se rozhoduje během TLS handshake mechanismem ALPN: klient v úvodním pozdravu pošle seznam protokolů, které umí, server jeden vybere. Nedomluví-li se, jede se po staru.
openssl s_client -alpn h2 -connect example.cz:443 < /dev/null 2>&1 | grep ALPNTohle je nejdůležitější odstavec na stránce, protože se skoro všude tvrdí opak.
HTTP/2 odstranilo blokování na úrovni HTTP, ale ne na úrovni TCP. A svůj dopad dokonce zhoršilo.
TCP předává aplikaci jeden souvislý proud bajtů v pořadí. Když se po cestě ztratí jeden segment, jádro drží všechna už doručená pokračování v bufferu, dokud se díra nezaplní opakovaným odesláním. Jádro netuší, že nad tím existují nějaké proudy HTTP/2 — pozdržená data přitom obvykle patří úplně jiným požadavkům, které s tou ztrátou nemají nic společného.
Důsledek: jeden ztracený paket zastaví všechny souběžné požadavky. U HTTP/1.1 se šesti spojeními zabrzdí zhruba šestina provozu, u HTTP/2 s jedním spojením se zastaví všechno.
Proto HTTP/2 na špatné WiFi nebo na přetížené mobilní síti dokáže být pomalejší než HTTP/1.1. Není to chyba v nastavení, je to vlastnost TCP a spravit se to uvnitř TCP nedá. Přesně kvůli tomu vzniklo QUIC.
HTTP/3 je HTTP/2 postavené na novém transportu. Ten transport se jmenuje QUIC, běží nad UDP a řeší přesně tu věc z předchozího odstavce.
Proč UDP? Ne proto, že by byl UDP rychlejší. Nový transportní protokol vedle TCP by v praxi nešlo nasadit — routery, firewally a krabice poskytovatelů by pakety, kterým nerozumí, zahodily. UDP je jediná cesta, jak dostat nový transport skrz existující internet. QUIC si spolehlivost, potvrzování a řízení toku dělá sám nad ním.
Co z toho plyne:
Proudy jsou záležitost transportu. QUIC ví, které bajty patří kterému požadavku, takže ztráta paketu zdrží jen ten požadavek, kterého se týká. Ostatní běží dál.
TLS 1.3 je součástí protokolu. Nešifrovaný QUIC neexistuje. Navázání spojení navíc splyne s TLS handshakem, takže místo „TCP handshake, potom TLS handshake" je to jedno kolo.
Spojení přežije změnu adresy. QUIC identifikuje spojení podle Connection ID, ne podle čtveřice adres a portů. Telefon, který přejde z WiFi na mobilní data, si spojení i s TLS stavem podrží. Tohle je v praxi ta nejhmatatelnější výhoda — a pro homelab to znamená plynulejší Immich nebo Jellyfin na telefonu cestou z domu.
Při opětovném připojení umí QUIC poslat data hned v prvním paketu. Zní to skvěle, ale taková data jdou zopakovat: kdo je odposlechne, může je poslat znovu a server je zpracuje podruhé. Smí tudy jít jen požadavky, u kterých opakování nevadí. Zapnout ssl_early_data před aplikací, která mění stav na GET, je bezpečnostní chyba, ne ladění výkonu.
První připojení navíc žádné 0-RTT není, to je vždycky jedno kolo.
Aby to bylo poctivé:
- Uvnitř jednoho proudu se pořadí drží dál. Ztráta uprostřed velké odpovědi tu odpověď pořád zdrží.
- QPACK (komprese hlaviček) může zdržet hlavičky, které odkazují na položky tabulky, jež ještě nedorazily.
- Ztráta paketu pořád zmenší okno zahlcení celému spojení. Proudy se nezastaví, ale všechny se zpomalí.
Tohle většina lidí přehlédne a pak se diví, proč se HTTP/3 „nezapnulo".
Alt-Svc. Server pošle v odpovědi hlavičku:
Alt-Svc: h3=":443"; ma=86400
Znamená to „příště to zkus přes HTTP/3 na portu 443". Háček: je to hlavička v odpovědi, takže první načtení stránky proběhlo přes HTTP/2. HTTP/3 se použije až od druhého spojení. Přes nešifrované HTTP se Alt-Svc navíc ignoruje.
DNS záznam HTTPS. Novější a lepší způsob — do DNS se dá záznam typu HTTPS se seznamem protokolů:
dig +short example.cz HTTPS
# 1 . alpn="h3,h2" ipv4hint=...Prohlížeč pak jede přes HTTP/3 hned napoprvé. V homelabu tohle skoro nikdo nenastavuje, takže „mám zapnuté HTTP/3" v praxi obvykle znamená „od druhého požadavku".
Záleží hlavně na tom, jakou proxy už máš.
Nic nedělej, Caddy má HTTP/3 zapnuté od verze 2.6. Sám otevře QUIC a sám posílá Alt-Svc.
Tvoje jediná práce je pustit dovnitř UDP:
ports:
- "443:443"
- "443:443/udp" # bez tohohle HTTP/3 nefungujeOd verze 3 už to není experimentální funkce, stačí:
entryPoints:
websecure:
address: ":443"
http3: {}Tady je to pracné a selhává tiše. Musí sedět pět věcí naráz:
server {
listen 443 ssl;
listen 443 quic reuseport; # tohle je ten skutečný přepínač
http2 on;
add_header Alt-Svc 'h3=":443"; ma=86400' always;
}- Binárka přeložená s
--with-http_v3_module— ověříšnginx -V -
listen ... quicnavíc klisten ... ssl -
reuseportprávě jednou na danou adresu a port, ne v každém bloku - Ručně přidaná hlavička
Alt-Svc— nginx ji sám neposílá - Průchozí UDP 443
Pozor na direktivu http3 on;, kterou návody uvádějí jako řešení: ta je zapnutá už ve výchozím stavu a sama o sobě neudělá nic. Rozhoduje quic u listen.
Klasický výsledek je server, který má modul přeložený, UDP proražené i nějaké to http3_* ladění v konfiguraci — a pořád jede po HTTP/2, protože v listen chybí quic. Nikde se přitom neobjeví žádná chyba.
nc -zvu host 443 nepoužívej. UDP je nespojovaný, takže nc hlásí úspěch pokaždé, když nedostane zpět „port unreachable" — což je přesně to, co dělá firewall, který pakety mlčky zahazuje. Rozliší „otevřeno" od „černá díra" asi tak nikdy.
Spolehlivé je jedině skutečné navázání QUIC spojení zvenku: prohlížeč a ve vývojářských nástrojích sloupec Protocol, nebo curl s podporou HTTP/3.
Systémový curl na Ubuntu 24.04 HTTP/3 neumí a --http3 rovnou odmítne. Je přeložený s nghttp2, ale bez ngtcp2. Lidé to čtou jako chybu serveru. Potřebuješ curl z kontejneru nebo přeložený s podporou QUIC. A openssl s_client -alpn h3 fungovat nemůže v principu, protože h3 se domlouvá až uvnitř QUIC.
Poctivá odpověď: HTTP/1.1 → HTTP/2 je ten upgrade, na kterém záleží. HTTP/3 je třešnička.
Zapni ho, když už ho tvoje proxy umí sama (Caddy, Traefik). Je to práce na minutu a nemáš důvod ho vypínat.
Nepřekládej si kvůli němu nginx ze zdrojáků. Pět kroků, z nichž každý selže potichu, za velmi malý přínos.
Kde vyhraje: telefon na mobilních datech, špatná WiFi v hotelu, linky s velkým zpožděním a hlavně přechody mezi sítěmi.
Kde nepřinese nic, nebo uškodí: přístup po LAN, gigabitový optický přípoj a všechno, co už teď jde přes WireGuard — ten je sám o sobě UDP a šifrovat šifrované je práce navíc. Na slabém železe navíc QUIC stojí zhruba dvojnásobek až trojnásobek procesorového času na bajt, protože běží v uživatelském prostoru a nemá odlehčení od síťové karty, které TCP dostává zadarmo. Kdo sotva utáhne gigabit přes HTTPS, si HTTP/3 nepomůže.
Firemní a hotelové sítě někdy UDP 443 blokují. Nic se tím nerozbije, prohlížeč spadne zpátky na HTTP/2 — je to důvod udržovat HTTP/2 v pořádku, ne důvod HTTP/3 nezapínat.
Otázka, která napadne každého: když mluvím s prohlížečem přes HTTP/2, nemám tak mluvit i s aplikací za proxy?
Ne, a není to chyba. Standardní a správné uspořádání je h2/h3 ven ke klientovi a obyčejné HTTP/1.1 s keep-alive dovnitř k aplikaci. Všechno, co h2 a h3 přinášejí — multiplexování, komprese hlaviček, zotavení ze ztrát — řeší problémy dlouhé, ztrátové cesty přes internet. Skok z proxy do kontejneru má zpoždění pod milisekundu a nulovou ztrátovost; tam je keep-alive optimální a levnější.
Výjimky, kdy na protokolu k aplikaci záleží, jsou tři: gRPC, dlouhá streamovaná spojení a WebSocket přes HTTP/2. Přes HTTP/3 nemluví s aplikací žádná proxy.
Věci, které pořád najdeš v návodech:
Server push (http2_push_preload). Chrome ho vypnul v roce 2022, Firefox odstranil v roce 2024, nginx direktivy zrušil ve verzi 1.25.1. Používej Link: rel=preload, případně 103 Early Hints. Pro úplnost: pro HTTP/3 je push pořád ve specifikaci, jen ho nikdo neimplementoval.
Priority podle původní specifikace HTTP/2 (rámec PRIORITY, stromy závislostí). Zrušené. Náhradou je jednoduchá hlavička priority: u=3, i, kterou stejně nemáš co ladit.
Pipelining. Vypnutý všude.
Domain sharding a sprite sheets. Viz výš — dnes škodí.
Obousměrné spojení, po kterém můžou obě strany posílat kdykoliv. Používá ho všechno živé: Home Assistant, Proxmox konzole, chaty, upozornění v administracích.
Začíná to jako obyčejný HTTP požadavek:
GET /ws HTTP/1.1
Host: home.example.cz
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Server odpoví 101 Switching Protocols a od té chvíle už to není HTTP. Po tomtéž TCP spojení teče vlastní rámcový protokol. Jede po portu 443 jako všechno ostatní, jen se místo https:// píše wss://.
Z toho plyne to podstatné pro provoz: proxy musí pochopit, že má spojení přepnout a pak už do něj nesahat.
Nejčastější zdroj rozbitých aplikací v celém homelabu:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
location / {
proxy_pass http://127.0.0.1:8123;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
}Tři věci, které tam musí být:
proxy_http_version 1.1 — nginx k aplikaci historicky mluvil protokolem 1.0, který Upgrade neumí. Od verze 1.29.7 je výchozí hodnota konečně 1.1, ale na všem starším je ten řádek nutný. Nech ho tam; na novém nginxu neuškodí.
Ta dvojice hlaviček. map je tam proto, aby se Connection: upgrade posílalo jen u skutečného WebSocketu. Napevno zapsané Connection "upgrade" u všech požadavků ruší keep-alive a některé aplikace se z něj zblázní.
proxy_read_timeout. Výchozí hodnota je 60 sekund a počítá se od poslední přijaté zprávy. WebSocket, kterým chvíli nic neteče, se tiše zavře. Projeví se to jako aplikace, která „po minutě nečinnosti přestane reagovat".
Caddy i Traefik to zvládají samy, bez jediného řádku navíc. Je to jeden z hlavních důvodů, proč se s nimi v homelabu žije líp.
Časté nedorozumění: wss:// jede po 443 jako všechno ostatní a handshake je obyčejný HTTPS požadavek. Když se stránka načte a WebSocket ne, není to firewallem ani port forwardingem — je to nastavením proxy. Nikam nelez a oprav hlavičky.
Původní WebSocket stojí na mechanismu Upgrade, který v HTTP/2 neexistuje. Doplnilo se to později rozšířeným CONNECT, jenže v praxi to prakticky nikdo nepoužívá: nginx to neumí vůbec a servery psané v Go, což jsou i Caddy a Traefik, to mají ve výchozím stavu vypnuté. Pro HTTP/3 je to specifikované taky a neumí to zatím žádný prohlížeč.
Prakticky to znamená, že tvůj WebSocket jede po HTTP/1.1 na vlastním TCP spojení, i když web jinak běží po HTTP/2 nebo HTTP/3. Nevadí to a nemusíš s tím nic dělat.
Jeden důsledek si ale zapamatuj: nevypínej HTTP/1.1. Občas se doporučuje nechat na serveru jen h2 a h3 jako modernější volbu — tím si spolehlivě zabiješ všechny WebSockety. Výchozí protocols h1 h2 h3 v Caddy je správně a není co vylepšovat.
Jednosměrná varianta: server posílá, klient poslouchá. Obyčejné HTTP s Content-Type: text/event-stream, které se nikdy neukončí. Prohlížeč se sám umí znovu připojit. Na upozornění a průběžné výpisy je to jednodušší a robustnější než WebSocket.
Rozbíjí se to o bufferování a kompresi na proxy — dorazí buď všechno naráz na konci, nebo nic:
location /events {
proxy_pass http://127.0.0.1:3000;
proxy_buffering off;
gzip off;
proxy_cache off;
proxy_read_timeout 3600s;
}Pro pořádek: rozšířené tvrzení, že nginx události zadržuje vždycky, není pravda. Když aplikace odpovídá po částech (Transfer-Encoding: chunked), moderní nginx je posílá průběžně i se zapnutým bufferováním. Skuteční viníci jsou komprese a aplikace, které konec odpovědi značí zavřením spojení. proxy_buffering off to spraví v obou případech, takže ho nastav — ale ber to jako pojistku, ne jako opravu nginxu.
Aplikace to umí vyřešit i sama hlavičkou X-Accel-Buffering: no.
Jede výhradně po HTTP/2 a potřebuje ho po celé cestě. Proxy, která ukončí HTTP/2 a k aplikaci mluví po HTTP/1.1, gRPC rozbije — pro gRPC totiž žádný tvar po HTTP/1.1 neexistuje.
V nginxu se místo proxy_pass použije grpc_pass (a na naslouchání musí být http2 on), v Caddy se aplikace zapíše jako h2c://, v Traefiku se službě nastaví schéma h2c.
Nepleť si to s proxy_http_version 2, které nginx umí od verze 1.29.4. To je něco jiného, nemultiplexuje to a rozbíjí to WebSockety, protože není co přepínat. Do sdíleného úryvku konfigurace to nedávej.
Tohle je ta výjimka z pravidla „dovnitř stačí HTTP/1.1" o dva odstavce výš.
WebSocket i SSE spadnou na tom, že je někdo po cestě považuje za mrtvé. Míst je několik:
Hledej je v tomhle pořadí:
Proxy. proxy_read_timeout, výchozích 60 s. Zdaleka nejčastější viník a jediný, kterého máš plně v ruce.
Tabulka spojení na routeru. Tady se často chybuje: samotný Linux drží záznam o navázaném TCP spojení 5 dní, takže na tvém serveru to skoro nikdy není. Domácí routery jsou ale přísnější (OpenWrt zhruba dvě hodiny) a CGNAT u poskytovatele klidně minuty. U UDP je to podstatně kratší i na Linuxu — 30 sekund, u obousměrného provozu 120. Souvisí to s NATem: jakmile záznam zmizí, odpovědi nemají kam.
Cloudflare ukončí odpověď, kterou server nepošle do 125 s, chybou 524. Pevný limit pro WebSockety nezveřejňuje a sám doporučuje tlouct srdcem.
Řešení je vždycky stejné: posílat ping. Pozor ale na to, kdo ho posílá — v prohlížeči to nejde. Rozhraní WebSocket v JavaScriptu umí jen send() a close(), žádné ping(). Prohlížeč na Ping od serveru automaticky odpoví, ale sám ho nikdy nezačne. Tlukot srdce tedy musí vysílat server, nebo si ho aplikace udělá vlastní běžnou zprávou přes send().
# jakou verzi server nabízí
curl -I --http2 https://example.cz
curl -sI https://example.cz | grep -i alt-svc
# co se domluvilo v ALPN
openssl s_client -alpn h2 -connect example.cz:443 < /dev/null 2>&1 | grep ALPN
# je v DNS záznam pro HTTP/3?
dig +short example.cz HTTPS
# WebSocket handshake ručně
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
-H "Sec-WebSocket-Version: 13" https://home.example.cz/wsU posledního příkazu chceš vidět 101 Switching Protocols. Když přijde 200, proxy požadavek nepřepnula a spolkla ho jako obyčejné HTTP.
Ve vývojářských nástrojích prohlížeče si zapni ve výpisu požadavků sloupec Protocol. Je to nejrychlejší způsob, jak zjistit, co se doopravdy použilo — http/1.1, h2, nebo h3.
| Problém | Příčina |
|---|---|
| Prohlížeč jede po HTTP/1.1 | server nenabízí h2 v ALPN, nebo je před ním starší proxy |
| HTTP/3 se nepoužije | chybí Alt-Svc, u nginxu quic v listen, nebo neprochází UDP 443 |
| HTTP/3 až od druhého načtení | tak Alt-Svc funguje; napravíš záznamem HTTPS v DNS |
curl: option --http3: not supported |
systémový curl na Ubuntu HTTP/3 neumí, není to chyba serveru |
WebSocket vrací 200 místo 101
|
proxy nepřeposílá Upgrade a Connection
|
| WebSocket nejde, stránka ano | vždycky proxy, nikdy firewall — wss:// jede po 443 |
| Spojení padá po minutě |
proxy_read_timeout, výchozích 60 s |
| SSE nedorazí nic | komprese, nebo aplikace neposílá po částech |
| gRPC hlásí protokolovou chybu | proxy převádí na HTTP/1.1, chybí grpc_pass
|
502 hned |
za proxy nic neposlouchá, špatný port nebo jméno kontejneru |
504 po chvíli čekání |
aplikace žije, ale nestihla odpovědět do proxy_read_timeout
|
499 v logu nginxu |
klient zavřel spojení dřív, než přišla odpověď. Normální. |
| Na mobilu se přeruší při přechodu na WiFi | bez HTTP/3 se spojení naváže znovu |
Našel jsi chybu nebo něco chybí? Založ issue nebo pošli pull request. — Psáno 2026, licence CC BY-SA 4.0
Základy
- Cesta paketu
- Vrstvy a zapouzdření
- MAC, ARP a přepínání
- Prefixy a masky
- Směrování
- DHCP a DNS
- Porty a spojení
- NAT a port forwarding
Domácí síť
IPv6
Linux firewall
TLS a reverse proxy
- TLS a HTTPS
- HTTP, QUIC a WebSocket
- Reverse proxy
- Caddy
- Nginx
- Traefik
- Certifikáty a Let's Encrypt
- Vlastní certifikační autorita
Kryptografie
- Stavební kameny
- Veřejný a soukromý klíč
- Diffie-Hellman
- Hashe, HMAC a podpisy
- Náhodnost a entropie
- TLS handshake
- Postkvantová kryptografie
- Šifrování disků a souborů
Virtualizace a kontejnery
Diagnostika
Vzdálený přístup
Provoz a bezpečnost
Praxe