Let’s Encrypt meddelade i maj 2025 att man slutar utfärda certifikat med utökad nyckelanvändning för TLS-klientautentisering (“TLS Client Authentication” EKU) från och med 2026. Ändringen träder i kraft i samband med bytet till certifikathierarkin Generation Y i februari 2026. För de flesta webbplatser spelar det ingen roll. Men för team som använder mTLS, ömsesidig TLS-autentisering, för att skydda API:er, mikrotjänster eller interna admin-paneler betyder det att en gratis genväg försvinner. Man måste bygga och driva sin egen certifikatutfärdare (CA) för klientcertifikat.
Den här guiden visar hur du sätter upp mTLS med Nginx från grunden: egen CA, servercertifikat, klientcertifikat och en produktionsklar konfiguration som kräver giltigt klientcertifikat innan en enda byte data släpps igenom. Vi går igenom 12 konkreta steg, pekar ut de vanligaste felen och ger en felsökningslista du kan bocka av när handskakningen vägrar gå igenom.
Vad är mTLS och varför behöver du det 2026
Vanlig TLS (det hänglås du ser i webbläsaren) autentiserar bara servern. Klienten, alltså webbläsaren eller appen, vet att den pratar med rätt server, men servern vet ingenting om vem som ringer upp. mTLS vänder på det. Enligt Nginx officiella dokumentation är “Mutual TLS (mTLS) is a security method that uses client certificates to verify both the server and the client during communication” (ömsesidig TLS är en säkerhetsmetod som använder klientcertifikat för att verifiera både server och klient under kommunikationen). Båda parter måste alltså visa upp giltiga certifikat innan anslutningen släpps igenom.
Det gör mTLS till förstahandsvalet när du vill stänga ute allt förutom kända, förtroendegivna klienter: interna API:er mellan mikrotjänster, admin-gränssnitt som bara ska nås av utrustning du själv har utfärdat certifikat till, IoT-enheter som pratar med en backend, eller partnerintegrationer där lösenord och API-nycklar känns för svaga. Till skillnad från en API-nyckel kan ett klientcertifikat inte enkelt kopieras ut ur en TLS-session, och det går att återkalla enskilda certifikat utan att röra resten av flottan.
Tekniskt bygger mTLS vidare på TLS 1.3, definierat i RFC 8446, som redan i grunden kräver AEAD-chiffer (Authenticated Encryption with Associated Data) och tar bort föråldrade nyckelutbytesmetoder som statisk RSA. Det som skiljer mTLS från vanlig TLS är ett enda extra steg i handskakningen: servern skickar en “CertificateRequest” och väntar in klientens svar innan sessionen anses upprättad. Misslyckas klienten att svara med ett certifikat som kedjar upp till en betrodd CA, avbryts anslutningen innan någon applikationsdata någonsin skickas. Det gör mTLS till ett skydd på transportlagret, långt innan din applikationskod ens hinner köra en rad.
Nyhetshaken för 2026 är konkret. I sitt officiella meddelande skriver Let’s Encrypt att “Let’s Encrypt will no longer include the ‘TLS Client Authentication’ Extended Key Usage (EKU) in our certificates beginning in 2026” (Let’s Encrypt kommer inte längre inkludera utökad nyckelanvändning för TLS-klientautentisering i sina certifikat från och med 2026). Samtidigt tillägger man att “Most users who use Let’s Encrypt to secure websites won’t be affected and won’t need to take any action” (de flesta som använder Let’s Encrypt för att säkra webbplatser påverkas inte och behöver inte göra något). Men just den lilla gruppen som byggt mTLS-lösningar ovanpå gratis Let’s Encrypt-certifikat måste nu flytta till en egen CA, precis det den här guiden visar.
Förutsättningar och versioner
Innan du börjar, se till att du har följande på plats. Guiden är skriven för Ubuntu 24.04 LTS men fungerar med mindre justeringar på Debian, RHEL och de flesta Linux-distributioner.
- En Linux-server (fysisk, virtuell eller molnbaserad) med root- eller sudo-access
- Nginx version 1.25 eller senare (kommandona i guiden är verifierade mot 1.29-serien, som enligt Nginx officiella blogg introducerade stöd för Encrypted Client Hello i version 1.29.4)
- OpenSSL 3.0 eller senare för att generera CA och certifikat
- Ett domännamn som pekar mot servern, om du vill kombinera mTLS med publik TLS via Let’s Encrypt för de delar som fortfarande ska vara öppna mot vanliga webbläsare
- Grundläggande kännedom om Linux-terminalen och textredigering (nano, vim eller motsvarande)
- Cirka 60–90 minuters obruten tid, samt en testklient (curl, en annan server, eller en mobilapp) för att verifiera resultatet
Ett vanligt missförstånd är att tro att mTLS kräver ett kommersiellt certifikat. Det gör det inte. Poängen med mTLS är att du kontrollerar tillitskedjan själv, så en privat CA som du skapar och styr är precis rätt verktyg, inte en genväg.
Steg 1: Skapa katalogstruktur för certifikat
Börja med att organisera filerna så att du inte blandar ihop CA-nycklar med servernycklar. En vanlig nybörjarmiss är att lägga allt i samma mapp och sedan råka checka in privata nycklar i git.
sudo mkdir -p /etc/nginx/mtls/{ca,server,clients}
cd /etc/nginx/mtls
sudo chmod 700 ca server clients
sudo chown root:root ca server clients
Katalogen ca håller din privata certifikatutfärdare, server håller Nginx servercertifikat och clients håller de certifikat du utfärdar till varje enskild klient. Separationen gör det enklare att sätta rätt filrättigheter senare, och den gör det tydligt vilka filer som aldrig ska lämna servern.
Steg 2: Bygg din egen certifikatutfärdare (CA)
Nu skapar du en privat nyckel och ett självsignerat rotcertifikat som ska agera CA. Det här certifikatet är grunden för hela tillitskedjan, så nyckeln måste skyddas hårt.
cd /etc/nginx/mtls/ca
openssl genrsa -aes256 -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
-out ca.crt \
-subj "/C=SE/ST=Stockholm/O=MittForetag/CN=MittForetag Intern CA"
sudo chmod 400 ca.key
Flaggan -aes256 lägger ett lösenord på den privata CA-nyckeln, vilket du bör ha eftersom den här filen är den viktigaste hemligheten i hela uppsättningen. Giltighetstiden på 3650 dagar (tio år) är rimlig för en rot-CA som du sällan roterar, till skillnad från server- och klientcertifikaten som bör ha betydligt kortare livslängd.
Steg 3: Generera servercertifikatet
Servern behöver ett eget certifikat, signerat av din nya CA, som Nginx presenterar för klienterna under handskakningen.
cd /etc/nginx/mtls/server
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr \
-subj "/C=SE/ST=Stockholm/O=MittForetag/CN=api.mittforetag.se"
openssl x509 -req -in server.csr \
-CA ../ca/ca.crt -CAkey ../ca/ca.key -CAcreateserial \
-out server.crt -days 397 -sha256
Giltighetstiden på 397 dagar är inget godtyckligt val. Det är samma maxgräns som CA/Browser Forum satt för publikt betrodda TLS-certifikat, och att hålla samma disciplin för interna certifikat gör rotationsrutinerna enklare att komma ihåg. Byt ut CN=api.mittforetag.se mot det faktiska domännamn eller den interna DNS-post din tjänst svarar på.
Steg 4: Generera klientcertifikat för varje betrodd part
Varje klient som ska få prata med servern behöver ett unikt certifikat. Undvik frestelsen att återanvända samma klientcertifikat för flera tjänster, då tappar du möjligheten att återkalla åtkomst för en enskild part utan att slå ut alla andra.
cd /etc/nginx/mtls/clients
openssl genrsa -out client-tjanst-a.key 2048
openssl req -new -key client-tjanst-a.key -out client-tjanst-a.csr \
-subj "/C=SE/O=MittForetag/CN=tjanst-a"
openssl x509 -req -in client-tjanst-a.csr \
-CA ../ca/ca.crt -CAkey ../ca/ca.key -CAcreateserial \
-out client-tjanst-a.crt -days 397 -sha256
# Bunta ihop till PKCS#12 om klienten är en mobilapp eller webbläsare
openssl pkcs12 -export \
-out client-tjanst-a.p12 \
-inkey client-tjanst-a.key \
-in client-tjanst-a.crt \
-certfile ../ca/ca.crt
PKCS#12-filen (.p12) är praktisk när klienten är en webbläsare eller mobilapp som förväntar sig ett enda importerbart certifikatpaket. För server-till-server-kommunikation räcker det oftast med de separata .key– och .crt-filerna som curl och de flesta HTTP-bibliotek kan läsa direkt.
Steg 5: Konfigurera Nginx för att kräva klientcertifikat
Nu kopplar du ihop certifikaten i en server-block-konfiguration. Det är här de flesta gör sitt första misstag genom att blanda ihop ssl_client_certificate (som pekar på din CA, för verifiering) med ssl_certificate (som pekar på serverns eget certifikat, för presentation).
server {
listen 443 ssl;
server_name api.mittforetag.se;
ssl_certificate /etc/nginx/mtls/server/server.crt;
ssl_certificate_key /etc/nginx/mtls/server/server.key;
ssl_client_certificate /etc/nginx/mtls/ca/ca.crt;
ssl_verify_client on;
ssl_verify_depth 2;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers on;
ssl_session_tickets off;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header X-Client-Cert-CN $ssl_client_s_dn_cn;
}
}
ssl_verify_client on; är den rad som faktiskt tvingar fram klientcertifikatet. Utan den kommer Nginx be om ett certifikat men inte neka anslutningar som saknar ett, vilket ger en falsk känsla av säkerhet. Raden proxy_set_header X-Client-Cert-CN vidarebefordrar identiteten från certifikatet till backend-applikationen, så att din applikationslogik kan göra behörighetskontroller baserat på vilken klient som faktiskt anropade.
Steg 6: Testa konfigurationen och starta om Nginx
Innan du laddar om Nginx i produktion, låt alltid konfigurationstestet köra. Det fångar syntaxfel innan de orsakar driftstopp.
sudo nginx -t
sudo systemctl reload nginx
Ett lyckat test skriver ut syntax is ok och test is successful. Reload snarare än restart är att föredra i produktion eftersom det inte stänger befintliga anslutningar under bytet.
Steg 7: Verifiera med curl från klientsidan
Testa nu att en anslutning utan certifikat nekas, och att en anslutning med rätt certifikat släpps igenom.
# Utan klientcertifikat, ska misslyckas
curl -v https://api.mittforetag.se/
# Med klientcertifikat, ska lyckas
curl -v https://api.mittforetag.se/ \
--cert /etc/nginx/mtls/clients/client-tjanst-a.crt \
--key /etc/nginx/mtls/clients/client-tjanst-a.key \
--cacert /etc/nginx/mtls/ca/ca.crt
Exempel på utdata när certifikatet saknas:
* TLS alert, unknown CA (560)
* OpenSSL SSL_connect: SSL_ERROR_SSL
curl: (35) OpenSSL SSL_connect: SSL_ERROR_SSL detail error
Och exempel på utdata när rätt certifikat presenteras:
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* Server certificate verify ok
* Client certificate accepted
< HTTP/1.1 200 OK
Om första testet i stället lyckas med 200 OK har du sannolikt glömt ssl_verify_client on; i konfigurationen. Gå tillbaka till steg 5.
Steg 8: Lägg till certifikatåterkallning med CRL
Ett klientcertifikat som läckt eller tillhör en tjänst som avvecklats måste kunna spärras utan att du bygger om hela CA:t. Certificate Revocation List (CRL) löser det.
cd /etc/nginx/mtls/ca
touch index.txt
echo 1000 > crlnumber
# Spärra ett certifikat
openssl ca -config openssl.cnf -revoke ../clients/client-tjanst-a.crt
# Generera CRL-filen
openssl ca -config openssl.cnf -gencrl -out ca.crl
Lägg sedan till CRL-filen i Nginx-konfigurationen:
ssl_crl /etc/nginx/mtls/ca/ca.crl;
Kom ihåg att regenerera och ladda om CRL-filen varje gång du spärrar ett nytt certifikat. En vanlig driftsmiss är att spärra ett certifikat i loggböckerna men glömma att faktiskt distribuera den uppdaterade CRL-filen till Nginx, vilket gör att det spärrade certifikatet fortsätter fungera.
Steg 9: Kombinera mTLS med publik TLS via Let's Encrypt
De flesta system behöver både öppna, publika endpoints (som en inloggningssida) och skyddade endpoints som kräver klientcertifikat (som ett internt API). Lösningen är att separera dem i olika server-block eller location-block, inte att blanda kraven i samma block.
server {
listen 443 ssl;
server_name app.mittforetag.se;
# Publikt certifikat från Let's Encrypt, ingen klientverifiering
ssl_certificate /etc/letsencrypt/live/app.mittforetag.se/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.mittforetag.se/privkey.pem;
location /internal-api/ {
# Endast denna sökväg kräver klientcertifikat
ssl_client_certificate /etc/nginx/mtls/ca/ca.crt;
ssl_verify_client on;
proxy_pass http://127.0.0.1:8080;
}
location / {
proxy_pass http://127.0.0.1:3000;
}
}
Notera att ssl_verify_client tekniskt sett gäller för hela server-blocket i äldre Nginx-versioner, så om du behöver blandad åtkomst på samma domän är det ofta enklare och säkrare att köra det skyddade API:et på en egen subdomän med ett eget server-block, exakt som i steg 5. Det undviker sammanblandning av regler som annars är en vanlig källa till felkonfigurerad åtkomst.
Steg 10: Automatisera certifikatrotation
Med en giltighetstid på 397 dagar för server- och klientcertifikat behöver du en rutin för förnyelse innan de går ut. Ett enkelt cron-jobb som varnar i god tid räcker långt.
#!/bin/bash
# check-cert-expiry.sh
CERT=/etc/nginx/mtls/server/server.crt
DAYS_LEFT=$(( ($(date -d "$(openssl x509 -enddate -noout -in "$CERT" | cut -d= -f2)" +%s) - $(date +%s)) / 86400 ))
if [ "$DAYS_LEFT" -lt 30 ]; then
echo "VARNING: Servercertifikatet går ut om $DAYS_LEFT dagar" | \
mail -s "mTLS-certifikat snart utgånget" [email protected]
fi
Lägg skriptet i crontab för daglig körning. För större flottor med många klientcertifikat är det värt att titta på ett verktyg som Smallstep step-ca eller HashiCorp Vaults PKI-motor, som automatiserar hela utfärdande- och rotationsflödet i stället för att du sköter det manuellt med OpenSSL-kommandon.
Steg 11: Härda TLS-konfigurationen ytterligare
Med mTLS på plats är det läge att strama åt resten av TLS-inställningarna. Enligt hårdningsguider för Nginx 2026 bör TLS 1.0 och 1.1 vara helt avstängda, eftersom de formellt föråldrades redan 2021 genom RFC 8996. Lägg till följande i din serverblock eller i en delad snippet:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
OCSP-stapling gör att servern själv hämtar och bifogar återkallningsstatus i handskakningen i stället för att klienten behöver fråga certifikatutfärdaren separat, vilket både snabbar upp anslutningen och är mer robust när en extern OCSP-tjänst är långsam eller nere. Kombinerat med HSTS-headern tvingas alla anslutningar över till HTTPS permanent, med förinställning (preload) mot webbläsarnas HSTS-listor.
Om du vill jämföra din egen konfiguration mot en etablerad baslinje kan Mozillas generator för serversidans TLS-konfiguration ge färdiga chiffersviter och inställningar för tre olika säkerhetsnivåer (modern, mellan och gammal), sorterade efter vilka klienter du behöver stödja. För en renodlad mTLS-uppsättning mot interna tjänster är "modern"-profilen nästan alltid rätt val, eftersom du själv kontrollerar vilka klienter som ansluter och därför inte behöver kompromissa för äldre webbläsare.
Steg 12: Logga och övervaka certifikatanvändning
Sista steget är att se till att du faktiskt märker om något går fel. Utöka Nginx-loggformatet så att klientens certifikat-CN loggas vid varje anrop.
log_format mtls_access '$remote_addr - $ssl_client_s_dn_cn '
'[$time_local] "$request" $status '
'$body_bytes_sent "$ssl_client_verify"';
access_log /var/log/nginx/mtls-access.log mtls_access;
Variabeln $ssl_client_verify visar SUCCESS, FAILED eller NONE för varje request, vilket gör det enkelt att i efterhand se om någon har försökt komma åt API:et utan giltigt certifikat eller med ett certifikat som redan spärrats. Skicka loggarna vidare till ett SIEM-verktyg om du redan har ett på plats, så att avvikande mönster (till exempel upprepade FAILED från samma IP) triggar en varning.
Verktyg för att validera din mTLS-konfiguration
Curl räcker långt för snabba tester, men för en djupare kontroll av hela TLS-uppsättningen finns bättre verktyg. openssl s_client ger dig full insyn i handskakningen, inklusive vilket certifikat servern faktiskt skickar och vilken chiffersvit som förhandlas fram.
openssl s_client -connect api.mittforetag.se:443 \
-cert clients/client-tjanst-a.crt \
-key clients/client-tjanst-a.key \
-CAfile ca/ca.crt \
-servername api.mittforetag.se
Leta efter raden Verify return code: 0 (ok) längst ner i utskriften. Allt annat än 0 betyder att något i certifikatkedjan inte stämmer, och felkoden som visas (till exempel 19 för "self signed certificate in certificate chain") talar om exakt vad som är fel.
För en bredare kontroll av hela TLS-konfigurationen, inte bara klientcertifikatet, är det öppna skriptet testssl.sh ett standardverktyg bland driftteam. Det kontrollerar protokollversioner, chiffersviter, kända sårbarheter som Heartbleed och POODLE, samt om HSTS och OCSP-stapling faktiskt fungerar som tänkt, i ett enda körbart skript utan externa beroenden.
git clone --depth 1 https://github.com/testssl/testssl.sh.git
cd testssl.sh
./testssl.sh --mutual-tls api.mittforetag.se:443
Om du hellre vill testa mot en extern, publikt tillgänglig endpoint kan Qualys SSL Labs ge en gratis, oberoende bedömning av hela din TLS-konfiguration med ett bokstavsbetyg från F till A+. Tänk på att SSL Labs inte kan testa mTLS-skyddade endpoints fullt ut eftersom tjänsten inte har ett klientcertifikat att presentera, så använd den i första hand för de publika delarna av din uppsättning, och testssl.sh eller openssl s_client lokalt för de mTLS-skyddade delarna.
Prestandapåverkan: vad kostar mTLS egentligen?
En vanlig invändning mot mTLS är att den extra handskakningen kostar för mycket prestanda. I praktiken är kostnaden mindre dramatisk än ryktet, men den är inte noll och bör tas med i kapacitetsplaneringen.
Den extra kostnaden uppstår huvudsakligen på tre ställen. För det första måste servern verifiera klientens certifikatkedja mot sin lista av betrodda CA:er vid varje ny anslutning, en operation som är CPU-bunden men i regel tar enstaka millisekunder med moderna ECDSA-certifikat. För det andra växer själva handskakningens datamängd något eftersom klientcertifikatet skickas över ledningen. För det tredje, om du använder CRL-kontroll snarare än OCSP-stapling, tillkommer en extra nätverksrundtripp för att slå upp återkallningsstatus, vilket är den delen som faktiskt märks mest i praktiken.
Tre konkreta åtgärder minskar den overheaden märkbart. Håll TLS-sessioner vid liv med keepalive-anslutningar mellan tjänster så att handskakningen bara sker en gång per anslutning i stället för per request. Använd ECDSA-certifikat i stället för RSA där det går, eftersom ECDSA-signaturer är betydligt snabbare att verifiera vid samma säkerhetsnivå. Och aktivera OCSP-stapling (steg 11) så att återkallningskontrollen sker en gång per certifikat i stället för per anslutning. Med de tre åtgärderna på plats är mTLS-overheaden i de flesta interna nätverk försumbar jämfört med den nätverkslatens som redan finns mellan tjänster i olika delar av infrastrukturen.
Vanliga fallgropar när du bygger mTLS
Följande misstag dyker upp om och om igen hos team som sätter upp mTLS för första gången.
- Att blanda ihop ssl_client_certificate och ssl_certificate. Den förstnämnda pekar på din CA för verifiering av inkommande certifikat, den sistnämnda pekar på serverns eget certifikat. Att kasta om dem ger obegripliga felmeddelanden.
- Att glömma ssl_verify_client on. Utan den raden ber Nginx om ett certifikat men kräver aldrig ett, vilket ger falsk trygghet.
- Att sätta för lång giltighetstid på klientcertifikat. Ett certifikat med tio års livslängd som läcker är ett tioårigt säkerhetshål. Håll dig till max 397 dagar, precis som publika certifikat.
- Att återanvända samma klientcertifikat för flera tjänster. Det gör det omöjligt att spärra åtkomst för en enskild klient utan att slå ut alla andra som delar certifikatet.
- Att glömma uppdatera CRL efter en spärrning. Ett spärrat certifikat som saknas i den aktiva CRL-filen fungerar precis som vanligt.
- Att inte skydda CA-nyckeln med lösenord och strikta filrättigheter. Om
ca.keyläcker kan angriparen utfärda hur många giltiga klientcertifikat som helst. - Att testa mTLS med samma webbläsarflik som redan är inloggad någon annanstans. Cachade TLS-sessioner kan dölja om verifieringen faktiskt fungerar. Testa alltid i ett rent curl-anrop eller en privat webbläsarsession.
Felsökning: när handskakningen misslyckas
Om något inte fungerar som förväntat, gå igenom listan nedan innan du börjar googla slumpmässiga felkoder.
| Symptom | Trolig orsak | Åtgärd |
|---|---|---|
| curl: (35) unknown CA | Klienten saknar korrekt --cacert eller CA:t matchar inte serverns ssl_client_certificate | Kontrollera att samma ca.crt används på båda sidor |
| 400 Bad Request, "No required SSL certificate was sent" | ssl_verify_client är satt men klienten skickade inget certifikat | Lägg till --cert och --key i klientanropet |
| Anslutning lyckas trots saknat certifikat | ssl_verify_client är off eller optional i stället för on | Ändra till ssl_verify_client on; och kör nginx -t igen |
| nginx -t misslyckas med "SSL_CTX_use_PrivateKey_file failed" | Fel sökväg till nyckelfilen eller felaktiga filrättigheter | Kontrollera sökväg och att Nginx-processen (ofta www-data) kan läsa filen |
| Spärrat certifikat accepteras ändå | CRL-filen är inte uppdaterad eller inte inladdad i Nginx | Kör openssl ca -gencrl igen och verifiera ssl_crl-raden i konfigurationen |
| "unable to get local issuer certificate" | Klienten saknar mellanliggande CA i sin certifikatkedja | Bunta ihop hela kedjan i en fullchain-fil på klientsidan |
| Handskakningen tar väldigt lång tid | OCSP-uppslag mot en extern tjänst som svarar långsamt | Aktivera ssl_stapling on; för att slippa klientens externa uppslag |
| Certifikat fungerar i testmiljö men inte i produktion | Olika CA-filer i de två miljöerna, eller klockskillnad mellan servrar | Synka NTP-tid och verifiera att samma ca.crt distribuerats till alla miljöer |
mTLS jämfört med andra autentiseringsmetoder
mTLS är inte alltid rätt verktyg. Tabellen nedan visar var det passar bäst jämfört med API-nycklar och OAuth 2.0.
| Metod | Passar bäst för | Återkallning | Komplexitet att drifta |
|---|---|---|---|
| mTLS | Server-till-server, mikrotjänster, IoT | Via CRL eller OCSP, kräver egen CA | Hög, kräver certifikatinfrastruktur |
| API-nycklar | Enkla integrationer, tredjepartsappar | Enkel, oftast en databasrad | Låg |
| OAuth 2.0 med PKCE | Användarautentisering, mobilappar, SPA | Token-återkallning eller kort livslängd | Medel, kräver auktoriseringsserver |
| Sessionscookies | Traditionella webbappar med inloggning | Enkel, radera session | Låg till medel |
I praktiken kombinerar många team dessa metoder. mTLS skyddar transportlagret mellan tjänster, medan OAuth 2.0 (som vi tidigare gått igenom i vår guide om OAuth 2.0 med PKCE i Node.js) hanterar vem den mänskliga användaren bakom en förfrågan faktiskt är. De konkurrerar sällan, de löser olika problem.
Avancerade tips för produktionsmiljöer
När grundkonfigurationen fungerar finns det några steg till som gör lösningen mer robust i skarp drift.
- Byt CRL mot OCSP-responder för stora certifikatvolymer. En CRL-fil som växer till tusentals rader blir långsam att distribuera. Ett internt OCSP-svar per certifikat skalar bättre för flottor med hundratals klienter.
- Utvärdera Encrypted Client Hello (ECH) om du kör Nginx 1.29.4 eller senare. ECH krypterar delar av TLS-handskakningen som annars läcker vilken domän klienten ansluter till, vilket enligt Nginx officiella blogg är "the final piece in a decade-long effort to encrypt web communications end-to-end". Observera att fullt stöd i OpenSSL ännu inte landat i en stabil release, så räkna med att testa detta i en icke-produktionsmiljö först.
- Automatisera hela PKI:t med step-ca eller Vault. Manuell OpenSSL-hantering fungerar för en handfull certifikat, men blir ohållbart över hundra klienter. Ett dedikerat PKI-verktyg ger dig ACME-liknande automatisk förnyelse även för interna certifikat.
- Separera CA-nyckeln fysiskt från webbservern. I en mognare uppsättning bör
ca.keyligga på en offline-maskin eller i en HSM, inte på samma server som terminerar trafiken. - Sätt kortare livslängd på klientcertifikat än på servercertifikat. Klienter roterar oftare och är mer utsatta för läckor (till exempel om en bärbar dator med ett certifikat stjäls), så 90 dagar är ett rimligare mål än 397 för just klientsidan.
Verktyg för att skala upp PKI:t bortom OpenSSL
Kommandona i den här guiden fungerar utmärkt för ett fåtal tjänster, men manuell hantering med OpenSSL blir snabbt ohållbar när antalet klienter växer till tiotals eller hundratals. Tabellen nedan jämför de vanligaste alternativen för att driva en egen certifikatutfärdare i större skala.
| Verktyg | Bäst för | Automatisk rotation | Driftskomplexitet |
|---|---|---|---|
| OpenSSL (manuellt) | Enstaka tjänster, labbmiljöer, lärande | Nej, kräver egna skript | Låg, men skalar dåligt |
| Smallstep step-ca | Interna PKI:er med ACME-stöd för egna certifikat | Ja, via ACME-protokollet | Medel |
| HashiCorp Vault (PKI-motor) | Organisationer som redan använder Vault för hemligheter | Ja, med kort livslängd som standard | Hög, kräver egen Vault-drift |
| cert-manager (Kubernetes) | Kubernetes-kluster och service mesh-miljöer | Ja, integrerat i klustrets livscykel | Medel, kräver Kubernetes |
Om din mTLS-uppsättning i huvudsak skyddar kommunikation mellan poddar i ett Kubernetes-kluster är det ofta mer rimligt att luta sig mot ett service mesh som redan sköter certifikatutbyte automatiskt mellan tjänster, snarare än att bygga den logiken själv ovanpå Nginx. Vår guide om Kubernetes-säkerhet: Stoppa CVSS 9,4-hot går igenom fler av grundskydden som bör vara på plats innan du lägger till mTLS som ytterligare ett lager.
Komplett arbetsflöde: från noll till fungerande mTLS
Som sammanfattning, här är hela flödet i rätt ordning om du vill kopiera kommandona i ett svep till en testserver.
# 1. Katalogstruktur
sudo mkdir -p /etc/nginx/mtls/{ca,server,clients}
cd /etc/nginx/mtls
# 2. Egen CA
cd ca && openssl genrsa -aes256 -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
-out ca.crt -subj "/C=SE/O=MittForetag/CN=MittForetag Intern CA"
# 3. Servercertifikat
cd ../server
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr \
-subj "/C=SE/O=MittForetag/CN=api.mittforetag.se"
openssl x509 -req -in server.csr -CA ../ca/ca.crt -CAkey ../ca/ca.key \
-CAcreateserial -out server.crt -days 397 -sha256
# 4. Klientcertifikat
cd ../clients
openssl genrsa -out client-tjanst-a.key 2048
openssl req -new -key client-tjanst-a.key -out client-tjanst-a.csr \
-subj "/C=SE/O=MittForetag/CN=tjanst-a"
openssl x509 -req -in client-tjanst-a.csr -CA ../ca/ca.crt -CAkey ../ca/ca.key \
-CAcreateserial -out client-tjanst-a.crt -days 397 -sha256
# 5. Testa Nginx-konfigurationen och ladda om
sudo nginx -t && sudo systemctl reload nginx
# 6. Verifiera
curl -v https://api.mittforetag.se/ \
--cert clients/client-tjanst-a.crt \
--key clients/client-tjanst-a.key \
--cacert ca/ca.crt
Den här grundstrukturen räcker för att skydda ett internt API i produktion. För en fullständig hantering av kryptografiska nycklar och certifikat i stort, se även vår guide om OpenSSL 3.5: nycklar och certifikat, och för en genomgång av grunderna i TLS och hänglåset i webbläsaren, läs HTTPS och TLS: hänglåset, certifikat och vad de skyddar.
Hur klienter faktiskt får tag i sina certifikat säkert
Att generera certifikat är den enkla delen. Den svårare frågan är hur du får den privata nyckeln från din CA-server till varje klient utan att den passerar en osäker kanal på vägen, till exempel ett vanligt e-postmeddelande eller en delad Slack-kanal. Om en privat nyckel skickas okrypterad genom fel kanal har hela poängen med mTLS gått förlorad redan innan certifikatet ens tagits i bruk.
För server-till-server-integrationer inom samma infrastruktur är det vanligaste mönstret att certifikatet genereras direkt på målmaskinen (eller i en container-image som byggs vid deploy), så att den privata nyckeln aldrig behöver lämna maskinen den ska användas på. Certifikatet signeras genom att endast CSR-filen (Certificate Signing Request), som inte innehåller någon privat nyckel, skickas till CA:t för signering. Det signerade certifikatet skickas sedan tillbaka, medan den privata nyckeln stannar kvar lokalt hela tiden.
# Körs på klientmaskinen, nyckeln lämnar aldrig maskinen
openssl genrsa -out /etc/mtls/client.key 2048
openssl req -new -key /etc/mtls/client.key -out client.csr \
-subj "/C=SE/O=MittForetag/CN=tjanst-b"
# Endast client.csr skickas till CA-servern för signering,
# aldrig client.key
För mobilappar och IoT-enheter är läget svårare eftersom certifikatet ofta måste installeras vid tillverkning eller första uppstart. Här används i regel ett provisioneringsflöde där enheten autentiserar sig mot en registreringstjänst med en engångskod eller en fabriksinstallerad hemlighet, och först därefter får sitt riktiga klientcertifikat utfärdat över en krypterad kanal. Att hantera detta manuellt för mer än en handfull enheter är opraktiskt, vilket är ytterligare ett skäl att titta på dedikerade PKI-verktyg som Smallstep step-ca, som har inbyggt stöd för just den typen av automatiserad enrollering.
När mTLS inte är rätt lösning
mTLS är ett tungt verktyg och det finns lägen där det gör mer skada än nytta. Om din API-konsument är en webbläsare som en vanlig användare loggar in från är mTLS opraktiskt, det kräver att varje slutanvändare installerar ett personligt certifikat, vilket är krångligt i de flesta konsumentflöden. Där passar OAuth 2.0 eller sessionsbaserad inloggning bättre, kombinerat med skydd mot vanliga API-hot som vi beskrivit i vår guide om OWASP Top 10:2025: Härda API.
mTLS passar sämst också när organisationen saknar resurser att drifta en egen PKI-lösning över tid. Ett certifikat som glöms bort och går ut mitt i natten kan slå ut hela integrationen mellan två system, så innan du inför mTLS brett bör du ha en fungerande övervaknings- och förnyelserutin, inte bara den initiala uppsättningen.
Säkerhetsöverväganden utöver certifikaten
mTLS löser autentisering, inte auktorisering. Ett giltigt klientcertifikat bevisar bara vem klienten är, inte vad den får göra. Bygg alltid en behörighetskontroll i applikationslagret som använder $ssl_client_s_dn_cn-värdet för att avgöra vilka resurser den specifika klienten faktiskt får nå. Detta är samma princip som ligger bakom skyddet mot BOLA-attacker (Broken Object Level Authorization) som beskrivs mer i detalj i vår artikel om Testa API-säkerhet med Burp Suite. Ett giltigt certifikat räcker aldrig ensamt som fullständig auktorisering.
Se också till att SSH-nycklarna till servern som håller din CA är minst lika hårt skyddade som certifikaten själva. Vår guide om SSH-nycklar i Linux går igenom grunderna för att låsa ner just den typen av åtkomst.
Slutligen, dokumentera vilka certifikat som existerar och vem som ansvarar för varje klient. Det låter som ett administrativt sidospår, men i praktiken är det den vanligaste orsaken till att gamla, oanvända klientcertifikat aldrig spärras. Ett litet register, även bara ett kalkylark till att börja med, som listar CN, utfärdandedatum, förfallodatum och ansvarig kontaktperson för varje utfärdat certifikat gör återkallningsprocessen i steg 8 mycket enklare att sköta konsekvent över tid, särskilt när fler än en person i teamet är inblandad i att utfärda nya certifikat.
Vanliga frågor om mTLS med Nginx
Kan jag använda Let's Encrypt för klientcertifikat 2026?
Nej. Let's Encrypt meddelade att man tar bort stödet för utökad nyckelanvändning för TLS-klientautentisering i sina certifikat från och med 2026, i samband med bytet till certifikathierarkin Generation Y i februari. Den tidigare ACME-profilen för klientautentisering kommer inte längre vara tillgänglig, och inga nya certifikat med den nyckelanvändningen utfärdas. Du behöver en egen certifikatutfärdare, precis som den vi bygger i den här guiden.
Är mTLS överkurs för ett litet projekt?
Ofta, ja. Om du bara har en handfull interna tjänster och redan kör dem i ett privat nätverk med brandvägg kan enklare metoder som API-nycklar över en privat VPC räcka. mTLS lönar sig när du behöver stark, kryptografisk identitet på transportlagret, till exempel för känsliga finansiella integrationer eller reglerade branscher.
Vad händer om ett klientcertifikat läcker?
Du lägger till certifikatets serienummer i din CRL-fil (steg 8) eller markerar det som återkallat i din OCSP-responder, laddar om Nginx, och utfärdar ett nytt certifikat till den drabbade klienten. Eftersom varje klient har ett unikt certifikat påverkas ingen annan tjänst.
Fungerar mTLS med lastbalanserare framför Nginx?
Det beror på om lastbalanseraren termineras TLS eller passerar trafiken igenom obruten (TLS passthrough). Om lastbalanseraren avslutar TLS-anslutningen måste den själv hantera klientcertifikatverifieringen och vidarebefordra identiteten i en header till Nginx. Många molnleverantörers lastbalanserare stödjer detta, men konfigurationen skiljer sig åt mellan leverantörer, så kontrollera dokumentationen för just din lastbalanserare innan du antar att det fungerar som i denna guide.
Kan jag ha flera CA:er för olika kundgrupper?
Ja. Du kan antingen ha flera separata rot-CA:er (en per kundgrupp) eller en mellanliggande CA per grupp under samma rot. Det senare är oftast enklare att administrera eftersom du bara behöver en ssl_client_certificate-fil som innehåller hela kedjan, samtidigt som du kan spärra en hel mellanliggande CA om en kundgrupps hela certifikatuppsättning måste inaktiveras.
Hur ofta ska jag rotera klientcertifikat?
En vanlig praxis är 90 till 397 dagar beroende på hur känslig integrationen är. Kortare livslängd minskar skadan om ett certifikat läcker, men kräver bättre automatisering för att inte orsaka driftstopp. Om du inte har automatiserad förnyelse på plats, håll dig närmare 397 dagar och sätt kalenderpåminnelser i god tid.
Skiljer sig mTLS-konfigurationen mellan Nginx open source och Nginx Plus?
Grundkonfigurationen med ssl_client_certificate och ssl_verify_client fungerar likadant i båda versionerna. Nginx Plus lägger till extra funktioner för centraliserad certifikathantering via Nginx Instance Manager, vilket kan förenkla drift av mTLS över många instanser, men är inte ett krav för att följa den här guiden.
Måste jag stänga av TLS 1.2 helt och bara köra TLS 1.3?
Nej, det är inte nödvändigt för de flesta. TLS 1.2 med moderna AEAD-chiffer (AES-GCM eller ChaCha20-Poly1305) och forward secrecy anses fortfarande säkert 2026 och behövs ofta som fallback för äldre klientbibliotek. Det som formellt är avrått sedan 2021 (RFC 8996) är TLS 1.0 och 1.1, som du alltid bör stänga av helt, vilket görs med raden ssl_protocols TLSv1.2 TLSv1.3; som visas i steg 5.




