
🌐 Auch auf: English · Français · Español
Viele Nutzer meiner Nextcloud-Instanzen sind mit dem Smartphone unterwegs. Genau dafür wurde HTTP/3 gebaut: Es läuft über QUIC statt TCP, braucht für den Verbindungsaufbau eine Paketumrundung weniger, verkraftet Paketverlust besser und kann eine Verbindung beim Wechsel von WLAN zu Mobilfunk mitnehmen. Mein Ziel war aber nicht „HTTP/3 haben“, sondern: HTTP/3 nur, wenn es messbar hilft – mehr Durchsatz oder eine stabilere Verbindung bei schlechtem Netz.
Dieser Beitrag beschreibt, wie ich es eingeschaltet habe, wie ich nachweise, dass es wirklich läuft – und die zwei Bremsen, die HTTP/3 anfangs deutlich langsamer machten als HTTP/2.
🔌 Der Controller kann es nicht – sein nginx schon
Als Ingress läuft bei mir der NGINX Ingress Controller von F5. Er hat keine Option für HTTP/3: keinen Schalter, keinen ConfigMap-Schlüssel, nichts in den Release Notes. Ein Blick in den laufenden Pod zeigt aber: Das mitgelieferte nginx 1.31.7 ist mit --with-http_v3_module gebaut, dazu OpenSSL 3.5, das als erste OpenSSL-Version QUIC auf Serverseite beherrscht. Und weil der Controller mit hostNetwork läuft, kann nginx UDP 443 direkt öffnen.
Also geht es über die Snippets, die ich ohnehin nutze:
# server-snippets of the main site (once per address):
listen 443 quic reuseport rcvbuf=4m sndbuf=4m;
listen [::]:443 quic reuseport rcvbuf=4m sndbuf=4m;
# http-snippets of the controller:
map $scheme $http3_alt_svc {
https 'h3=":443"; ma=604800';
default "";
}
http3_stream_buffer_size 1m;
quic_gso on;
# next to the other security headers (location-snippets):
add_header Alt-Svc $http3_alt_svc always;reuseportnur einmal pro Adresse: nginx erlaubt diese Option nur in einer einzigenlisten-Zeile je Adresse und Port. Sie steht deshalb nur im Server-Block der Hauptseite, alle anderen Hosts teilen sich diese Sockets.- Alt-Svc nur über HTTPS: Über diesen Header erfahren Browser, dass es HTTP/3 gibt. Die
mapliefert ihn nur bei HTTPS, auf Port 80 bleibt der Wert leer, und nginx sendet dann gar keinen Header. - Alt-Svc neben die anderen Header:
add_headerwird in nginx nicht vererbt, sobald eine Location eigene Header setzt. Meine Sicherheits-Header stehen in den Location-Snippets – also muss Alt-Svc dort mit hinein. - Firewall: UDP 443 eingehend für IPv4 und IPv6. Ausgehend braucht es keine Regel, die Antworten sind für conntrack „established“.
Alles hängt an einem Schalter pro Host (ingress_http3_enabled), standardmäßig aus. Eingeschaltet habe ich zuerst den Blog, dann die kleinere Nextcloud. Vor dem echten Lauf habe ich die neuen Zeilen mit nginx -t an einer Kopie der Konfiguration im Pod geprüft.
Was man mitnimmt: „Wird nicht unterstützt“ heißt manchmal nur „hat keinen Schalter“. Ein Blick auf nginx -V zeigt, was das Binary wirklich kann.
🤔 Warum ich HTTP/3 lange für unmöglich hielt
Im August hatte ich das schon einmal geprüft – mit dem Ergebnis „geht nicht“. So stand es seitdem auch als Kommentar in meiner Firewall-Vorlage. Geprüft hatte ich damals zwei Dinge: Gibt es im Controller eine Option für HTTP/3? Nein. Antwortet der Server über HTTP/3? Nein, natürlich nicht – es gab ja keinen QUIC-Listener.
Was ich nicht geprüft hatte: ob das nginx im Controller das Modul überhaupt enthält. Die Schlussfolgerung „keine Option, also keine Unterstützung“ war zu schnell. Ob das damalige Image das Modul schon hatte, lässt sich heute nicht mehr nachvollziehen – das aktuelle hat es, zusammen mit einer OpenSSL-Version, die QUIC serverseitig kann. Erst die Frage „was kann das Binary?“ statt „was bietet die Konfiguration an?“ brachte die andere Antwort.
Was man mitnimmt: Ein „geht nicht“ verdient ein Datum und eine Begründung – und eine zweite Prüfung, wenn sich die Software geändert hat.
✅ Woran man sieht, dass HTTP/3 wirklich läuft
Eine Seite, die lädt, sagt nichts über das Protokoll. Diese Prüfungen habe ich nach jeder Umstellung gemacht:
curl -sI --http2 https://www.apt-upgrade.me/ | grep -i alt-svc
alt-svc: h3=":443"; ma=604800
curl -s --http3-only -o /dev/null -w '%{http_code} h%{http_version}\n' https://www.apt-upgrade.me/
200 h3- Erzwungenes HTTP/3 mit
curl --http3-onlyauf allen wichtigen Pfaden: Startseite, Login, Nextcloudsstatus.php, die OCS-API, WebDAV, Collabora – überall die erwartete Antwort über h3. - Gleicher Inhalt: eine große JavaScript-Datei über HTTP/2 und HTTP/3 geladen – identische SHA-256-Prüfsumme. Auch die Antwort-Header sind dieselben.
- Serverseite:
ss -ulnpzeigt einen UDP-Socket pro nginx-Worker auf Port 443, IPv4 und IPv6; im Access-Log stehen Anfragen mitHTTP/3.0. - Von außen: http3check.net bestätigt QUIC Version 1 und HTTP/3, getestet über IPv6 – das ich von zu Hause aus gar nicht prüfen kann.
- Sicherheit unverändert: Die fail2ban-Filter erkennen Login-Zeilen mit
HTTP/3.0genauso wie mitHTTP/2.0. 0-RTT-Frühdaten bleiben aus, damit niemand ein Login-Formular wiederholt einspielen kann.
Eine kleine Falle dabei: Mein erster fail2ban-Test schlug fehl – mit einer selbst zusammengebauten Logzeile im falschen Format. Mit einer echten Zeile aus dem Log, bei der nur das Protokoll ersetzt war, passte der Filter sofort.
Was man mitnimmt: Ein Nachweis braucht mehrere Blickwinkel – Client, Server, ein Dritter von außen und die eigenen Sicherheitswerkzeuge.
🐢 Bremse 1: 64 KB pro Paketumrundung
Kleine Anfragen waren über HTTP/3 sofort gleich schnell oder schneller. Dann kam der Test mit einer großen Datei: 5,7 MB brauchten über HTTP/2 0,18 Sekunden, über HTTP/3 0,99 Sekunden – mehr als fünfmal so lange.
Der Client verbrauchte dabei kaum Rechenzeit, er wartete. Die Rechnung führte zur Ursache: nginx puffert pro QUIC-Stream standardmäßig nur 64 KB (http3_stream_buffer_size). Mehr kann ein Stream nicht unterwegs haben. 64 KB pro Paketumrundung bei 11 ms ergeben genau die gemessenen 5,8 MB/s. Auf einer Mobilfunkverbindung mit 50 ms wären es nur noch etwa 1,3 MB/s – HTTP/3 hätte meine Nutzer unterwegs also gebremst.
Mit 1 MB Puffer lagen Downloads über HTTP/3 bei 27–31 MB/s, gleichauf mit HTTP/2.
Was man mitnimmt: Ein neues Protokoll bringt neue Standardwerte mit. Ein einziger Test mit einer großen Datei hätte gereicht, um die Bremse zu finden – deshalb gehört er in jede Abnahme.
🧺 Bremse 2: ein Socket-Puffer, der überläuft
Dann die Gegenrichtung: 20 MB Upload. Über HTTP/3 kamen im Schnitt 7,8 MB/s an, über HTTP/2 11,9 MB/s – und der Server zählte in dieser Zeit 284 verworfene UDP-Pakete (RcvbufErrors in /proc/net/snmp).
Der Grund liegt im Unterschied zu TCP. Bei TCP regelt der Kernel Fluss und Puffer selbst. QUIC läuft dagegen im Programm, über UDP: Jedes Paket wartet im Empfangspuffer des Sockets, bis nginx es abholt. Linux gibt einem Socket standardmäßig höchstens 208 KB – bei einem schnellen Upload ist der voll, und der Kernel wirft weitere Pakete weg. QUIC muss sie neu senden und bremst sich dabei selbst.
Die Lösung hat zwei Teile:
- Kernel-Grenze anheben:
net.core.rmem_maxundnet.core.wmem_maxauf 7,5 MB – der Wert, den QUIC-Implementierungen empfehlen. Das hebt nur die Obergrenze; alle anderen Sockets behalten ihren Standard. - Puffer anfordern: Die QUIC-Listener verlangen mit
rcvbuf=4m sndbuf=4mselbst größere Puffer. Der Kernel verdoppelt den Wert intern;ss -mzeigt danach 8 MB pro Socket.
Danach: 0 verworfene Pakete, und Uploads über HTTP/3 lagen auf dem Niveau von HTTP/2 – die verbleibenden Schwankungen kamen von meinem eigenen Anschluss.
Was man mitnimmt: Wer UDP mit hohem Durchsatz betreibt, muss die Socket-Puffer kennen. Die Zahl der verworfenen Pakete steht im Kernel – man muss nur nachsehen.
📊 Die Messungen
Gemessen habe ich von meinem Homelab aus, mit curl, jeweils 15 neue Verbindungen pro Wert (Median). Der Blog liegt weiter weg (rund 42 ms pro Umrundung), die Nextcloud näher (rund 11 ms).
| Messung | HTTP/2 | HTTP/3 |
|---|---|---|
| Blog: Verbindung + Verschlüsselung fertig | 96 ms | 58 ms |
| Blog: erstes Byte der Startseite | 139 ms | 104 ms |
| Blog: Startseite komplett | 268 ms | 227 ms |
| Nextcloud: Verbindung + Verschlüsselung fertig | 31 ms | 27 ms |
| Nextcloud: 5,7 MB Download, Standardpuffer | 34 MB/s | 5,8 MB/s |
| Nextcloud: 5,7 MB Download, 1 MB Stream-Puffer | 28 MB/s | 30 MB/s |
| Nextcloud: 20 MB Upload, 208 KB Socket-Puffer | 11,9 MB/s | 7,8 MB/s |
| Nextcloud: 20 MB Upload, 8 MB Socket-Puffer | 15,1 MB/s | 13,0 MB/s |
| Nextcloud: 100 MB Upload, 8 MB Socket-Puffer | 8,7 MB/s | 7,7 MB/s |
| Collabora: Discovery-Abruf (47 KB) | 55–72 ms | 57 ms |
Bei 100 MB begrenzt mein eigener Upload beide Protokolle gleich, verworfene Pakete gab es dabei keine. Größere Downloads ließen sich ohne Nutzerkonto nicht testen: Die größte öffentlich abrufbare Datei der Nextcloud ist die 5,7-MB-Skriptdatei. Collabora bearbeitet Dokumente über WebSockets, die weiter über TCP laufen – messbar ist dort nur der Abruf der Discovery-Datei.
Der Vorteil beim Verbindungsaufbau ist eine Paketumrundung: TCP und TLS brauchen zwei, QUIC erledigt beides in einer. Beim Blog mit 42 ms sind das rund 40 ms pro neuer Verbindung, bei der nahen Nextcloud fast nichts. Im Mobilfunk mit 50 bis 100 ms ist der Gewinn entsprechend größer.
Was man mitnimmt: HTTP/3 spart vor allem Wartezeit, nicht Bandbreite. Je weiter weg der Nutzer, desto mehr bringt es.
🔧 Die übrigen Stellschrauben
quic_gso on: nginx übergibt dem Kernel mehrere QUIC-Pakete auf einmal statt eines pro Aufruf. Das spart CPU beim Senden; meine virtuellen Netzwerkkarten machen das in Software, was trotzdem hilft. Eingeschaltet.- Alt-Svc 7 Tage statt 1 Tag: Wiederkehrende Besucher starten länger direkt mit HTTP/3. Schaltet man HTTP/3 später ab, kostet das jeden Browser höchstens einen Fehlversuch – er fällt von selbst auf HTTP/2 zurück. Eingestellt.
quic_bpf: sorgt dafür, dass Pakete einer Verbindung nach einem Netzwechsel beim richtigen nginx-Worker landen. Dafür muss nginx ein eBPF-Programm laden, und das braucht zusätzliche Rechte (CAP_BPF,CAP_NET_ADMIN) – ausgerechnet im Container, der aus dem Internet erreichbar ist und bewusst nur ein einziges Recht hat. Nicht eingeschaltet: Scheitert eine Migration, baut das Handy eben neu auf, mit QUIC in einer Umrundung.- 0-RTT (
ssl_early_data): würde bei wiederkehrenden Verbindungen noch eine Umrundung sparen, aber solche Anfragen lassen sich wiederholt einspielen – auch Logins. Bleibt aus.
Was man mitnimmt: Jede Stellschraube hat einen Preis. Eingeschaltet wird, was wenig kostet; was an der Härtung rüttelt, bleibt aus, auch wenn es auf dem Papier schneller wäre.
🧭 Schon beim ersten Besuch: der HTTPS-Eintrag im DNS
Alt-Svc hat einen Schönheitsfehler: Ein Browser erfährt erst aus der ersten Antwort, dass es HTTP/3 gibt. Der allererste Besuch läuft deshalb immer über HTTP/2. Abhilfe schafft ein eigener DNS-Eintrag vom Typ HTTPS (RFC 9460): Er sagt dem Browser schon bei der Namensauflösung, welche Protokolle der Server spricht.
# DNS zone (example): HTTPS record, priority 1, "." = same name
www.example.org. 3600 IN HTTPS 1 . alpn="h3,h2"
# check it:
dig +short HTTPS www.example.org
1 . alpn="h3,h2"Die 1 ist die Priorität, der Punkt steht für „derselbe Name“, und alpn listet die Protokolle in der gewünschten Reihenfolge. Auf IP-Hinweise im Eintrag verzichte ich bewusst – sie müssten bei jedem Adresswechsel mitgepflegt werden. Chrome, Firefox und Safari werten den Eintrag aus. Nicht jede DNS-Verwaltung bietet den Typ an; es lohnt ein Blick, bevor man sucht, warum er fehlt.
Bei mir ist genau das der Fall: Die DNS-Verwaltung von Strato, bei der apt-upgrade.me liegt, bietet nur A, AAAA, CNAME, MX, NS, TXT und SRV an – einen HTTPS-Eintrag kann ich dort nicht setzen. Nachbilden lässt er sich auch nicht, Browser werten nur den echten Typ aus. Für den Blog bleibt es deshalb bei Alt-Svc: Der erste Besuch läuft über HTTP/2, jeder weitere sieben Tage lang direkt über HTTP/3. Den DNS-Anbieter nur dafür zu wechseln, wäre mir der Gewinn nicht wert.
Was man mitnimmt: Der Server kann HTTP/3 nur anbieten. Ob der Browser es gleich beim ersten Mal nutzt, entscheidet das DNS.
📱 Was es meinen Nutzern bringt – und was nicht
- Browser am Smartphone: der größte Nutzen – schnellerer Aufbau, keine Blockade aller Anfragen bei einem verlorenen Paket, und die Verbindung überlebt den Wechsel zwischen WLAN und Mobilfunk.
- Nextcloud-App auf dem iPhone: iOS nutzt HTTP/3 von sich aus, sobald der Server es per Alt-Svc anbietet.
- Android-App und Desktop-Client: nutzen nach meinem Stand Netzwerkbibliotheken ohne HTTP/3. Wie hoch der Anteil wirklich ist, zeigt das Access-Log nach ein paar Tagen.
- Collabora und notify_push: laufen über WebSockets. nginx kann WebSockets nicht über HTTP/3, diese Verbindungen bleiben bei HTTP/1.1 über TCP – das funktioniert, nur ohne Vorteil.
Was ich von hier aus nicht messen konnte, ist das Verhalten bei schlechtem Netz. Das ist der nächste Test: mit dem Smartphone in einer Gegend mit schwachem Empfang, HTTP/3 an und aus im Vergleich.
Was man mitnimmt: Ehrlich bleiben, für wen sich etwas ändert. Ein Protokoll-Upgrade hilft nur den Clients, die es auch sprechen.
📅 Stand und Ausblick
Inzwischen läuft HTTP/3 auf allen drei Servern: dem Blog und beiden Nextcloud-Instanzen. Die zweite Nextcloud habe ich mit denselben Prüfungen abgenommen wie die erste, und der Drift-Check meldet überall changed=0 – die Server entsprechen genau dem Code im öffentlichen Repository.
In einer Woche schaue ich mir drei Dinge an:
- Nutzung: Wie hoch ist der Anteil der Anfragen über
HTTP/3.0im Access-Log – getrennt nach Browsern, Nextcloud-Apps und Sync-Clients? - Fehler: Gibt es über HTTP/3 mehr Fehlerantworten oder abgebrochene Uploads als über HTTP/2?
- Praxis: Wie verhält sich die Nextcloud am Smartphone bei schwachem Empfang, mit und ohne HTTP/3?
Die Zahlen veröffentliche ich in den nächsten sieben Tagen in einem eigenen Beitrag. Bringt HTTP/3 meinen Nutzern kaum etwas oder zeigen sich mehr Fehler, schalte ich es pro Server wieder ab – das ist ein Schalter und ein Playbook-Lauf.
🎯 Die Lehren
- Erst nachsehen, was das Binary kann – „keine Option“ heißt nicht „kein Modul“.
- Mit großen Dateien testen, in beide Richtungen – kleine Anfragen verstecken jede Durchsatzbremse.
- UDP-Puffer gehören zu QUIC dazu –
RcvbufErrorszeigen, ob der Kernel Pakete wegwirft. - Mit echten Logzeilen testen – nachgebaute Beispiele prüfen nur das Beispiel.
- Einschalten, weil es hilft – gemessen, nicht vermutet, und mit einem Schalter, der sich wieder umlegen lässt.





