// Tutorial · Web · TLS · HTTP/3

HTTP/3 mit QUIC in nginx: aktivieren, prüfen, Fallback sichern

📅 20.09.2026 ⏱ 16 Min. Lesezeit

Aufbauend auf Teil 1 (Zertifikate automatisch mit acme.sh) und Teil 2 (ECC-Zertifikate, zentraler Webroot, saubere TLS-Einstellungen) geht es hier um das Transportprotokoll darunter: HTTP/3 über QUIC. Es ist kein Schalter, den man aus Prinzip umlegt – sondern eine zweite Tür zum selben Dienst, die auf UDP läuft, andere Fehler macht als TCP und deshalb einen sauberen Fallback braucht.

Warum es HTTP/3 überhaupt gibt

HTTP/1.1 und HTTP/2 laufen beide über TCP. TCP garantiert die Reihenfolge aller Pakete – und genau das wird zum Problem, wenn ein Paket unterwegs verloren geht: Alles dahinter wartet, auch Datenströme, die mit dem Verlust nichts zu tun haben. HTTP/2 hat dieses „Head-of-Line-Blocking“ nur auf die Verbindungsebene verschoben, nicht beseitigt.

HTTP/3 setzt deshalb auf QUIC, ein eigenes Transportprotokoll auf Basis von UDP. Die Streams werden einzeln verwaltet, ein verlorenes Paket blockiert nur seinen eigenen Stream. Dazu kommen ein kürzerer Verbindungsaufbau (TLS 1.3 ist eingebaut, nicht aufgesetzt), Verbindungsmigration (Wechsel von WLAN zu Mobilfunk ohne Neuaufbau) und – bewusst abschaltbar – 0-RTT.

ThemaHTTP/2HTTP/3
TransportTCP, verschlüsselt mit TLS 1.2 oder 1.3QUIC über UDP, immer mit TLS 1.3
Port443/tcp443/udp (zusätzlich, nicht stattdessen)
Paketverlustblockiert die ganze Verbindungblockiert nur den betroffenen Stream
VerbindungsaufbauTCP-Handshake + TLS-Handshakeein Handshake (QUIC + TLS 1.3)
Netzwechsel (WLAN → LTE)Verbindung bricht abVerbindung zieht mit (Connection Migration)
Erkennungdirektüber den Header Alt-Svc
Was HTTP/3 nicht ist: kein Ersatz für HTTP/2, kein SEO-Hebel und kein Zertifikatsthema. Es ist eine zweite, parallele Zustellung derselben Inhalte. Deshalb gilt der Merksatz dieses Artikels: HTTP/3 kann man zuschalten, HTTP/2 darf man nie abschalten.

Was dein nginx können muss

Zwei Dinge müssen zusammenpassen: Das Modul http_v3 und eine TLS-Bibliothek, die QUIC spricht. Ob beides da ist, sagt der Build-Aufruf deines nginx:

nginx -V 2>&1 | tr ' ' '\n' | grep -E 'nginx/|http_v3|http_v2|openssl'

Erwartet wird eine Version ab 1.25.0 – dort kam HTTP/3 offiziell dazu – und die Zeile --with-http_v3_module. Fehlt sie, hilft kein Konfigurationstrick: Du brauchst ein anderes Paket.

WegWann sinnvollWorauf achten
Paket der DistributionDebian 13, CachyOS/ArchErst nginx -V prüfen; ob HTTP/3 einkompiliert wurde, entscheidet das Paket, nicht die Version.
Repository von nginx.orgWenn das Distributionspaket zu alt oder ohne v3 gebaut istOffizielles Repo einbinden, Pakete pinnen – sonst mischt sich das Distro-Paket ein.
Docker-Imagenginx als ContainerUDP-Port mappen (siehe Feuer-Abschnitt) und im Container dieselbe Prüfung nginx -V fahren.
Caddy statt nginxWer HTTP/3 ohne Build-Fragen willCaddy spricht QUIC ab Werk, aber du verlässt die nginx-Konfiguration dieser Serie.
Kurztest ohne Konfiguration: nginx -t prüft die Syntax, aber nicht das Modul. Ob http3 verstanden wird, siehst du sofort, wenn du die Direktive in eine Testdatei schreibst und nginx -t erneut laufen lässt: unknown directive "http3" heißt: Modul fehlt.

UDP 443: die häufigste Fehlerquelle

HTTP/3 braucht UDP 443 – und in den meisten Setups ist UDP der Port, der nie geöffnet wurde. Symptom: Die Konfiguration ist korrekt, nginx -t ist grün, und trotzdem bleibt der Browser bei HTTP/2. Die Verbindung kommt einfach nie an.

# UFW (Debian)
sudo ufw allow 443/tcp
sudo ufw allow 443/udp        # <-- entscheidend

# nftables (Beispiel, an die eigene Tabelle anpassen)
sudo nft add rule inet filter input udp dport 443 accept

# Docker: der UDP-Port muss separat veroeffentlicht werden
docker run -d -p 443:443/tcp -p 443:443/udp nginx

In docker-compose.yml entsprechend:

ports:
  - "443:443/tcp"
  - "443:443/udp"     # ohne diese Zeile bleibt HTTP/3 im Container unsichtbar
StelleWas dort blockieren kann
Host-FirewallUFW/nftables/firewalld ohne UDP-Regel – der Klassiker.
Container-NetzwerkNur -p 443:443/tcp veröffentlicht, UDP fehlt.
Router / NATKeine UDP-Weiterleitung, oder CGNAT beim Provider – hier hilft nur ein anderer Anschluss.
Cloud-FirewallBei vServern oft eine zweite, separate UDP-Regel nötig (Security Group).
Client-NetzViele Firmen-VPNs und manche Provider blocken UDP 443 – dann kommt der Besucher per HTTP/2. Kein Fehler, sondern der Normalfall.
UDP 443 ist für Angreifer interessant. Weil QUIC auf UDP läuft, ist Spoofing einfacher als bei TCP, und es gibt keine Verbindung, die man „halb offen“ lassen könnte. Deshalb gehört quic_retry on; dazu (Adressvalidierung vor dem Handshake) und ein Blick auf die Paketraten unter Last. Ohne Retry kann ein einzelnes gefälschtes Paket eine Antwort an eine fremde Adresse auslösen – das ist genau die Amplification, die man nicht bauen will.

Der Serverblock

Der entscheidende Punkt vorweg: Es wird nichts ersetzt. Der bestehende listen 443 ssl;-Block bleibt genau so, wie er in Teil 2 aussah – dazu kommt ein zweiter Listener auf UDP, die Direktive http3 on; und der Alt-Svc-Header, der den Browsern von HTTP/3 erzählt.

server {
    # QUIC laeuft ueber UDP - eigener Listener, plus TCP wie bisher
    listen 443 quic reuseport;
    listen [::]:443 quic reuseport;
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    http3 on;                      # ab nginx 1.25

    server_name pdf.meine.domain;

    ssl_certificate     /etc/ssl/private/pdf.meine.domain_ecc/fullchain.cer;
    ssl_certificate_key /etc/ssl/private/pdf.meine.domain_ecc/pdf.meine.domain.key;

    ssl_protocols TLSv1.2 TLSv1.3;         # HTTP/3 nutzt intern immer TLS 1.3
    ssl_ecdh_curve X25519:prime256v1:secp384r1;

    # Der Hinweis an den Browser: dieselbe Adresse gibt es auch per HTTP/3
    add_header Alt-Svc 'h3=":443"; ma=86400' always;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Danach sudo nginx -t && sudo systemctl reload nginx. Ein Neustart ist nicht nötig – der Reload öffnet den UDP-Socket mit.

HTTP/3 und die Kurvenliste: Weil QUIC intern TLS 1.3 spricht, gilt ssl_ecdh_curve aus Teil 2 auch hier – die ausgehandelte Gruppe entscheidet der Client, wie dort gemessen. Bei aktuellen Browsern landet HTTP/3 damit auf X25519.

Die Schrauben im Detail

Für einen funktionierenden Betrieb reichen die vier Zeilen aus dem Serverblock. Die folgenden Direktiven sind für Sonderfälle – und man sollte wissen, welche davon echte Auswirkungen haben.

DirektiveStandardWann du sie anfassen solltest
quic_retry onausImmer, sobald der Server öffentlich erreichbar ist: Adressvalidierung per Retry-Token vor dem Handshake.
quic_gso onausLinux-Kernel ≥ 4.18 vorausgesetzt: schickt mehrere QUIC-Pakete in einem Syscall – deutlich weniger CPU pro Paket.
quic_host_keyzufällig beim StartNur bei mehreren Instanzen hinter derselben Adresse: gleicher Schlüssel für Retry-Tokens, sonst weisen sich die Instanzen gegenseitig ab.
http3_max_concurrent_streams128Viele parallele Anfragen pro Verbindung? Höher setzen. Speicher pro Client steigt mit.
http3_stream_buffer_size64kNur bei Problemen mit großen Uploads über QUIC anpassen.
ssl_early_data onaus0-RTT: spart eine Runde, ist aber wiederholbar. Braucht Session-Tickets – siehe Abschnitt zu acme.sh.
$http3 im LogFür die Auswertung: enthält h3, wenn die Anfrage über QUIC kam, sonst einen Bindestrich.
# Eigene Logdatei mit Protokoll-Kennzeichnung
log_format quic '$remote_addr - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" "$http_user_agent" '
                'proto=$server_protocol h3=$http3';

access_log /var/log/nginx/access-h3.log quic;

Prüfen in vier Schritten

Nach dem Reload lässt sich in wenigen Sekunden belegen, ob HTTP/3 wirklich ausgeliefert wird.

# 1) Hat nginx das Modul?
nginx -V 2>&1 | tr ' ' '\n' | grep with-http_v3_module

# 2) Antwortet der Dienst ueberaupt per HTTP/3?
#    (curl muss mit HTTP/3 gebaut sein: curl -V | grep HTTP3)
curl --http3-only -sI https://pdf.meine.domain/ | head -n 1
# HTTP/3 200

# 3) Ist der Fallback-Hinweis gesetzt? Die Antwort kommt hier bewusst per HTTP/2
curl -sI --http2 https://pdf.meine.domain/ | grep -i alt-svc
# alt-svc: h3=":443"; ma=86400

# 4) Der Handshake selbst - ALPN und Protokollversion
openssl s_client -quic -connect pdf.meine.domain:443 -alpn h3 < /dev/null 2>/dev/null \
  | grep -E 'Protocol|Cipher|ALPN'
# Protocol: QUICv1
# ALPN protocol: h3
# Cipher is TLS_AES_256_GCM_SHA384

Diese vier Ausgaben sind die ganze Wahrheit: Modul vorhanden, QUIC-Verbindung möglich, Fallback angekündigt, ALPN verhandelt. Im Browser ergänzt die Entwicklerwerkzeuge-Ansicht den letzten Beweis – im Reiter „Netzwerk“ steht unter Protokoll dann h3 statt h2.

Der Blick ins Log lohnt sich: nginx schreibt die Protokollversion in die Request-Zeile. Ein tail -f /var/log/nginx/access.log zeigt dann "GET / HTTP/3.0" – und daneben weiter "HEAD / HTTP/2.0" von Werkzeugen, die kein QUIC sprechen. Genau so soll es aussehen.

Warum HTTP/2 bleiben muss

Alt-Svc ist keine Weiterleitung, sondern ein Angebot: „Dieselbe Ressource gibt es auch unter dieser Adresse per HTTP/3.“ Der Browser entscheidet. Deshalb sind vier Dinge wichtig:

PunktWarum
always im HeaderOhne always fehlt der Hinweis bei 4xx/5xx-Antworten – dann lernt der Browser h3 nie kennen.
ma richtig wählenDie Merkzeit in Sekunden. 86400 (ein Tag) ist ein guter Wert; fällt h3 später aus, greift der Rückfall erst nach Ablauf.
HTTP/2 aktiv lassenJeder Client ohne QUIC, jedes VPN und jeder HTTP-Test läuft weiter über TCP. Schaltest du h2 ab, verlierst du genau diese Fälle.
Alt-Svc im CacheBrowser merken sich das Angebot. Beim Testen deshalb immer ein frisches Profil oder den Cache des Alt-Svc-Eintrags leeren.
Der Klassiker beim Testen: Erst wird h3 aktiviert, dann getestet – und der Browser bleibt stur bei HTTP/2. Häufigste Ursache ist nicht nginx, sondern der bereits gecachte Alt-Svc-Eintrag oder ein Test-Setup hinter VPN. Gegenprobe auf der Kommandozeile mit curl --http3-only trennt beide Fälle sauber: Kommt die Antwort, ist der Server in Ordnung – und es liegt am Client-Netz.

Zusammenspiel mit acme.sh

Die gute Nachricht: Für HTTP/3 brauchst du kein eigenes Zertifikat. QUIC nutzt dasselbe ssl_certificate wie der TCP-Listener – das ECC-Zertifikat aus Teil 2 gilt also für beide Wege. Auch die Erneuerung ändert sich nicht: acme.sh stellt neu aus und ruft --reloadcmd, nginx lädt neu, der UDP-Socket bekommt das neue Zertifikat mit.

Drei Punkte sind dennoch erwähnenswert:

  • Kein Port 80 mehr im Spiel: Für die Ausstellung selbst ändert HTTP/3 nichts. Wer aber ohnehin auf die DNS-Challenge umsteigt, kann Port 80 ganz schließen – HTTP/3 braucht ihn nicht.
  • TLS 1.3 ist Pflicht: Steht in ssl_protocols nur TLSv1.2, funktioniert HTTP/2 weiter, HTTP/3 aber nicht. Der Listener ist dann still tot – es gibt keine Fehlermeldung, nur kein h3.
  • 0-RTT und Session-Tickets: In Teil 2 haben wir ssl_session_tickets off gesetzt – für Forward Secrecy. 0-RTT („ssl_early_data“) braucht aber genau diese Tickets, um eine frühere Sitzung fortzusetzen.
Beides gleichzeitig geht nicht. Entweder Session-Tickets aus – sicherer, kein 0-RTT. Oder Tickets an und ssl_early_data on – dann kann der erste Datenblock wiederholt werden (Replay), was für lesende Dienste unkritisch ist, bei Uploads und POSTs aber riskant. Wer 0-RTT nutzt, sollte den Upstream mit proxy_set_header Early-Data $ssl_early_data; informieren und dort nicht-idempotente Anfragen ablehnen.

Was es wirklich bringt – und was nicht

HTTP/3 ist kein allgemeiner Turbo. Die ehrliche Einordnung: Auf einer guten Verbindung mit niedriger Latenz misst man kaum einen Unterschied, weil der Flaschenhals dort die Serverantwort ist. Der Gewinn entsteht dort, wo Verbindungen schlecht sind – Mobilfunk, WLAN mit Verlusten, hohe Paketumlaufzeiten.

SituationErwartung
Erster Aufruf über MobilfunkKürzerer Aufbau, weil Verbindung und Verschlüsselung in einem Handshake entstehen.
Verluste im NetzDeutlich ruhigeres Verhalten: Ein fehlendes Paket blockiert nicht mehr alles.
Netzwechsel unterwegsVerbindung bleibt bestehen, statt neu aufzubauen.
Lokales LAN, schnelles WLANPraktisch kein Unterschied – hier lohnt der Aufwand nur aus Neugier.
Viele kleine Requests über einen ProxyKann CPU kosten. Gegenmittel: quic_gso on und größere UDP-Puffer.
# Latenz-Vergleich derselben URL, einmal h2, einmal h3-only
curl -o /dev/null -s -w 'h2: %{http_version} appconnect=%{time_appconnect}s ttfb=%{time_starttransfer}s\n' \
     --http2 https://pdf.meine.domain/
curl -o /dev/null -s -w 'h3: %{http_version} appconnect=%{time_appconnect}s ttfb=%{time_starttransfer}s\n' \
     --http3-only https://pdf.meine.domain/

# Bei viel Durchsatz: UDP-Puffer des Kernels anheben (Werte in /etc/sysctl.d/)
# net.core.rmem_max = 26214400
# net.core.wmem_max = 26214400
Messen, nicht glauben: Nimm für den Vergleich zehn Durchläufe und schau auf die Streuung, nicht auf den besten Wert. Ein einzelner Aufruf sagt fast nichts – erst der Median über mehrere Läufe zeigt, ob h3 in deinem Szenario wirklich etwas ändert.

Wenn es schiefgeht

SymptomUrsacheVorgehen
nginx startet nicht: unknown directive "http3"Modul http_v3 fehlt im Paket.nginx -V prüfen, anderes Paket oder nginx.org-Repo nutzen.
Browser bleibt bei h2, curl --http3-only läuft ins TimeoutUDP 443 kommt nicht durch (Firewall, Docker-Mapping, Router, Cloud-Regel).Erst lokal testen, dann Host-Firewall, dann Container-Ports, dann Router.
Server ist erreichbar, aber kein alt-svc-HeaderHeader fehlt oder ist an eine Bedingung geknüpft.always ergänzen und mit curl -sI prüfen.
h3 funktionierte, plötzlich nicht mehrquic_host_key nicht gesetzt und Instanz/Socket neu gestartet, oder UDP-Blockade neu dazugekommen.Schlüssel fest setzen, Firewall-Regel und Provider-Änderungen kontrollieren.
Hohe CPU-Last unter LastQUIC-Verarbeitung pro Paket, fehlende Offloads.quic_gso on, UDP-Puffer anheben, Paketraten beobachten.
Große Uploads brechen abBuffergrößen, MTU/Path-MTU, VPN-Tunnel mit kleiner MTU.http3_stream_buffer_size erhöhen, MTU im Pfad prüfen, testweise über h2 hochladen.
Im Log steht überall HTTP/2.0Alle Testclients sprechen kein QUIC (oder der $http3-Eintrag fehlt im Format).Mit curl --http3-only und einem frischen Browserprofil gegenprüfen.
Der stille Ausfall: Anders als beim Zertifikat gibt es bei HTTP/3 keine Warnung im Browser. Fällt der UDP-Pfad weg, nutzen Besucher einfach HTTP/2 – es sieht alles normal aus, und niemand merkt, dass die Hälfte der Konfiguration wirkungslos ist. Deshalb gehört der --http3-only-Check in dein Monitoring, nicht nur in den Aktionstag.

Gefahrlos einführen

SchrittWas du tustRückweg
1. BestandsaufnahmeBuild prüfen, UDP-Regel vorbereiten, nginx -t als Referenzlauf.
2. Ein Testserverh3 zuerst auf einer Subdomain oder einem Test-vHost aktivieren, nicht auf der Hauptseite.Zeilen entfernen, reload.
3. PrüfenDie vier Befehle aus dem Prüf-Abschnitt, danach ein echter Browser ohne VPN.
4. AusrollenAlt-Svc auf den echten Servern setzen, ma ruhig auf einen Tag.Alt-Svc-Zeile löschen – nach Ablauf der Merkzeit sind alle zurück auf h2.
5. BeobachtenAnteil h3 im Log, CPU, UDP-Paketraten.Im Zweifel zuerst den Client-Anteil prüfen, bevor du am Server drehst.
Warum der Rückweg so einfach ist: Du baust nichts um – du legst einen zweiten Weg daneben und bewirbst ihn per Header. Ein reload ohne die quic-Listener schaltet h3 ab, ohne bestehende Verbindungen zu verlieren.

Häufige Fragen (FAQ)

FrageAntwort
Ist HTTP/3 schneller?Nur in bestimmten Situationen: bei hoher Latenz, Paketverlusten und Netzwechseln. Im LAN oder über eine gute Glasfaserverbindung ist der Unterschied im Rauschen.
Brauche ich es überhaupt?Nein. Es ist eine Optimierung mit überschaubarem Aufwand. Wer viel mobile Besucher hat, profitiert; wer nur im eigenen Netz arbeitet, kann sich das sparen.
Kann ich HTTP/1.1 abschalten?Technisch ja, sinnvoll nein: Alte Clients, manche Werkzeuge und Monitoring-Checks sprechen nur HTTP/1.1. Der Betrieb kostet nichts.
Was ist mit einem CDN wie Cloudflare davor?Dann terminiert das CDN HTTP/3 und spricht mit deinem Server in der Regel HTTP/2. Deine nginx-Konfiguration ist dafür irrelevant – prüfe die Einstellung beim Anbieter.
Kostet HTTP/3 mehr Traffic?Nein, praktisch gleich. QUIC hat einen etwas größeren Header-Anteil pro Paket, das fällt aber nicht ins Gewicht.
Kann ich QUIC angreifen lassen – DoS durch UDP?QUIC ist gegen Spoofing und Amplification besser geschützt als pures UDP, aber nicht immun. quic_retry on und Rate-Limits vor dem Server sind die Basis; nginx selbst hat für QUIC keine eigenen Limits.
Warum steht im Log „HTTP/3.0“?nginx schreibt die Protokollversion der Anfrage in die Request-Zeile. Das ist korrekt und der einfachste Beleg, dass h3 im Einsatz ist.
Erkenne ich HTTP/3 in Uptime-Kuma?Standard-Checks laufen über HTTP/1.1 oder HTTP/2. Ein HTTP/3-Monitor braucht einen Client mit QUIC – deshalb h3 wie oben beschrieben separat prüfen.
Muss ich etwas am Zertifikat ändern?Nein. Gleiches Zertifikat, gleiche Erneuerung, nur der Reload muss weiterhin funktionieren.

Fazit

HTTP/3 ist keine Revolution, sondern eine Ergänzung: ein zweiter Transportweg über UDP, der dort gewinnt, wo TCP nervt – Mobilfunk, Verluste, Netzwechsel. Technisch ist der Aufwand klein, weil die Zertifikate und die TLS-Einstellungen der ersten beiden Teile unverändert weiterverwendet werden. Gefährlich ist nur ein Punkt: zu glauben, es funktioniere, weil die Konfiguration grün ist.

Deshalb ist der wichtigste Teil dieses Artikels nicht der Serverblock, sondern die Prüfung – vier Befehle, die zeigen, ob QUIC wirklich verhandelt wird, und ein UDP-Port, der in den meisten Setups vergessen wird.

Die fünf Merksätze: ① HTTP/3 nutzt UDP 443 – TCP bleibt für HTTP/2 und HTTP/1.1 bestehen. ② nginx -V zeigt, ob dein Build --with-http_v3_module hat. ③ Alt-Svc mit always ist der Werbeträger für h3 – ohne ihn probiert es kein Browser. ④ QUIC braucht TLS 1.3 und Session-Tickets für 0-RTT – beides hängt an den Entscheidungen aus Teil 2. ⑤ Ein h3-Ausfall ist lautlos: Prüfen mit curl --http3-only und ins Log schauen.
📝
HuuuHosting-Redaktion

Open-Source-Tools für den eigenen Server – getestet, dokumentiert und in der Debian-13-Serie Schritt für Schritt erklärt.