Zertifikatsketten: Warum eine Seite im Browser aufgeht und in curl scheitert

Inhalt
Ein TLS-Handshake sieht aus wie ein einziges Ja oder Nein. Tatsächlich ist er ein kleiner Beweis: Der Server legt ein Zertifikat vor, und der Client muss daraus einen Pfad zu einer Wurzel bauen, der er ohnehin vertraut. Der Beweis gelingt oder nicht, und die Antwort hängt davon ab, wer fragt.
Genau das überrascht. Derselbe Server kann in derselben Sekunde von Chrome anerkannt und von curl abgewiesen werden.

Was im Handshake übertragen wird
Der Server sendet sein eigenes Zertifikat, das Blatt, und normalerweise ein oder zwei darüber. Das Blatt trägt die Hostnamen, für die es gilt, einen öffentlichen Schlüssel und eine Signatur. Diese Signatur stammt nicht von der Wurzel, sondern von einem Zwischenzertifikat, das seinerseits von der Wurzel signiert wurde.
Die Wurzel selbst wird nie gesendet. Im Vertrauensspeicher des Betriebssystems oder des Browsers liegen einige Hundert davon, und sie sind der einzige Teil, den der Client schon hat.
Der Client braucht also alles zwischen dem Blatt und dieser Wurzel. Sendet der Server das Zwischenzertifikat nicht, entsteht eine Lücke, und eine Lücke heißt: kein Beweis.
Das Glied, das verloren geht
Fast jede Zertifizierungsstelle liefert zwei Dateien: das Zertifikat selbst und ein Bündel aus Zertifikat und Zwischenzertifikat. Bei Let’s Encrypt heißen sie cert.pem und fullchain.pem. Die Namen sind keine Zierde.
cert.pem dort einzutragen, wo fullchain.pem hingehört, ist die häufigste TLS-Fehlkonfiguration überhaupt, und der Grund liegt auf der Hand: Die Seite geht danach einwandfrei auf. Nichts warnt, nichts protokolliert, und das Zertifikat ist tatsächlich gültig.
Die Lücke besteht nur für Clients, die sich weigern, sie selbst zu schließen.
Warum der Browser verzeiht
In einem Zertifikat steckt ein Feld namens Authority Information Access. Darin steht unter anderem eine Adresse: die Stelle, an der sich das ausstellende Zwischenzertifikat herunterladen lässt. Browser nutzen sie. Fehlt der Kette ein Glied, holen sie das fehlende Zertifikat von dieser Adresse, vervollständigen den Pfad und zeigen das Schloss.
Manche Browser speichern zusätzlich Zwischenzertifikate, die sie auf anderen Seiten gesehen haben. Dadurch ist das Problem schwerer nachzustellen: Ein Server mit unvollständiger Zertifikatskette kann auf zwei Rechnern korrekt zu funktionieren scheinen, aber auf jedem aus einem anderen Grund.
curl, OpenSSL und die meisten HTTP-Bibliotheken in Python, PHP, Go und Java tun das nicht. Diese Clients lesen dasselbe Feld und übergehen es, denn ihm zu folgen macht aus einem Handshake zwei Netzumläufe und hängt mitten in eine Verbindung, die direkt sein soll, die Abhängigkeit von einem fremden Host. Die Weigerung ist Absicht.
Daraus entsteht ein Fehlerbild mit sehr klarer Form: Die Website funktioniert, der Verkehr zwischen Maschinen nicht. Ein Zahlungsdienstleister, der einen Webhook aufruft, eine Überwachungssonde, eine App, ein Partner, der eine API abfragt – alle melden unable to get local issuer certificate, während der Browser überhaupt nichts anzeigt.

Was zwölf Seiten wirklich senden
Zwölf bekannte Adressen wurden am 2. September 2026 gemessen, gelesen wurde allein, was der Server im Handshake anbot.
Sechs sendeten drei Zertifikate, sechs vier. Keine einzige sendete das Blatt allein. Die Laufzeit war bei allen zwölf gleich: 90 Tage, wie sie die automatisierten Aussteller vergeben und worauf sich die Branche eingependelt hat. Die Restgültigkeit lag zwischen 31 und 88 Tagen, im Mittel bei 70 – die Streuung, die eine automatische Erneuerung bei zwei Dritteln der Laufzeit erzeugt.
Elf der zwölf handelten TLS 1.3 aus, eine blieb bei 1.2. Keine nahm TLS 1.0 an. Neun verwendeten einen ECDSA-Schlüssel statt RSA. Acht Zertifikate kamen von Let’s Encrypt, drei von Google Trust Services, eines von Sectigo.
Weit stärker als alles andere schwankte die Zahl der Namen im Zertifikat: einer am unteren Ende, 41 bei wikipedia.org. Diese Zahl ist kein Gütezeichen, sondern nur ein Hinweis darauf, wie der Bestand organisiert ist.
Namen, nicht der Common Name
Ein zweiter Fehler sieht von außen ähnlich aus. Das Zertifikat ist vollständig und gültig, nur steht der Host nicht darin.
Aktuelle Browser ignorieren den Common Name vollständig und vergleichen ausschließlich die Subject Alternative Names. Ein Zertifikat für example.com, in dem www.example.com fehlt, deckt genau eine der beiden Adressen ab, und die Weiterleitung zwischen ihnen scheitert, bevor sie beginnt.
Platzhalter decken eine Ebene und keine weitere. *.example.com passt auf www.example.com, aber weder auf example.com selbst noch auf a.b.example.com. Beide Hälften dieses Satzes kosten regelmäßig jemanden einen Ausfall.
Lesen ohne Browser
Der Unterschied, auf den es ankommt, ist im Browser nicht zu sehen, gerade weil der Browser ihn repariert. Nötig ist ein Client, der das nicht tut.
openssl s_client -connect example.com:443 -servername example.com < /dev/null
curl -vI https://example.com/
In der Ausgabe von OpenSSL listet der Block zur Zertifikatskette auf, was der Server gesendet hat. Ein einziger Eintrag heißt: Das Zwischenzertifikat fehlt, unabhängig davon, ob die Prüfzeile darunter ok meldet – ein lokal zwischengespeichertes Zwischenzertifikat kann auch sie irreführend machen. Verlässlich ist das Zählen dessen, was ankam.
Dieselbe Lesart ohne Kommandozeile liefert der TLS-Zertifikat- und Handshake-Inspector in der Toolbox: Er öffnet den Handshake, verifiziert einmal mit dem Vertrauensspeicher und einmal ohne, listet die Kette genau so auf, wie der Server sie gesendet hat, und probiert danach jede TLS-Version einzeln. Die Lücke erscheint als Kette der Länge eins, daneben eine Verifikation, die aus einem lesenswerten Grund gescheitert ist.
Fragen und Antworten
Gehört das Wurzelzertifikat mit in die Kette, die der Server sendet?
Nein. Der Client muss der Wurzel ohnehin schon vertrauen, eine mitgeschickte Wurzel beweist also nichts und kostet bei jedem Handshake nur Bytes; RFC 8446 erlaubt ausdrücklich, sie wegzulassen. Entscheidend ist allein, dass jedes Zwischenzertifikat zwischen Blatt und Wurzel dabei ist, und genau das enthält fullchain.pem.
Die Kette lief monatelang, plötzlich meldet ein Webhook-Absender unable to get local issuer certificate. Woran kann das liegen?
Wenn am Server nichts geändert wurde, kommen vor allem zwei Ursachen in Frage, eine auf jeder Seite der Verbindung:
- Das Zwischenzertifikat hat gewechselt. Zertifizierungsstellen stellen von Zeit zu Zeit über neue Zwischenzertifikate aus. Eine Konfiguration, die cert.pem mit einer einmal von Hand abgelegten Zwischendatei kombiniert, schickt nach der nächsten Erneuerung ein Glied, das nicht mehr zum Blatt passt; der Pfad hat dann dieselbe Lücke wie ganz ohne Zwischenzertifikat. fullchain.pem wird bei jeder Erneuerung mit dem passenden Zwischenzertifikat neu geschrieben und hat dieses Problem nicht.
- Der Vertrauensspeicher des Clients ist veraltet. Endet die Kette nach einer Erneuerung bei einer anderen Wurzel, fehlt diese womöglich in alten Betriebssystemen oder in Container-Images, die lange nicht neu gebaut wurden. Die Meldung ist dieselbe, obwohl der Server alles richtig sendet.
Welche der beiden zutrifft, klärt erneutes Zählen: Kommen bei openssl s_client Blatt und Zwischenzertifikat an und verifiziert ein aktuelles System die Kette, liegt das Problem im Vertrauensspeicher des Absenders, und helfen kann nur ein Update seiner CA-Liste.