Setup & verify Mutual TLS Authentication (MTLS) with OpenSSL


In this article we will explore Mutual Transport Layer Security (MTLS) and we will use a client and server setup to quickly validate mTLS authentication. We will use openssl to create the required certificates and verify the mutual TLS authentication.


1. Overview on SSL and TLS

  • I hope you are already familiar with SSL and TLS.
  • Transport Layer Security (TLS) is a protocol you can use to protect network communications from eavesdropping and other types of attacks.
  • It is an update to the Secure Sockets Layer (SSL) protocol that preceded it, and often people still refer to both collectively as “SSL” or use the terms “SSL” and “TLS” interchangeably.
  • TLS protects traffic at the transport layer so you can wrap a number of higher-level plain-text protocols in TLS to secure them.
  • Some popular examples of protocols that can be wrapped in TLS include HTTP (HTTPS, that lock icon in the URL bar of your browser), FTP (FTPS, not to be confused with SFTP which uses the SSH protocol), IMAP (IMAPS), POP3 (POP3S), and SMTP (SMTPS), among others. As you can see, it’s common to add an “S” at the end of a protocol that is wrapped in SSL or TLS.

TLS provides you with two primary protections.

  • The first protection, and the one most commonly associated with TLS, is that TLS encrypts your traffic to protect it from eavesdropping.
  • The second and equally important protection is that TLS authenticates the server to the client (and optionally the client to the server as well) with the use of signed certificates.


2. Overview on mTLS

  • By default TLS only validates the authenticity of the server and not of the client (application) which is sending the request.
  • We can use mutual TLS where both clients request certificates from the server to ensure the server is who it says it is, and the server requests certificates from the client to prove who it is as well.
  • When using mutual authentication, not only does the service side prove its identity by exposing a certificate, but also the clients prove their identity to the servers by exposing a client-side certificate.
  • This provides a higher level of security compared to normal TLS/HTTPS usage, where only the identity of the server is proven.
  • Setting up and maintaining mutual authentication; that is, the provision of new, and the rotating of outdated, certificates, is known to be complex and is therefore seldom used.


3. Why should we use mutual authentication (MTLS)?

Isn't it sufficient to protect external APIs with HTTPS and OAuth 2.0/OIDC access tokens?

As long as the attacks come through the external API, it might be sufficient.

But what if a client node with the application becomes compromised?

For example, if an attacker gains control over the node, then the attacker can start listening to traffic between other nodes in the setup. If the internal communication is sent as plain text, it will be very easy for the attacker to gain access to sensitive information sent between the nodes in the setup. To minimize the damage caused by such an intrusion, mutual authentication can be used to prevent an attacker from eavesdropping on internal network traffic.


4. Lab Environment

I will be using Virtual Machines running on Oracle VirtualBox to demonstrate this article. These VMs are installed with CentOS 7 and 8 Linux. I have two VMs where in one will act as a server while the other will act as a client. I will use the server node to create all the certificates i.e. CA certificate, server and client certificate.

The hostname of the server node is with an IP address while the client hostname is with an IP address of


5. Create CA certificate

First we would need a CA certificate which can sign both the client and server certificates. So let's create our directory structure to store the CA certificate and key.

[root@server ~]# mkdir /root/mtls

[root@server ~]# cd /root/mtls/

[root@server mtls]# mkdir certs private

Next create an index.txt and serial file to track the list of certificates signed by the CA certificate.

[root@server mtls]# echo 01 > serial
[root@server mtls]# touch index.txt


5.1 Configure openssl.cnf

I will copy openssl.cnf from /etc/pki/tls/openssl.cnf and then modify it. I have already explained individual section of this file in Configure openssl.cnf for Root CA Certificate.

So here I will skip the explanation, just sharing a copy of my openssl.cnf for reference:

# This definition stops the following lines choking if HOME isn't
# defined.
HOME                    = .
RANDFILE                = $ENV::HOME/.rnd

#oid_file               = $ENV::HOME/.oid
oid_section             = new_oids

[ new_oids ]
# Policies used by the TSA examples.
tsa_policy1 =
tsa_policy2 =
tsa_policy3 =

[ ca ]
default_ca      = CA_default            # The default ca section

[ CA_default ]
dir             = /root/mtls            # Where everything is kept
certs           = $dir/certs            # Where the issued certs are kept
database        = $dir/index.txt        # database index file.
                                        # several certs with same subject.
new_certs_dir   = $dir/certs            # default place for new certs.
certificate     = $dir/certs/cacert.pem       # The CA certificate
serial          = $dir/serial           # The current serial number
crlnumber       = $dir/crlnumber        # the current crl number
                                        # must be commented out to leave a V1 CRL
private_key     = $dir/private/cakey.pem # The private key

name_opt        = ca_default            # Subject Name options
cert_opt        = ca_default            # Certificate field options

default_days    = 365                   # how long to certify for
default_crl_days= 30                    # how long before next CRL
default_md      = sha256                # use SHA-256 by default
preserve        = no                    # keep passed DN ordering
policy          = policy_match

# For the CA policy
[ policy_match ]
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_anything ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
default_bits            = 2048
default_md              = sha256
default_keyfile         = privkey.pem
distinguished_name      = req_distinguished_name
attributes              = req_attributes
x509_extensions = v3_ca # The extentions to add to the self signed cert

[ req_distinguished_name ]
countryName                     = Country Name (2 letter code)
countryName_default             = IN
countryName_min                 = 2
countryName_max                 = 2
stateOrProvinceName             = State or Province Name (full name)
stateOrProvinceName_default     = Some-State
localityName                    = Locality Name (eg, city)
localityName_default            = BANGALORE
0.organizationName              = Organization Name (eg, company)
0.organizationName_default      = GoLinuxCloud
organizationalUnitName          = Organizational Unit Name (eg, section)
commonName                      = Common Name (eg, your name or your server\'s hostname)
commonName_max                  = 64
emailAddress                    = Email Address
emailAddress_max                = 64

[ req_attributes ]
challengePassword               = A challenge password
challengePassword_min           = 4
challengePassword_max           = 20
unstructuredName                = An optional company name

[ v3_req ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

[ v3_ca ]
# Extensions for a typical CA
basicConstraints = critical,CA:true

[ crl_ext ]
# issuerAltName=issuer:copy


5.2 Create private key

We would need a private key for the CA certificate.

[root@server mtls]# openssl genrsa -out private/cakey.pem 4096
Generating RSA private key, 4096 bit long modulus
e is 65537 (0x10001)


5.3 Create CA certificate

We will use this private key to generate our CA certificate:

[root@server mtls]# openssl req -new -x509 -days 3650 -config /root/mtls/openssl.cnf -key private/cakey.pem -out certs/cacert.pem

Sample output snippet from my terminal:
Setup & verify mutual TLS authentication (MTLS) with openssl


5.4 Convert certificate to PEM format

This is an optional step but you can convert the certificate into PEM format:

[root@server mtls]# openssl x509 -in certs/cacert.pem -out certs/cacert.pem -outform PEM


6. Create client certificate

Now we will create the client certificate which will be used by the client node i.e. in our case. I will use the same node i.e. to generate the client certificates. But let me create a different directory to store these certificates:

[root@server mtls]# mkdir /root/client_certs
[root@server mtls]# cd /root/client_certs/


6.1 Create private key

We will again need a different private key for the client certificate.

[root@server client_certs]# openssl genrsa -out client.key.pem 4096
Generating RSA private key, 4096 bit long modulus
e is 65537 (0x10001)


6.2 Generate Certificate Signing Request (CSR)

Next we need to generate the CSR for the client certificate. For our CA certificate we had given few details such as Country Name. State, Locality etc. If you check the openssl.cnf file, the CA certificate is using the policy_match section for the CSR.

policy          = policy_match

# For the CA policy
[ policy_match ]
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

It is important that any CSR you generate either for client or server certificates, should have matching Country Name, State and Organization Name with the CA certificate or else while signing the certificate you will get error like:

The stateOrProvinceName field needed to be the same in the
CA certificate (XXXXX) and the request (YYYYY

So with that in mind, let's generate the CSR for the client certificate.

[root@server client_certs]# openssl req -new -key client.key.pem -out client.csr

Sample output from my terminal:
Setup & verify mutual TLS authentication (MTLS) with openssl

Make sure you give proper Common Name with the hostname of your client node. If you have a scenario where you can access the server using multiple addresses then you should create a SAN certificate.


6.3 Add certificate extensions

We will also add some certificate extensions to our client certificate. You may modify or ignore this step based on your requirement. We will create an additional configuration file with the required extensions

[root@server client_certs]# cat client_ext.cnf
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection


6.4 Create client certificate

Next we will create our client certificate:

[root@server client_certs]# openssl ca -config /root/mtls/openssl.cnf -extfile client_ext.cnf -days 1650 -notext -batch -in client.csr -out client.cert.pem
Using configuration from /root/mtls/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 1 (0x1)
            Not Before: Apr  8 11:43:21 2021 GMT
            Not After : Oct 14 11:43:21 2025 GMT
            countryName               = IN
            stateOrProvinceName       = Karnataka
            organizationName          = GoLinuxCloud
            commonName                = server-2
            emailAddress              =
        X509v3 extensions:
            X509v3 Basic Constraints:
            Netscape Cert Type:
                SSL Client, S/MIME
            Netscape Comment:
                OpenSSL Generated Client Certificate
            X509v3 Subject Key Identifier:
            X509v3 Authority Key Identifier:

            X509v3 Key Usage: critical
                Digital Signature, Non Repudiation, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Client Authentication, E-mail Protection
Certificate is to be certified until Oct 14 11:43:21 2025 GMT (1650 days)

Write out database with 1 new entries
Data Base Updated

The path of the CA certificate required to sign the certificate will be picked from /root/mtls/openssl.cnf. You can verify the same using:

[root@server client_certs]# cat /root/mtls/index.txt
V       251014114321Z           01      unknown /C=IN/ST=Karnataka/O=GoLinuxCloud/CN=server-2/

So this says that a certificate for server-2 has been signed by the root CA certificate.


7. Create server certificate

We will again create a separate directory to store the server certificates.

[root@server client_certs]# mkdir ../server_certs
[root@server client_certs]# cd ../server_certs/


7.1 Create private key

We need another private key for the server certificate:

[root@server server_certs]# openssl genrsa -out server.key.pem 4096
Generating RSA private key, 4096 bit long modulus
e is 65537 (0x10001)


7.2 Create Certificate Signing Request (CSR)

We will follow the same guidelines as I explained while creating CSR for client certificate. The Country, State and Organization Name must match with the CA certificate or else the signing would fail later.

[root@server server_certs]# openssl req -new -key server.key.pem -out server.csr

Sample output snippet from my terminal:
Setup & verify mutual TLS authentication (MTLS) with openssl

I have highlighted the Common Name used as it is important that you use the hostname/FQDN which will be used to identify the server. Here in my case the FQDN of my server is but I have just given the hostname here as the CN value.


7.3 Add certificate extensions

Similar to client certificate, we will again add some extensions to our server certificate. Additionally I am adding Subject Alternative Name field also known as SAN. This is used to define multiple Common Name. In my case it is possible that a client can access the server using IP address or FQDN so I would also like to add those in the certificate.

[root@server server_certs]# cat server_ext.cnf
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
IP.1 =
IP.2 =
DNS.1 =

Since I have two interfaces on the, I have given the IP address of both the interface as client can also reach to my server using other interface.


7.4 Create server certificate

Now we will use this extension file along with the private key and CSR to generate our server certificate. The path of the CA certificate required to sign the certificate will be picked from /root/mtls/openssl.cnf.

[root@server server_certs]# openssl ca -config /root/mtls/openssl.cnf -extfile server_ext.cnf -days 1650 -notext -batch -in server.csr -out server.cert.pem
Using configuration from /root/mtls/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 2 (0x2)
            Not Before: Apr  8 11:52:58 2021 GMT
            Not After : Oct 14 11:52:58 2025 GMT
            countryName               = IN
            stateOrProvinceName       = Karnataka
            organizationName          = GoLinuxCloud
            commonName                = server
            emailAddress              =
        X509v3 extensions:
            X509v3 Basic Constraints:
            Netscape Cert Type:
                SSL Server
            Netscape Comment:
                OpenSSL Generated Server Certificate
            X509v3 Subject Key Identifier:
            X509v3 Authority Key Identifier:

            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication
            X509v3 Subject Alternative Name:
                IP Address:, IP Address:,
Certificate is to be certified until Oct 14 11:52:58 2025 GMT (1650 days)

Write out database with 1 new entries
Data Base Updated

Verify the index content of the root CA:

[root@server ~]# cat /root/mtls/index.txt
V       251014114321Z           01      unknown /C=IN/ST=Karnataka/O=GoLinuxCloud/CN=server-2/
V       251014115258Z           02      unknown /C=IN/ST=Karnataka/O=GoLinuxCloud/CN=server/

So the root CA has also signed another certificate for CN=server.


8. Validate mutual TLS authentication

Now we will verify the mutual TLS authentication between the server and the client node. To achieve this I will copy the client certificates on to the client node under a new directory /root/certs. I have already created this directory on my client node, here is the IP address of my client i.e.

[root@server server_certs]# scp ../client_certs/*
root@'s password:
client.cert.pem                                                        100% 2220     2.7MB/s   00:00
client.csr                                                             100% 1740     2.4MB/s   00:00
client_ext.cnf                                                         100%  290   580.1KB/s   00:00
client.key.pem                                                         100% 3243     4.9MB/s   00:00

We also need to copy the CA certificate over to the client node:

[root@server server_certs]# scp /root/mtls/certs/cacert.pem                   
root@'s password:
cacert.pem                                                             100% 2106     3.5MB/s   00:00

There are multiple methods to verify the mTLS authentication but I will use nodejs and openssl to validate this. You can also use LDAP, Apache etc to verify this in your environment.


Mehtod-1: Using Nodejs

In this method I will create a simple nodejs file to setup a web server our HTTPS and then access this web server using the client node over TLS.

First make sure nodejs is installed on your server node which is part of EPEL repo:

[root@server ~]# yum install epel-release -y

[root@server ~]# yum install nodejs -y

This will install NodeJS on the server node. Next create a simple webserver application file, here I have provided the path of CA certificate, server certificate and server private key.

[root@server ~]# cat web-server.js
const https = require('https');
const fs = require('fs');

const hostname = '';
const port = 3000;

const options = {
    ca: fs.readFileSync('/root/mtls/certs/cacert.pem'),
    cert: fs.readFileSync('/root/server_certs/server.cert.pem'),
    key: fs.readFileSync('/root/server_certs/server.key.pem'),
    rejectUnauthorized: true,
    requestCert: true,

const server = https.createServer(options, (req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World\n');

server.listen(port, hostname, () => {
  console.log(`Server running at https://${hostname}:${port}/`);

Start your web server:

[root@server ~]# node web-server.js
Server running at

You can also enable port 3000 in the firewall on the server node:

[root@server ~]# firewall-cmd --add-port=3000/tcp --permanent

[root@server ~]# firewall-cmd --reload

Now login to your client node and using curl try to access the web server:

[root@server-2 ~]# curl --cacert /root/certs/cacert.pem --key /root/certs/client.key.pem --cert /root/certs/client.cert.pem
Hello World

So as expected, the client was able to connect to the web server using the client certificate. So this proves the mutual TLS authentication where both server and client are using TLS certificate to prove their identity.


Method-2: Using openssl

Let me show you one more method to validate the mutual TLS authentication in Linux using openssl.

On the server node execute the following command:

[root@server ~]# openssl s_server -accept 3000 -CAfile /root/mtls/certs/cacert.pem  -cert /root/server_certs/server.cert.pem -key /root/server_certs/server.key.pem  -state

Next on the client node execute:

[root@server-2 ~]# openssl s_client -connect -key /root/certs/client.key.pem  -cert /root/certs/client.cert.pem -CAfile /root/certs/cacert.pem -state

If everything is correct then you should see a successful TCP handshake between the server and client node.

Sample output from the server node:

[root@server ~]# openssl s_server -accept 3000 -CAfile /root/mtls/certs/cacert.pem  -cert /root/server_certs/server.cert.pem -key /root/server_certs/server.key.pem  -state
Using default temp DH parameters
Signature Algorithms: ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512:0x07+0x08:0x08+0x08:0x09+0x08:0x04+0x08:0x0A+0x08:0x05+0x08:0x0B+0x08:0x06+0x08:RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA224:RSA+SHA224:ECDSA+SHA1:RSA+SHA1
Supported Elliptic Curve Point Formats: uncompressed:ansiX962_compressed_prime:ansiX962_compressed_char2
Supported Elliptic Curves: 0x001D:P-256:0x001E:P-521:P-384
Shared Elliptic curves: P-256:P-521:P-384
Secure Renegotiation IS supported

Sample output from the client node:

[root@server-2 ~]# openssl s_client -connect -key /root/certs/client.key.pem  -cert /root/certs/client.cert.pem -CAfile /root/certs/cacert.pem -state
SSL handshake has read 4217 bytes and written 415 bytes
Verification: OK

New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 4096 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES256-GCM-SHA384
Session-ID: 7D1BDC137AF9E00707C00FCCFEE195E520FD20EF4C0F0C31BF94E759F3CD0C6E
Master-Key: A06B2906FB51CF3214D371F49C3C8B7C0E82C0BFA5AB2D845A8DAA8A08BF894D74BACEB58471C7AD8262C03197FCA592
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 300 (seconds)



In this tutorial we learned all about Transport Layer Security (MTLS) and how it is different compared to SSL and TLS. We also learned the steps to validate mutual TLS authentication between a server and a client node using openssl and NodeJS. I had written another article to verify similar scenario using Apache server. Now you should be familiar with mTLS and the steps to create and validate certificates.


Further Readings

Implementing SSL/TLS Using Cryptography and PKI
Mutual Authentication


Views: 1,578
Deepak Prasad

Deepak Prasad

He is the founder of GoLinuxCloud and brings over a decade of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels in various domains, from development to DevOps, Networking, and Security, ensuring robust and efficient solutions for diverse projects. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to

Thank You for your support!!

2 thoughts on “Setup & verify Mutual TLS Authentication (MTLS) with OpenSSL”

  1. hi,I want to know what if we use the same certificate(thus same private key,same cacert) for both client and servers for tls connection?will it work fine?
    also ,in this example you used private key +certificate,is there anyway we can have generate 1 certificate that has both pub key info and private key info with in it?

    • You can generate certificate with extendedKeyUsage as clientAuth and serverAuth and then we can use the same cert for both client and server. We also use it in production with no issues
      Regarding second question, I actually have never tried so can’t give you an honest response. But I will try to test this and write an article soon.


Leave a Comment