SSD Nodes Learn 🎉 VPS ab $5.50/Monat
Anleitungen Matt ConnorVon Matt Connor

nginx mit mTLS: Client-Zertifikate einrichten

Sichern Sie ein Admin-Panel mit mTLS: Erstellen Sie mit openssl eine private CA, stellen Sie Client-Zertifikate aus und konfigurieren Sie nginx für ihre Prüfung.

Was mTLS bewirkt

Mutual TLS, meist als mTLS bezeichnet, veranlasst nginx, von jedem Client ein Zertifikat anzufordern. nginx weist die Anfrage zurück, wenn das Zertifikat fehlt oder nicht von einer von Ihnen kontrollierten Certificate Authority (CA) ausgestellt wurde. Die Prüfung erfolgt während des TLS-Handshakes (Transport Layer Security). Ein Client ohne gültiges Clientzertifikat erreicht Ihre Anwendung daher gar nicht erst. Das ist der entscheidende Vorteil: Ein Administrationsbereich oder ein Metrik-Endpunkt kann ohne Anmeldeseite und ohne für Bots erratbare Zugangsdaten im öffentlichen Internet erreichbar sein.

Die Konfiguration ist klein. Sie benötigen eine mit openssl erstellte private CA, ein Zertifikat pro Person und drei Direktiven im nginx-server-Block. Ob diese Lösung ein Jahr lang zuverlässig funktioniert, hängt vom Betrieb ab. Deshalb behandelt dieser Leitfaden überwiegend Gültigkeitszeiträume, Sperrungen, personenbezogene Zertifikate und das Vorgehen, wenn ein Client abgewiesen wird und die Ursache nicht erkennbar ist.

Zwei Ketten, nicht eine

In einer mTLS-Konfiguration gibt es zwei Zertifikatsketten, die nichts miteinander zu tun haben. Sie zusammenzufassen, ist der erste Fehler, den fast alle machen.

Die erste Kette gehört zum Server. Ihr VPS präsentiert ein Zertifikat für admin.example.com, das von einer öffentlichen CA wie Let's Encrypt ausgestellt wurde. Der Browser prüft es gegen den Root-Zertifikatsspeicher, der mit dem Betriebssystem ausgeliefert wird. mTLS ändert an diesem Teil nichts. Wenn certbot dieses Zertifikat heute für Sie ausstellt, lassen Sie es unverändert: siehe ein Let's-Encrypt-Zertifikat für nginx mit certbot ausstellen.

Die zweite Kette gehört zum Client. Sie erstellen eine eigene kleine CA, signieren für jede zugriffsberechtigte Person ein Zertifikat und weisen nginx an, bei der Prüfung von Clients ausschließlich dieser CA zu vertrauen. Kein öffentlicher Root-Zertifikatsspeicher kennt Ihre CA, und das ist auch nicht erforderlich. Die einzige Instanz, die ihr vertrauen muss, ist nginx, und zwar über die Datei ssl_client_certificate.

Daher wirkt sich ssl_client_certificate niemals auf das von nginx präsentierte Zertifikat aus. Ebenso wirkt sich die Let's-Encrypt-Kette niemals darauf aus, welche Clients zugelassen werden. ssl_client_certificate auf fullchain.pem zu verweisen, bewirkt nicht das, wonach es aussieht: Diese Direktive gibt an, von welchen Ausstellern ein Client-Zertifikat stammen darf. Das betrifft die andere Seite der Verbindung. Den Server selbst für ausgehende Verbindungen Ihrer CA vertrauen zu lassen, ist eine separate Aufgabe. Sie wird unter Ihre eigene CA zum Ubuntu-Vertrauensspeicher hinzufügen beschrieben. Der System-Vertrauensspeicher ist nicht der Speicher, den nginx beim Prüfen eines Clients verwendet.

Eigene Client-CA mit openssl erstellen

Erstellen Sie die CA an einem anderen Ort als auf dem Webserver. nginx benötigt nur das öffentliche Zertifikat der CA. Der private CA-Schlüssel signiert neue Clientzertifikate. Wenn er auf einem aus dem Internet erreichbaren System liegt, kann ein Angreifer nach einem einzigen Einbruch nach Belieben gültige Clientzertifikate für sich selbst ausstellen.

mkdir -p ~/client-ca/certs ~/client-ca/newcerts ~/client-ca/private ~/client-ca/csr
cd ~/client-ca
chmod 700 private
touch index.txt
echo 1000 > serial
echo 1000 > crlnumber

index.txt, serial und crlnumber bilden die CA-Datenbank. openssl ca startet ohne diese Dateien nicht. Sie ermöglichen außerdem den späteren Widerruf. Eine Widerrufsliste enthält Seriennummern. Deshalb muss die CA speichern, welche Seriennummer welchem Zertifikat zugewiesen wurde.

Erstellen Sie ~/client-ca/openssl.cnf. Setzen Sie dir auf den tatsächlichen Pfad dieses Verzeichnisses, da openssl ca ~ nicht expandiert.

[ ca ]
default_ca = client_ca

[ client_ca ]
dir               = /home/you/client-ca
database          = $dir/index.txt
new_certs_dir     = $dir/newcerts
certificate       = $dir/ca.crt
private_key       = $dir/private/ca.key
serial            = $dir/serial
crlnumber         = $dir/crlnumber
default_md        = sha256
default_days      = 365
default_crl_days  = 30
policy            = policy_loose
rand_serial       = no
unique_subject    = no
email_in_dn       = no

[ policy_loose ]
commonName              = supplied
countryName             = optional
stateOrProvinceName     = optional
organizationName        = optional
organizationalUnitName  = optional
emailAddress            = optional

[ client_ext ]
basicConstraints        = CA:FALSE
keyUsage                = critical, digitalSignature, keyEncipherment
extendedKeyUsage        = clientAuth
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid,issuer

Jetzt erstellen Sie den CA-Schlüssel und das selbstsignierte Zertifikat:

openssl genrsa -aes256 -out private/ca.key 4096
chmod 600 private/ca.key
openssl req -x509 -new -key private/ca.key -sha256 -days 3650 \
  -subj "/O=Example Ops/CN=Example Ops Client CA" \
  -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign" \
  -out ca.crt

-aes256 schützt den CA-Schlüssel mit einer Passphrase. Bei jedem Signiervorgang wird diese abgefragt. Genau das ist beabsichtigt. Prüfen Sie das Ergebnis:

openssl x509 -in ca.crt -noout -subject -dates -ext basicConstraints

Der Subject sollte Ihre CA enthalten. Die Gültigkeitsdauer sollte zehn Jahre betragen. Die Erweiterungszeile sollte CA:TRUE, pathlen:0 lauten. pathlen:0 bedeutet, dass diese CA Endzertifikate signieren darf, aber keine weitere CA signieren darf. Dadurch bleibt die Zertifikatskette genau eine Ebene tief, und Sie können ssl_verify_depth unverändert lassen.

Ein Clientzertifikat pro Person

Ein Zertifikat pro Person. Niemals ein gemeinsames Zertifikat für ein Team, weil ein gemeinsames Zertifikat nicht widerrufen werden kann, ohne alle auszusperren. Außerdem ist damit nicht erkennbar, wer den Aufruf ausgeführt hat.

openssl genrsa -out private/alice.key 2048
openssl req -new -key private/alice.key -out csr/alice.csr \
  -subj "/O=Example Ops/CN=alice"
openssl ca -config openssl.cnf -extensions client_ext \
  -days 365 -notext -in csr/alice.csr -out certs/alice.crt

openssl ca gibt das Zertifikat aus, das signiert werden soll, fragt nach der Passphrase der CA, verlangt zweimal eine Bestätigung und hängt anschließend eine Zeile an index.txt an. Fügen Sie -batch hinzu, wenn Sie den Vorgang skripten. Der Abschnitt client_ext ist wichtig, weil er eine bestimmte Zeile enthält: extendedKeyUsage = clientAuth. Ein Zertifikat, dessen Extended-Key-Usage-Auflistung nur serverAuth enthält, wird als ungeeignet für die Clientauthentifizierung abgelehnt. Geben Sie den Verwendungszweck daher ausdrücklich an, statt darauf zu hoffen.

Prüfen Sie das Schlüsselpaar gegen die CA, bevor Sie es weitergeben:

openssl verify -CAfile ca.crt certs/alice.crt

Damit wird certs/alice.crt: OK ausgegeben. Jede andere Ausgabe bedeutet, dass Zertifikat und CA nicht zusammenpassen. Keine nginx-Konfiguration kann diesen Fehler beheben.

Bündeln Sie den Schlüssel und das Zertifikat in einer Datei, die ein Browser importieren kann:

openssl pkcs12 -export -inkey private/alice.key -in certs/alice.crt \
  -name "alice at example ops" -out alice.p12

Beim Export werden Sie nach einem Passwort gefragt. Es schützt die Datei während der Übertragung. Senden Sie die Datei und das Passwort über unterschiedliche Kanäle. Übergeben Sie den Personen außerdem .p12 statt eines ungeschützten .key. Sie können -certfile ca.crt hinzufügen, um die CA in das Bundle aufzunehmen. nginx benötigt sie jedoch nicht: nginx enthält bereits ca.crt. Ein Zertifikat, das direkt von dieser CA signiert wurde, wird daher selbstständig überprüft.

OpenSSL 3, das Ubuntu 24.04 ausliefert, schreibt PKCS#12-Dateien mit aktueller Verschlüsselung. Browser und Betriebssysteme, die im August 2026 verwendet werden, können diese Dateien lesen. Wenn ein alter Importer die Datei ablehnt, exportieren Sie sie mit -legacy erneut. Damit werden die älteren Algorithmen verwendet, die der Importer erwartet. Lesen Sie zuerst die Meldung des Importers, bevor Sie dieses Flag verwenden.

nginx mit ssl_client_certificate und ssl_verify_client konfigurieren

Kopieren Sie das CA-Zertifikat, und nur das CA-Zertifikat, auf den Server.

scp ca.crt user@admin.example.com:/tmp/client-ca.crt
ssh user@admin.example.com \
  'sudo install -o root -g root -m 644 /tmp/client-ca.crt /etc/nginx/client-ca.crt'

Der Modus 644 ist hier korrekt. Ein CA-Zertifikat ist öffentlich. Der CA-Schlüssel bleibt auf Ihrer Workstation.

Fügen Sie anschließend drei Direktiven in den Server-Block ein, der TLS bereits terminiert:

server {
    listen 443 ssl;
    server_name admin.example.com;

    ssl_certificate     /etc/letsencrypt/live/admin.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/admin.example.com/privkey.pem;

    ssl_client_certificate /etc/nginx/client-ca.crt;
    ssl_verify_client      on;
    ssl_verify_depth       1;

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

ssl_verify_depth 1 ist der nginx-Standard. Damit muss das Clientzertifikat direkt von der CA in dieser Datei signiert sein. Erhöhen Sie den Wert nur, wenn Sie eine Zwischenzertifizierungsstelle hinzufügen. nginx sendet während des Handshakes außerdem die Antragsteller-Namen aus ssl_client_certificate an den Client. Dadurch weiß ein Browser, welches seiner Zertifikate er anbieten soll. Deshalb verwenden Sie ssl_client_certificate statt ssl_trusted_certificate. Beide prüfen auf dieselbe Weise, aber ssl_trusted_certificate sendet keine Liste.

Ubuntu 24.04 enthält nginx 1.24. Dort wird HTTP/2 mit listen 443 ssl http2; in der listen-Zeile aktiviert. Ab nginx 1.25.1 ist diese Form veraltet. HTTP/2 wird dann mit der eigenen Direktive http2 on; aktiviert. Keine der beiden Varianten ändert die Zertifikatsprüfung.

Laden Sie die Konfiguration neu und prüfen Sie das Ergebnis:

sudo nginx -t && sudo systemctl reload nginx
curl -i https://admin.example.com/

nginx -t gibt syntax is ok und test is successful aus. Der Aufruf mit curl enthält kein Zertifikat. Er sollte daher mit 400 Bad Request und dem Inhalt No required SSL certificate was sent zurückkommen. nginx weist die Anfrage an seiner eigenen Zugriffssperre zurück. Damit ist die Konfiguration aktiv, und die Anwendung wurde nicht aufgerufen. Testen Sie es nun korrekt:

curl --cert certs/alice.crt --key private/alice.key https://admin.example.com/

Damit sollte die Antwort zurückkommen, die Ihre Anwendung bereitstellt.

Warum die Zugriffssperre in den Server-Block gehört

Das Zertifikat wird während des TLS-Handshakes ausgetauscht, bevor nginx eine Request-Zeile gelesen hat. Zu diesem Zeitpunkt weiß nginx daher nicht, in welchem location die Anfrage landen wird. ssl_verify_client on; innerhalb eines location fordert den Client auf, die Verbindung während der Übertragung neu auszuhandeln. TLS 1.3 hat die Neuaushandlung entfernt, und HTTP/2 verbietet sie. Auf einem aktuellen Stack schlägt dieses Muster daher fehl, statt eine Aufforderung anzuzeigen.

Legen Sie den Geltungsbereich selbst fest. Fordern Sie das Zertifikat auf Server-Ebene an und entscheiden Sie anschließend für jeden Location-Block:

ssl_verify_client optional;

location /metrics {
    if ($ssl_client_verify != SUCCESS) { return 403; }
    proxy_pass http://127.0.0.1:9090;
}

location /healthz {
    proxy_pass http://127.0.0.1:8080;
}

$ssl_client_verify enthält SUCCESS, oder NONE, wenn der Client kein Zertifikat gesendet hat, oder FAILED: gefolgt von einer Begründung. Mit optional fordert nginx ein Zertifikat an und prüft es nur, wenn eines gesendet wird. Dadurch kann der öffentliche Pfad /healthz funktionieren, während /metrics geschlossen bleibt. Ein gesendetes Zertifikat, dessen Prüfung fehlschlägt, wird von nginx an dieser Stelle trotzdem abgelehnt. Wenn Sie ein fehlerhaftes Zertifikat stattdessen selbst untersuchen möchten, verwenden Sie optional_no_ca. Ihr eigener Test muss dann jeden Wert außer SUCCESS als Ablehnung behandeln.

nginx verwendet dafür nicht standardisierte Statuscodes. Mit error_page können Sie diese abfangen, sodass ein abgelehnter Besucher eine Erklärung statt einer leeren 400-Antwort erhält:

error_page 495 496 = @needcert;

location @needcert {
    default_type text/plain;
    return 200 "This host requires a client certificate. Ask ops for one.\n";
}

495 bedeutet, dass die Prüfung des Clientzertifikats fehlgeschlagen ist. 496 bedeutet, dass der Client kein Zertifikat vorgelegt hat. Halten Sie diese Seite als einfachen Text, weil die lesende Person keine Sitzung und kein Konto hat.

Wie installiere ich das Clientzertifikat in einem Browser?

Firefox verwendet einen eigenen Zertifikatsspeicher: Öffnen Sie Einstellungen, dann Datenschutz und Sicherheit, anschließend Zertifikate anzeigen, wechseln Sie zur Registerkarte Ihre Zertifikate, wählen Sie Importieren aus und wählen Sie dann die .p12 aus. Geben Sie das zugehörige Kennwort ein.

Chrome und Edge verwenden unter Windows und macOS den Zertifikatsspeicher des Betriebssystems. Wenn Sie die Datei .p12 öffnen, startet der Importassistent des Systems. Unter Linux liest Chrome eine separate NSS-Datenbank (Network Security Services) in Ihrem Home-Verzeichnis. Der zuverlässige Weg führt über das Befehlszeilentool:

sudo apt install -y libnss3-tools
pk12util -d sql:$HOME/.pki/nssdb -i alice.p12

Laden Sie die Website anschließend. Der Browser fragt, welches Zertifikat gesendet werden soll. Chrome speichert diese Auswahl für die Dauer der Browsersitzung. Starten Sie den Browser neu, wenn die Abfrage erneut angezeigt werden soll. Das Zertifikat ist in einem Browserprofil auf einem Gerät gespeichert. Ein in Firefox importiertes Zertifikat ist daher für Chrome nicht sichtbar. Beide Zertifikate sind auf Ihrem Telefon nicht verfügbar.

Mit curl --cert testen

Debuggen Sie mit curl, weil das Tool protokolliert, welche Aktionen es ausgeführt hat.

curl -v --cert certs/alice.crt --key private/alice.key https://admin.example.com/

Sie können das Zertifikat und den Schlüssel in einer PEM-Datei zusammenfassen und diese als --cert alice.pem übergeben. Wenn der Schlüssel mit einer Passphrase geschützt ist, fordert curl diese an. curl akzeptiert auch --cert alice.pem:passphrase. Dadurch landet die Passphrase jedoch in der Shell-Historie. Verwenden Sie daher die Abfrage.

Führen Sie zwei Prüfungen durch, bevor Sie nginx die Ursache zuordnen. Erstens müssen Zertifikat und Schlüssel zusammengehören:

openssl x509 -noout -pubkey -in certs/alice.crt | openssl sha256
openssl pkey -pubout -in private/alice.key | openssl sha256

Zwei identische Hashes bedeuten, dass die Dateien zusammengehören. Zwei unterschiedliche Hashes bedeuten, dass Sie die Dateien zweier Personen verwechselt haben. Kein Client nennt Ihnen diese Ursache.

Zweitens sollte der Server Ihre CA anfordern:

openssl s_client -connect admin.example.com:443 -servername admin.example.com </dev/null

Suchen Sie in der Ausgabe nach dem Block Acceptable client certificate CA names und darin nach dem Subject Ihrer CA. Fehlt dieser Block vollständig, fordert nginx im antwortenden Server-Block kein Zertifikat an. Ihre Direktiven befinden sich dann in einem anderen Server-Block, häufig im Default-Server.

Den Client-CN an die Anwendung weitergeben

Das Zertifikat gibt an, wer die Anfrage gestellt hat. Die Anwendung hinter dem Proxy kann die TLS-Schicht jedoch nicht sehen. Daher muss nginx den Namen weitergeben.

map $ssl_client_s_dn $client_cn {
    default              "";
    "~,?CN=(?<cn>[^,]+)" $cn;
}

$ssl_client_s_dn enthält den Subject Distinguished Name im RFC-2253-Format. Er sieht beispielsweise wie CN=alice,O=Example Ops aus. Das Mapping übernimmt das CN-Feld in $client_cn. Verwenden Sie für den CN einen einfachen Benutzernamen. Ein Komma innerhalb eines CN wird in diesem Format maskiert. Der kurze reguläre Ausdruck oben verarbeitet diese Maskierung nicht.

location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host                 $host;
    proxy_set_header X-Forwarded-For      $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto    $scheme;
    proxy_set_header X-Client-Cert-CN     $client_cn;
    proxy_set_header X-Client-Cert-Serial $ssl_client_serial;
}

proxy_set_header ersetzt jeden Header dieses Namens, den der Aufrufer gesendet hat. Dadurch kann niemand X-Client-Cert-CN über diesen Location-Block fälschen. Zwei Bedingungen stellen das sicher. nginx übernimmt proxy_set_header von der äußeren Ebene nur, wenn die innere Ebene keine eigenen Einträge definiert. Eine zweite Location mit einer proxy_set_header-Zeile verliert daher stillschweigend alle darüber gesetzten Header, einschließlich dieses Headers. Außerdem darf die Anwendung nur über nginx erreichbar sein. Das bedeutet, dass sie an 127.0.0.1 statt an 0.0.0.0 gebunden wird. Eine Anwendung an einem öffentlichen Port würde den gefälschten Header direkt aus dem Internet einlesen. Die Proxy-Konfiguration dafür wird in einer zeilenweisen Erklärung einer nginx-Reverse-Proxy-Konfiguration beschrieben. Wenn die Anwendung das vollständige Zertifikat statt eines Namens benötigt, überträgt $ssl_client_escaped_cert es URL-kodiert und sicher innerhalb eines Headers.

Wie widerrufe ich ein einzelnes Clientzertifikat?

Wenn jemand das Unternehmen verlässt oder ein Laptop verloren geht, widerrufen Sie nur dieses eine Zertifikat. Alle anderen können weiterarbeiten. Genau deshalb wird pro Person ein eigenes Zertifikat ausgestellt.

cd ~/client-ca
openssl ca -config openssl.cnf -revoke certs/alice.crt
openssl ca -config openssl.cnf -gencrl -out crl.pem

Der erste Befehl ändert die Zeile für diese Seriennummer in index.txt von V in R. Der zweite Befehl schreibt eine Certificate Revocation List (CRL), also eine signierte Datei mit den widerrufenen Seriennummern. Übertragen Sie diese Datei auf den Server und verweisen Sie nginx mit ssl_crl /etc/nginx/client-ca.crl; neben den anderen Direktiven darauf.

scp crl.pem user@admin.example.com:/tmp/client-ca.crl
ssh user@admin.example.com 'sudo install -m 644 /tmp/client-ca.crl /etc/nginx/client-ca.crl && sudo nginx -t && sudo systemctl reload nginx'

Hier liegt die Falle, durch die der Zugriff für alle gesperrt wird. Eine CRL enthält ein nextUpdate-Datum. Dieses wird mit default_crl_days festgelegt und beträgt in der obigen Konfiguration 30. Nach Ablauf dieses Datums behandelt OpenSSL die Liste als veraltet und schlägt die Prüfung jedes Clientzertifikats mit CRL has expired fehl, nicht nur die Prüfung des widerrufenen Zertifikats. nginx liest die Datei beim Laden seiner Konfiguration ein. Eine aktualisierte CRL auf der Festplatte wirkt daher erst nach einem Reload. Erzeugen Sie die CRL regelmäßig neu und führen Sie den Reload deutlich innerhalb dieses Zeitfensters durch, beispielsweise wöchentlich bei 30 Tagen. Prüfen Sie die Datumsangaben, bevor Sie die Datei kopieren:

openssl crl -in crl.pem -noout -lastupdate -nextupdate

Für wenige Benutzer gibt es eine kleinere Lösung. Die CA gehört Ihnen. nginx kann daher eine Seriennummer direkt ablehnen und die CRL-Verwaltung überspringen:

map $ssl_client_serial $revoked {
    default 0;
    "1002"  1;
}

Kombinieren Sie das in der Location mit if ($revoked) { return 403; }. Diese Lösung hat kein Ablaufdatum, das vergessen werden kann. Sie wird außerdem nicht verteilt. Andere Systeme, die Ihrer CA vertrauen, wissen daher nichts davon. Bei einem nginx vor einer einzelnen Anwendung ist dies die einfache und sachgerechte Lösung. Wechseln Sie zur CRL, sobald es mehr als ein Zugriffstor gibt.

Wie lange sollten Client-Zertifikate gültig sein?

Legen Sie Client-Zertifikate für ein Jahr aus. Wenn Sie den zusätzlichen Aufwand für die Neuausstellung akzeptieren, können Sie auch eine kürzere Laufzeit wählen. Der Ablauf ist hier ein unauffälliger Fehler, weil der Inhaber vorher nicht gewarnt wird. Eines Morgens öffnet die Person das Bedienfeld, nginx lehnt die Verbindung ab, und der Browser beschreibt die Ablehnung mit seinen eigenen Worten. Das Wort „abgelaufen“ kommt darin nur selten vor. Legen Sie die CA auf eine Laufzeit von zehn Jahren aus. Notieren Sie das Ablaufdatum an einer Stelle, die Sie tatsächlich prüfen. Wenn das CA-Zertifikat abläuft, kann am selben Tag kein Zertifikat mehr verifiziert werden, das darauf basiert.

Mit zwei Befehlen behalten Sie den Überblick:

openssl x509 -in certs/alice.crt -noout -subject -serial -enddate
awk -F'\t' '{print $1, $2, $4}' ~/client-ca/index.txt

Die erste Spalte von index.txt enthält den Status: V für gültig, R für widerrufen und E für abgelaufen. Die zweite Spalte enthält das Ablaufdatum im Format YYMMDDHHMMSSZ. Die vierte Spalte enthält die Seriennummer. Diese Datei ist Ihr einziges Verzeichnis darüber, wer welches Zertifikat besitzt. Sichern Sie sie zusammen mit dem CA-Schlüssel und behandeln Sie beide als vertrauliche Daten.

Eine Erneuerung erstellt ein neues Zertifikat. Sie verlängert das alte Zertifikat nicht. Erzeugen Sie einen neuen Schlüssel und eine CSR (Certificate Signing Request), signieren Sie sie, übergeben Sie das neue Zertifikat und widerrufen Sie das alte, sobald die betreffende Person bestätigt, dass das neue Zertifikat funktioniert.

Wovor mTLS schützt und wovor nicht

mTLS beseitigt den nicht authentifizierten Zugriff. Ein Scanner, der Ihren Hostnamen findet, wird während des Handshakes abgewiesen. Er sendet daher keine HTTP-Anfrage, sieht kein Anmeldeformular und kann kein gestohlenes Passwort dagegen ausprobieren. Für Credential-Stuffing gibt es kein Ziel. Eine Schwachstelle im Anmeldeablauf der Anwendung ist für Personen ohne Zertifikat nicht erreichbar. Außerdem entfällt das gemeinsame Geheimnis, das Personen in Chats kopieren. Ein privater Schlüssel ist eine Datei, die sich nicht so leicht versehentlich kopieren lässt.

Gegen einen kompromittierten Client unternimmt mTLS nichts. Schadsoftware auf einem Laptop hat Zugriff auf die Schlüsseldatei. Sie kennt auch die Passphrase, sobald der Besitzer sie eingibt. Für den Server sieht dieser Angreifer genauso aus wie ein legitimer Benutzer. Ein Zertifikat weist den Besitz einer Datei nach, nicht die Anwesenheit einer Person. Das .p12-Passwort und die vollständige Festplattenverschlüsselung bleiben daher wichtig.

mTLS ist außerdem keine Autorisierung. Jedes gültige Zertifikat erhält Zugriff auf alles, was der Server-Block bereitstellt, sofern Sie $client_cn nicht prüfen und den Wert nicht auswerten. Standardmäßig haben zwei Zertifikatsinhaber identische Zugriffsrechte.

mTLS schützt außerdem nur den Pfad durch nginx. Wenn die Anwendung zusätzlich an einem öffentlichen Port lauscht, ist mTLS davor nur Dekoration. Binden Sie die Anwendung an 127.0.0.1 und lassen Sie ihren Port in der Firewall geschlossen. Der andere Zugang zu demselben System ist SSH. Er verdient dieselbe Aufmerksamkeit. Eine Anleitung dazu finden Sie unter SSH-Zugriff auf Ihrem VPS absichern.

Es gibt noch eine letzte Einschränkung. Sie zeigt sich an dem Tag, an dem Sie mTLS aktivieren. Alles, was kein Zertifikat vorlegen kann, funktioniert nicht mehr: ein Uptime-Monitor, ein Webhook eines Zahlungsanbieters, ein RSS-Reader oder eine mobile Anwendung ohne erreichbaren Zertifikatsspeicher. Entscheiden Sie darüber, bevor Sie ssl_verify_client on setzen. Der Ausfall ist vollständig und bleibt auf der jeweiligen Gegenseite still.

Wenn ein Client abgewiesen wird, lesen Sie die Meldung des Clients

Die Meldung eines abgewiesenen Clients hängt vom Browser, der curl-Version und der darunterliegenden TLS-Bibliothek ab. Lesen Sie daher die Ausgabe Ihres eigenen Clients, statt sie mit einer an anderer Stelle dokumentierten Meldung abzugleichen. Die relevante Information steht auf dem Server.

sudo tail -n 50 /var/log/nginx/error.log

Ein abgelehntes Zertifikat hinterlässt eine Zeile mit client SSL certificate verify error, gefolgt von der von OpenSSL angegebenen Begründung. Diese Begründung ist die entscheidende Information. In der Regel kommt nur eine von wenigen Ursachen infrage. Das Zertifikat stammt von einer anderen CA als die in ssl_client_certificate angegebene Datei. Das Zertifikat liegt außerhalb seines Gültigkeitszeitraums. Die CRL auf dem Server hat ihr nextUpdate überschritten und schlägt deshalb nun bei jedem Client fehl, nicht nur bei einem.

Wenn der Browser überhaupt kein Zertifikat anbietet, liegt das Problem vor der Prüfung. nginx sendet während des Handshakes die Namen der akzeptierten Aussteller. Der Browser hat in seinem Zertifikatsspeicher kein passendes Zertifikat gefunden und konnte daher keines anbieten. Importieren Sie .p12 erneut in das Profil, mit dem Sie tatsächlich browsen.

Ein weiterer Fall ist erwähnenswert. Wenn Sie mit einem einzelnen selbstsignierten Client-Zertifikat getestet haben, statt mit einem von Ihrer CA signierten Zertifikat, kann die Prüfung nicht erfolgreich sein. nginx prüft die Signatur anhand der CA-Datei, und ein selbstsigniertes Zertifikat ist darin nicht enthalten. Die Vorgehensweise zum Erstellen des Zertifikats ist dieselbe wie unter Erzeugen eines selbstsignierten Zertifikats unter Ubuntu. Für mTLS ist lediglich der zusätzliche Schritt erforderlich, bei dem Ihre CA das Zertifikat signiert.

FAQ

Benötige ich auch bei mTLS ein Let's-Encrypt-Zertifikat?

Ja. Die beiden Zertifikate sind unabhängig voneinander. Ihr Server stellt sein eigenes Zertifikat bereit, damit der Browser dem Hostnamen vertraut. Dieses Zertifikat muss weiterhin von einer CA stammen, die der Browser bereits kennt. Ihre Client-CA ist eine separate private Zertifikatskette. Sie wird nur verwendet, um zu prüfen, wer eine Verbindung herstellt. Das Setzen von ssl_client_certificate ändert nichts an dem Zertifikat, das nginx bereitstellt. Es darf nicht auf Ihre Let's-Encrypt-Kette verweisen.

Warum fordert mich mein Browser nie auf, ein Zertifikat auszuwählen?

nginx sendet während des Handshakes eine Liste akzeptierter Aussteller. Diese Liste wird aus der Datei in ssl_client_certificate erstellt. Ein Browser bietet nur Zertifikate an, deren Aussteller in dieser Liste enthalten ist. Eine fehlende Aufforderung bedeutet daher, dass der Browser kein Zertifikat Ihrer CA enthält. Der Import wurde möglicherweise in ein anderes Browserprofil durchgeführt, oder das Zertifikat wurde von einer anderen CA signiert als der auf dem Server installierten. Führen Sie openssl s_client -connect admin.example.com:443 aus und suchen Sie in der Ausgabe nach den Namen der akzeptierten Client-Zertifikat-CAs. So sehen Sie, welche CA der Server tatsächlich anfordert.

Kann ich ein Client-Zertifikat nur für eine URL verlangen?

Nicht mit ssl_verify_client on innerhalb eines location. Das Zertifikat wird während des Handshakes ausgetauscht, bevor nginx den angeforderten Pfad kennt. Die dafür erforderliche Neuaushandlung gibt es in TLS 1.3 nicht mehr und sie ist in HTTP/2 verboten. Setzen Sie ssl_verify_client optional; im Server-Block. Prüfen Sie anschließend in jeder geschützten Location $ssl_client_verify und geben Sie 403 zurück, wenn der Wert nicht SUCCESS ist.

Wie entziehe ich einer Person den Zugriff?

Widerrufen Sie das Zertifikat mit openssl ca -revoke, erzeugen Sie die Liste mit openssl ca -gencrl neu, kopieren Sie sie auf den Server und laden Sie nginx neu, damit die neue Datei eingelesen wird. Andere Benutzer sind davon nicht betroffen. Das funktioniert nur, wenn jede Person ein eigenes Zertifikat und kein gemeinsam verwendetes Zertifikat besitzt. Überprüfen Sie das nextUpdate-Datum der CRL. Eine abgelaufene CRL führt bei jedem Client zu einem fehlgeschlagenen Verifizierungsvorgang, nicht nur bei widerrufenen Clients.

Ersetzt mTLS eine Anmeldeseite?

Für den Zugriff ja: Ohne Zertifikat erreicht keine Verbindung die Anwendung. Es gibt daher kein Formular, das angegriffen werden kann, und kein Passwort, das erraten werden kann. Für die Identität innerhalb der Anwendung gilt das nicht. Ein Zertifikat weist nach, dass der Aufrufer eine Schlüsseldatei besitzt. Ein gestohlener Laptop ist damit ein gültiger Benutzer. Übergeben Sie den CN an das Backend, behalten Sie die vorhandenen Konten und Berechtigungen der Anwendung bei und behandeln Sie das Zertifikat als Zugangsschutz vor der Anwendung.

#tls#mtls#nginx#openssl#access-control