Mutual TLS (mTLS) with FastAPI and Uvicorn

The Challenge:

  • Help others who may want to configure mTLS with FastAPI
  • Clarify some of the lacking documentation (or things people know but may not have explained)
  • Document the approach (here)

The Approach

We Need two CAs:

  • A public CA (trusted) to create a secure channel
  • A validation CA which can issue client certs to be validated
  • A validation CA can be based on a public CA (however issuing client certs may be expensive) and Internal CA Based on Something Like Active Directory or a Self Manage CA.
  • I am going to use a Self Managed CA.

Nuts and Bolts

  • Create a validation_fullachain.pem for your validation CA. Below is my fullchain which has the Cryptoroo Root CA and the FastAPI MTLS Proofs Intermediate.
  • Create a Leaf Certificate Signed by the FastAPI MTLS Proofs Intermediate. Note: usually the Common Name is all that is needed for a MTLS Client Cert but you can add other extensions (e.g. OU, etc). Make sure that you add the Client Authentication Extended key usage.

Testing and Implementing

Phase 1: Ensure TLS/SSL is working

  • Run the command below. This should be the path to your Public CA Certificates.
  • For Let’s Encrypt it is the fullchain.pem and the privkey.pem files. Note: I use windows but linux clients should be /live/{domain}/ as well.
  • Note: I am using port 443 for a specific reason to make the testing consistent.
  • Note: You will need to run this as administrator on windows in order to be able to open the Certbot certificates.
uvicorn main:app — ssl-certfile <fullchain certificate in PEM> — ssl-keyfile <RSA / ECC Private Key>  — port 443
  • Browse to your domain name (mine is and inspect the certificate. If you see Let’s Encrypt and a green padlock you are good to go to the next phase.

Phase 2: For Testing CERT_OPTIONAL Configuration

  • Review the functioning of the CERT_OPTIONAL flag here.
  • Essentially the browser will ask for a certificate but if one is not presented it will allow users through.
  • Note: I can’t seem to be able to get the browser to show my self signed certificate so in the next phase I will use Postman and OpenSSL for verification
uvicorn main:app 
— ssl-certfile <fullchain certificate in PEM> — ssl-keyfile <RSA / ECC Private Key>
ssl-cert-reqs 1 --ssl-ca-certs <fullchain certificates in PEM>
— port 443

Browser Asking for a Certificate.

Clicking Cancel

  • Not sending a certificate still allows you to use the service.

Phase 3: For Testing CERT_REQUIRED Configuration

uvicorn main:app 
— ssl-certfile C:\Certbot\live\\fullchain.pem — ssl-keyfile C:\Certbot\live\\privkey.pem
— ssl-cert-reqs 2 — ssl-ca-certs E:\GITHUB\cryptoroo_ca\complete.crt
— port 443

Postman Call with no Client Cert:

Configuring Postman to Use SSL Certificates for

Refer to here for more details:


Verify that the certificate is in fact passed in:

Testing With OpenSSL:

openssl s_client -connect -cert <path to your mtls cert> -key <path to your mtls key>

We then Should get a command Prompt at the bottom:

Type In the Following to test a GET to the Root (/)

GET / HTTP/1.1


  • mTLS is quite possible and easy to configure with FastAPI
  • Uvicorn provides some tips on how to set this up but it does require knowledge of Python TLS/SSL library
  • There is an assumed knowledge of TLS and which arguments perform which function
  • Testing in Browser is hard as the personal keys don’t show up

To Do:

  • Demo using CURL
  • Fix Browser SSL Certificate Issue
  • Video Demos
  • Demo of how to create a Validation CA



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store