SSD Nodes Learn 🎉 VPS dès $5.50/mois
Guides Matt ConnorPar Matt Connor

mTLS avec nginx : certificats client et CA privée

Protégez un panneau d’administration avec mTLS : créez une CA privée avec openssl, émettez des certificats client et configurez nginx pour les refuser sans certificat valide.

Ce que fait mTLS

Mutual TLS, généralement appelé mTLS, demande à nginx un certificat pour chaque client et refuse la requête si ce certificat est absent ou n’a pas été émis par une autorité de certification (CA) que vous contrôlez. Le contrôle s’effectue pendant le handshake TLS (transport layer security). Un client qui ne présente pas de certificat valide n’atteint donc jamais votre application. C’est tout l’intérêt : un panneau d’administration ou un endpoint de métriques peut être exposé sur Internet sans page de connexion ni élément qu’un bot puisse deviner.

La mise en place est limitée. Il faut une CA privée créée avec openssl, un certificat par personne et trois directives dans le server block nginx. La pérennité de cette configuration dépend surtout de l’exploitation. Ce guide traite donc principalement des durées de validité, de la révocation, des certificats individuels et de la marche à suivre lorsqu’un client est refusé sans raison visible.

Deux chaînes, pas une

Il existe deux chaînes de certificats dans une configuration mTLS, et elles n’ont aucun lien entre elles. Les confondre est la première erreur que presque tout le monde commet.

La première chaîne est celle du serveur. Votre VPS présente un certificat pour admin.example.com, délivré par une AC publique comme Let’s Encrypt, et le navigateur le vérifie par rapport au magasin de certificats racines fourni avec le système d’exploitation. mTLS ne change rien à cette partie. Si certbot délivre aujourd’hui ce certificat pour vous, conservez-le exactement tel quel : consultez délivrer un certificat Let’s Encrypt pour nginx avec certbot.

La deuxième chaîne est celle du client. Vous créez votre propre petite AC, vous signez un certificat pour chaque personne qui doit accéder au service, puis vous indiquez à nginx de faire confiance à cette AC, et uniquement à celle-ci, lors de la vérification des clients. Aucun magasin de certificats racines public ne connaît votre AC, et ce n’est pas nécessaire. Le seul composant qui doit lui faire confiance est nginx, via le fichier ssl_client_certificate.

Ainsi, ssl_client_certificate n’a jamais d’incidence sur le certificat présenté par nginx, et la chaîne Let’s Encrypt n’a aucune incidence sur les clients autorisés à accéder au service. Pointer ssl_client_certificate vers fullchain.pem ne produit pas l’effet apparent : cette directive indique de quels émetteurs un certificat client peut provenir, c’est-à-dire l’autre extrémité de la connexion. Faire en sorte que le serveur lui-même fasse confiance à votre AC pour ses propres connexions sortantes est une tâche distincte, décrite dans ajouter votre propre AC au magasin de certificats de confiance d’Ubuntu, et nginx ne lit pas le magasin de certificats de confiance du système lorsqu’il vérifie un client.

Construire votre propre AC cliente avec openssl

Construisez l’AC sur une machine différente du serveur web. nginx a uniquement besoin du certificat public de l’AC. La clé privée de l’AC signe les nouveaux certificats clients. La conserver sur une machine exposée à Internet signifie qu’une compromission suffit à permettre à l’attaquant de créer à volonté des certificats clients valides pour lui-même.

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 et crlnumber constituent la base de données de l’AC. openssl ca refuse de fonctionner sans ces fichiers. Ils rendent également la révocation possible par la suite : une liste de révocation référence les numéros de série. L’AC doit donc mémoriser le certificat attribué à chaque client.

Créez ~/client-ca/openssl.cnf. Définissez dir avec le chemin réel de ce répertoire, car openssl ca ne développe pas ~.

[ 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

Créez maintenant la clé de l’AC et son certificat autosigné :

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 protège la clé de l’AC par une phrase secrète. Chaque opération de signature la demande donc. C’est précisément son rôle. Vérifiez ce que vous avez créé :

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

Le sujet doit correspondre à votre AC et la période de validité doit être de dix ans. La ligne d’extension doit contenir CA:TRUE, pathlen:0. pathlen:0 signifie que cette AC peut signer des certificats finaux, mais pas une autre AC. La chaîne reste ainsi limitée à un seul niveau et vous pouvez laisser ssl_verify_depth inchangé.

Délivrer un certificat client par personne

Un certificat par personne. N’utilisez jamais un certificat partagé par une équipe : il est impossible de révoquer un certificat partagé sans bloquer tout le monde, et il ne permet pas de savoir qui a effectué l’appel.

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 affiche le certificat qu’il s’apprête à signer, demande la passphrase de la CA, demande deux fois une confirmation, puis ajoute une ligne à index.txt. Ajoutez -batch si vous automatisez cette opération. La section client_ext est importante à cause d’une ligne qu’elle contient : extendedKeyUsage = clientAuth. Un certificat dont la liste d’usages étendus contient uniquement serverAuth est rejeté comme incompatible avec l’authentification client. Indiquez donc son usage explicitement.

Vérifiez la paire avec la CA avant de la remettre :

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

Cette commande affiche certs/alice.crt: OK. Tout autre résultat signifie que le certificat et la CA ne correspondent pas. Aucune configuration nginx ne pourra corriger ce problème.

Regroupez la clé et le certificat dans un seul fichier qu’un navigateur peut importer :

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

L’export demande un mot de passe qui protège le fichier pendant son transfert. Envoyez le fichier et le mot de passe par des canaux différents, et remettez aux utilisateurs le .p12 plutôt qu’un simple .key. Vous pouvez ajouter -certfile ca.crt pour inclure la CA dans le bundle, mais nginx n’en a pas besoin : nginx contient déjà ca.crt. Un certificat signé directement par cette CA est donc vérifié de manière autonome.

OpenSSL 3, fourni avec Ubuntu 24.04, crée des fichiers PKCS#12 avec les algorithmes de chiffrement actuels. Les navigateurs et les systèmes d’exploitation utilisés en août 2026 savent les lire. Si un ancien outil d’importation refuse le fichier, réexportez-le en ajoutant -legacy. Cette option rétablit les anciens algorithmes attendus par cet outil. Lisez le message affiché par l’outil d’importation avant d’utiliser cette option.

Configurer nginx avec ssl_client_certificate et ssl_verify_client

Copiez le certificat de l’AC, et uniquement le certificat de l’AC, sur le serveur.

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'

Le mode 644 convient ici. Un certificat d’AC est une information publique. La clé de l’AC reste sur votre poste de travail.

Ajoutez ensuite trois directives au bloc server qui termine déjà TLS :

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 est la valeur par défaut de nginx. Elle indique que le certificat client doit être signé directement par l’AC indiquée dans ce fichier. Augmentez cette valeur uniquement si vous ajoutez une AC intermédiaire. nginx envoie également les noms des sujets de ssl_client_certificate au client pendant le handshake. Le navigateur sait ainsi quels certificats proposer. C’est pourquoi il faut utiliser ssl_client_certificate plutôt que ssl_trusted_certificate. Les deux directives vérifient le certificat de la même manière, mais la seconde n’envoie aucune liste.

Ubuntu 24.04 fournit nginx 1.24. Avec cette version, HTTP/2 se configure sur la ligne listen avec listen 443 ssl http2;. À partir de nginx 1.25.1, cette forme est obsolète et HTTP/2 utilise sa propre directive, http2 on;. Les deux méthodes ne changent pas la vérification du certificat.

Rechargez la configuration et consultez le résultat :

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

nginx -t affiche syntax is ok et test is successful. La requête curl n’envoie aucun certificat. Elle doit donc renvoyer 400 Bad Request avec le corps No required SSL certificate was sent. nginx refuse alors la requête à son propre niveau. La configuration est donc active et l’application n’a jamais été sollicitée. Testez maintenant avec un certificat :

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

La commande doit renvoyer le contenu servi par votre application.

Pourquoi le contrôle doit se trouver dans le bloc server

Le certificat est échangé pendant la négociation TLS, avant que nginx ait lu la ligne de requête. À ce moment-là, nginx ne sait donc pas dans quel location la requête aboutira. Placer ssl_verify_client on; dans un location demande au client de renégocier au milieu de la connexion. TLS 1.3 a supprimé la renégociation et HTTP/2 l’interdit. Sur une stack actuelle, cette configuration échoue donc au lieu d’afficher une demande.

Gérez vous-même la portée. Demandez un certificat au niveau du serveur, puis prenez la décision dans chaque location :

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 contient SUCCESS, ou NONE lorsque le client n’a rien envoyé, ou FAILED: suivi d’une raison. Avec optional, nginx demande un certificat et ne le vérifie que si le client en fournit un. C’est ce qui permet à la route publique /healthz ci-dessus de fonctionner tout en maintenant /metrics fermé. Si un certificat est envoyé mais échoue à la vérification, nginx le refuse tout de même à ce stade. Si vous préférez inspecter vous-même un certificat en échec, utilisez optional_no_ca. Votre propre test doit alors traiter toute valeur autre que SUCCESS comme un refus.

nginx utilise des codes d’état non standard pour ce cas. error_page peut les intercepter afin qu’un visiteur refusé reçoive une explication plutôt qu’un simple 400 :

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 signifie que le certificat client n’a pas réussi la vérification. 496 signifie que le client n’a présenté aucun certificat. Gardez cette page en texte brut, car la personne qui la lit n’a ni session ni compte.

Comment installer le certificat client dans un navigateur ?

Firefox utilise son propre magasin de certificats : ouvrez Settings, puis Privacy and Security, puis View Certificates, sélectionnez l’onglet Your Certificates, cliquez sur Import, choisissez le fichier .p12 et saisissez son mot de passe.

Chrome et Edge utilisent le magasin du système d’exploitation sous Windows et macOS. L’ouverture du fichier .p12 lance donc l’assistant d’importation du système. Sous Linux, Chrome lit une base de données NSS (network security services) distincte dans votre répertoire personnel. La ligne de commande est alors la méthode la plus fiable :

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

Chargez ensuite le site. Le navigateur vous demande quel certificat envoyer. Chrome mémorise ce choix pour le reste de la session du navigateur. Redémarrez donc le navigateur lorsque vous voulez que cette question s’affiche à nouveau. Le certificat est enregistré dans un profil de navigateur sur une machine donnée. Un certificat importé dans Firefox est donc invisible pour Chrome, et les deux restent invisibles pour votre téléphone.

Tests avec curl --cert

Déboguez avec curl, car l’outil indique les opérations effectuées.

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

Vous pouvez concaténer le certificat et la clé dans un seul fichier PEM, puis le transmettre avec --cert alice.pem. Si la clé est protégée par une phrase secrète, curl vous la demande. curl accepte également --cert alice.pem:passphrase, mais la phrase secrète apparaît alors dans l’historique de votre shell. Utilisez donc l’invite.

Deux vérifications sont utiles avant d’incriminer nginx. Tout d’abord, le certificat et la clé doivent former une paire :

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

Deux empreintes identiques indiquent que les fichiers vont ensemble. Deux empreintes différentes indiquent que vous avez mélangé les fichiers de deux personnes. Aucun client ne vous indiquera cette cause.

Ensuite, le serveur doit demander votre CA :

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

Recherchez le bloc Acceptable client certificate CA names dans la sortie, ainsi que le subject de votre CA à l’intérieur de ce bloc. Si ce bloc est totalement absent, nginx ne demande pas de certificat dans le server block qui a répondu. Vos directives se trouvent donc dans un autre server block, souvent le serveur par défaut.

Transmettre le CN du client à l’application

Le certificat indique l’identité de l’appelant, mais l’application située derrière le proxy ne peut pas voir la couche TLS. nginx doit donc lui transmettre ce nom.

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

$ssl_client_s_dn contient le nom distinctif du sujet au format RFC 2253, qui ressemble à CN=alice,O=Example Ops. La directive map extrait le champ CN dans $client_cn. Conservez un nom d’utilisateur simple pour le CN, car une virgule dans un CN est échappée dans ce format et la petite expression régulière ci-dessus ne gère pas cet échappement.

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 remplace tout en-tête portant ce nom envoyé par l’appelant. Personne ne peut donc falsifier X-Client-Cert-CN via cet emplacement. Deux conditions permettent de garantir ce comportement. nginx n’hérite de proxy_set_header depuis le niveau extérieur que si le niveau intérieur ne définit aucune directive de ce type. Un second emplacement contenant une seule directive proxy_set_header perd donc silencieusement tous les en-têtes définis au niveau supérieur, y compris celui-ci. L’application doit également être inaccessible autrement que par nginx. Elle doit donc écouter sur 127.0.0.1 et non sur 0.0.0.0, car une application exposée sur un port public lira directement l’en-tête falsifié depuis Internet. La configuration du proxy est présentée dans une configuration de reverse proxy nginx expliquée ligne par ligne. Si l’application a besoin du certificat complet plutôt que d’un nom, $ssl_client_escaped_cert le transmet encodé dans une URL et utilisable sans risque dans un en-tête.

Comment révoquer un certificat client ?

Lorsqu’une personne quitte l’entreprise ou qu’un ordinateur portable disparaît, vous révoquez uniquement le certificat concerné. Tous les autres utilisateurs continuent de travailler. C’est précisément l’intérêt d’émettre un certificat par personne.

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

La première commande modifie la ligne correspondant à ce numéro de série dans index.txt, en remplaçant V par R. La seconde génère une liste de révocation de certificats (CRL), c’est-à-dire un fichier signé qui répertorie les numéros de série révoqués. Déployez ce fichier et indiquez-le à nginx avec ssl_crl /etc/nginx/client-ca.crl;, à côté des autres directives.

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'

Voici le piège qui bloque tout le monde. Une CRL contient une date nextUpdate, définie par default_crl_days, dont la valeur est 30 dans la configuration ci-dessus. Une fois cette date dépassée, OpenSSL considère la liste comme obsolète et échoue lors de la vérification de chaque certificat client avec CRL has expired, pas uniquement de celui qui a été révoqué. nginx lit le fichier au chargement de sa configuration. Une nouvelle CRL présente sur le disque ne change donc rien avant un reload. Régénérez la CRL et effectuez un reload selon une planification largement incluse dans cette fenêtre : chaque semaine pour une validité de 30 jours. Vérifiez les dates avant de copier le fichier :

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

Pour quelques utilisateurs, une solution plus légère est possible. La CA vous appartient, donc nginx peut refuser directement un numéro de série et éviter la gestion d’une CRL :

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

Associez cette configuration à if ($revoked) { return 403; } dans la location. Elle n’a aucune date d’expiration à surveiller. Elle n’est toutefois pas propagée : les autres systèmes qui font confiance à votre CA n’en savent rien. Pour un seul nginx placé devant une seule application, c’est la solution simple et appropriée. Passez à la CRL dès qu’il y a plusieurs points d’accès.

Quelle durée de validité choisir pour les certificats client ?

Donnez aux certificats client une validité d’un an, ou moins si vous pouvez gérer le travail de réémission. L’expiration est le problème discret dans ce cas, car rien n’avertit le détenteur à l’avance. Un matin, il ouvre le panneau d’administration, nginx refuse la connexion et le navigateur décrit le refus avec ses propres termes, qui contiennent rarement le mot « expiré ». Conservez une validité de dix ans pour la CA et notez sa date d’expiration à un endroit que vous consultez réellement, car lorsque le certificat de la CA expire, tous les certificats qu’elle a signés cessent d’être vérifiés le même jour.

Deux commandes vous permettent d’anticiper ce problème :

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

La première colonne de index.txt indique l’état : V pour un certificat valide, R pour un certificat révoqué et E pour un certificat expiré. La deuxième colonne contient la date d’expiration au format YYMMDDHHMMSSZ et la quatrième contient le numéro de série. Ce fichier est votre seul registre des certificats attribués. Sauvegardez-le avec la clé de la CA et traitez ces deux éléments comme des secrets.

Le renouvellement consiste à créer un nouveau certificat, pas à prolonger l’ancien. Générez une nouvelle clé et une CSR (certificate signing request), signez-la, remettez-la au titulaire, puis révoquez l’ancien certificat lorsque la personne confirme que le nouveau fonctionne.

Ce que mTLS protège et ce qu’il ne protège pas

Il supprime l’accès non authentifié. Un scanner qui découvre votre nom d’hôte est refusé pendant le handshake. Il n’envoie donc jamais de requête HTTP, ne voit jamais de formulaire de connexion et ne peut pas essayer un mot de passe volé. Le credential stuffing n’a plus rien à tester. Une vulnérabilité dans le parcours de connexion de l’application devient inaccessible à toute personne sans certificat. Cela supprime aussi le secret partagé que les utilisateurs copient dans les conversations. Une clé privée est un fichier qu’il est difficile de copier par accident.

En revanche, il ne protège pas contre un client compromis. Un malware présent sur un laptop possède le fichier de clé et obtient la passphrase dès que son propriétaire la saisit. Pour le serveur, cet attaquant ressemble exactement à un utilisateur légitime, car un certificat prouve la possession d’un fichier, pas la présence d’une personne. Le mot de passe .p12 et le chiffrement intégral du disque restent donc importants.

Ce n’est pas non plus un mécanisme d’autorisation. Chaque certificat valide accède à tout ce que sert ce server block, sauf si vous vérifiez $client_cn et utilisez sa valeur. Par défaut, deux détenteurs de certificats ont les mêmes droits.

Il ne protège également que le chemin qui passe par nginx. Si l’application écoute aussi sur un port public, le mTLS placé devant elle n’est qu’une protection de façade. Liez l’application à 127.0.0.1 et gardez son port fermé dans le firewall. L’autre point d’accès à la même machine est SSH. Il mérite la même attention, comme expliqué dans sécuriser l’accès SSH à votre VPS.

Il reste une dernière limite, qui se manifeste dès que vous activez le dispositif. Tout ce qui ne peut pas présenter de certificat cesse de fonctionner : un monitor de disponibilité, un webhook envoyé par un prestataire de paiement, un lecteur RSS ou une application mobile sans certificate store accessible. Décidez quoi en faire avant de définir ssl_verify_client on, car l’échec est total et silencieux de leur côté.

Lire ce que signale le client lorsqu’il est refusé

Le message affiché par un client refusé dépend du navigateur, de la version de curl et de la bibliothèque TLS utilisée. Lisez donc ce que votre propre client affiche au lieu de le comparer à un message trouvé ailleurs. Le détail utile se trouve sur le serveur.

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

Un certificat rejeté laisse une ligne contenant client SSL certificate verify error, suivie de la raison fournie par OpenSSL. C’est cette raison qu’il faut traiter. Il s’agit généralement de l’un des cas suivants. Le certificat provient d’une autre CA que celle indiquée dans le fichier nommé par ssl_client_certificate. Le certificat se trouve en dehors de sa période de validité. La CRL du serveur a dépassé son nextUpdate. Elle entraîne alors l’échec de tous les clients, et pas d’un seul.

Lorsque le navigateur ne propose jamais de certificat, le problème se situe avant la vérification. nginx envoie les noms des émetteurs acceptables pendant le handshake. Le navigateur n’a trouvé dans son store aucun certificat correspondant. Il n’avait donc rien à proposer. Importez à nouveau le .p12 dans le profil avec lequel vous naviguez réellement.

Un autre cas mérite d’être mentionné. Si vous avez effectué le test avec un certificat client autosigné, au lieu d’utiliser un certificat signé par votre CA, la vérification ne peut pas réussir. nginx vérifie la signature avec le fichier de la CA, et un certificat autosigné n’y figure pas. La création du certificat suit la même procédure que celle décrite dans générer un certificat autosigné sur Ubuntu. mTLS nécessite seulement l’étape supplémentaire au cours de laquelle votre CA le signe.

FAQ

Dois-je encore utiliser un certificat Let's Encrypt si j'utilise mTLS ?

Oui. Les deux certificats n'ont aucun lien. Votre serveur présente son propre certificat afin que le navigateur fasse confiance au nom d'hôte. Ce certificat doit toujours provenir d'une CA déjà connue du navigateur. Votre CA client forme une chaîne privée distincte, utilisée uniquement pour vérifier l'identité du client qui se connecte. La configuration de ssl_client_certificate ne change rien au certificat présenté par nginx. Elle ne doit pas pointer vers votre chaîne Let's Encrypt.

Pourquoi mon navigateur ne me demande-t-il jamais de choisir un certificat ?

nginx envoie, pendant le handshake, la liste des émetteurs acceptés. Cette liste est construite à partir du fichier indiqué dans ssl_client_certificate. Un navigateur ne propose que les certificats dont l'émetteur figure dans cette liste. L'absence de demande signifie donc que le navigateur ne contient aucun certificat de votre CA. L'importation a peut-être été effectuée dans un autre profil de navigateur, ou le certificat a été signé par une autre CA que celle installée sur le serveur. Exécutez openssl s_client -connect admin.example.com:443 et recherchez dans la sortie les noms des CA acceptées pour les certificats clients afin de voir quelle CA le serveur demande réellement.

Puis-je exiger un certificat client pour une seule URL ?

Pas avec ssl_verify_client on à l'intérieur d'un location. Le certificat est échangé pendant le handshake, avant que nginx connaisse le chemin de la requête. La renégociation qui permettrait de contourner cette limitation a disparu de TLS 1.3 et est interdite avec HTTP/2. Définissez ssl_verify_client optional; dans le server block. Dans chaque location protégée, testez ensuite $ssl_client_verify et retournez 403 lorsqu'il ne vaut pas SUCCESS.

Comment révoquer l'accès d'une personne ?

Révoquez son certificat avec openssl ca -revoke, régénérez la liste avec openssl ca -gencrl, copiez-la sur le serveur, puis rechargez nginx afin qu'il lise le nouveau fichier. Les autres utilisateurs ne sont pas concernés. Cela ne fonctionne que si chacun possède son propre certificat et non un certificat partagé. Surveillez la date nextUpdate de la CRL. Une CRL expirée fait échouer la vérification pour tous les clients, et pas uniquement pour les certificats révoqués.

mTLS remplace-t-il une page de connexion ?

Pour contrôler l'accès, oui : sans certificat, aucune requête n'atteint l'application. Il n'y a donc aucun formulaire à attaquer ni aucun mot de passe à deviner. Pour l'identité dans l'application, non. Un certificat prouve seulement que l'appelant détient un fichier de clé. Un ordinateur portable volé représente donc un utilisateur valide. Transmettez le CN au backend, conservez les comptes et les permissions déjà gérés par l'application, et utilisez le certificat comme contrôle d'accès placé devant eux.

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