mTLS with Apache HTTP server

Understanding mTLS and setting it up with Apache HTTP Web Server

What is mTLS ?

mTLS or Two way SSL, abbreviation for mutual Transport Layer Security, aims at establishing the authenticity of both the client and the server to each other. Original TLS / SSL establishes the identity of the server to the client. In simple terms, the clients also need to authenticate with the server with it’s own certificates.

mTLS Handshake Steps (between Client & Server)

Hows does mTLS work ?

It leverages the concept that a certificate can be used as symmetric key pair validation, where one entity can hold the public key, while the other can share the private key and get it validated.

The Client will provide Root, intermediate & the actual certificate of the client (or generated ) to the server. While the client is making an API call, it needs to provide the private key along with the connection (encrypted with the public key of the server — Refer step 6 from the flow) and if matching, the server authenticates the client. If the client doesn’t provide the certificate, a 403 Forbidden Error will be thrown. And, if the certificates are not matching then Handshake Error is served. The above diagram theoretically showcases all the steps that happen during a call from the client to server. Check out for the end of the blog on how it works.

How does mTLS help ?

mTLS proves it’s at most necessity, specially in the Cloud Era where security becomes a massive concern. It removes the dependencies in managing passwords (non-symmetric entity) or many-a-times being used alongside with these passwords, so that even if the credentials are compromised good luck for the attacker to impersonate the certificate. Additionally, certificates come with a default expiry time and it becomes a mandate to rotate them periodically, while we can not enforce the same for human bound passwords.

Benefits of configuring mTLS in a dedicated web-server

Spring boot can do this with embedded configuration at the top level where every API’s are being protected.

I look at this feature more from learning perspective not from a Production implementation because

  1. Under Spring boot, all API’s become mTLS protected with no exception. If you need to have any set of API’s unprotected, you need to have a separate service altogether.
  2. In microservices, implementing the same for every services becomes pain and overhead in management. Also, for every service dedicated SSL certificates also needs to be maintained, adding to more overhead.
  3. Apache HTTP can simply act as a single Entrypoint (similar to API Gateway) with reverse proxy capabilities to every service and we just need to implement the mTLS on Web-Server only.

Understanding the Certificate Chain

Certificate chain is the list of certificates used to identify the authenticity of the given certificate. Let’s understand this by a scenario — If you are opening a new website first time, the browser will not have the certificate and it will try to verify via it’s parent ( Certificate Authority aka CA) and see if parent is trusted already. If the parent is also trusted, it will check for parent’s parent (essentially grandparent) and goes on. Mostly the CA chain runs upto 2 parents CA’s (CA root and CA Intermediate).

Now, this is how an untrusted certificate would look like. The browser naturally doesn’t have this certificate and finds that it’s parent (Root CA) is also not there and so is the warning.

It can be easily bypassed through adding the certificate to the system. Steps to install certificate: Windows, Mac

google — Certificate Chain

From given certificate chain it’s evident that GTS CA 1C3 is the intermediate CA, GTS Root R1 is the root CA, & has inherited these chains.
Note: It’s a wildcard DNS registration.

Let’s get hands on….

We need a Server SSL certificate, Client SSL Certificate (different cname and details), and will use this to configure Apache HTTP Docker image (httpd).

All the configurations I have done on localhost only.

Generating Certificates

I followed this wonderful article on creating SSL Certificate for localhost, and reciprocate the same for the client certificate as well just by changing the extension certificate details.

If you already have a key pair that’s generated at the enterprise level, then just use the below command to verify if the CA and the certificate are matching properly — (OK output should be displayed)

openssl verify -verbose -CAfile ../CA.pem localhost.crtlocalhost.crt: OK

If you use wrong Certificate / Root CA or if Root CA is missing then might possibly encounter the below Error.

cat localhost.crt| openssl verifystdin: C = UK, ST = London, L = Durham, O = clientorganization, OU = engineering, CN =, emailAddress = myself@clientorganization.comerror 20 at 0 depth lookup:unable to get local issuer certificate

Configuring Apache HTTP server

There are two parts in this configuring:

  1. httpd.conf file: We need to to enable configurations from extras/httpd-ssl.conf file and enable modules, socache_shmcb_module. (just uncomment these lines)
  2. extras/httpd-ssl.conf file:
- SSLCACertificateFile — provide the path of the client’s certificates
- SSLVerifyClient — set to none, to ensure by default all API’s are unprotected
- Adding the below lines - ie. for /protected endpoint, we need to provide client certificate
<Location /protected >
SSLVerifyClient require SSLVerifyDepth 2</Location>- Changing the server name from to localhost
- <VirtualHost _default_:443> to <VirtualHost *:443> for HTTPS

3. Below is the Dockerfile for the same with self explanatory comments.

# Docker build 
docker build -t mtls:1
# Docker run (note it's exposed in 443 port)
docker run -it -p 443:443 mtls:1

Time for Testing…

Using cURL

Using curl we just need to pass the certificate and it’s key.

Here are the list of endpoints to test


Testing protected endpoint (need to use — cert and — key flag)

curl -ivk "https://localhost/protected/protected.html" --cert ./incoming-certificate/CA/localhost/localhost.crt --key ./incoming-certificate/CA/localhost/localhost.decrypted.key

Here is the flow of the logs, and lines 44-73 indicate the SSL Handshake between Client and the Server.

Incase you try to access via browser, the below Error is thrown

403 Forbidden Error on accessing protected endpoint via browser

For GUI user’s here’s the setup using Postman,

Try using curl in verbose mode as it provides the Handshake details between Client & Server

Here is the complete work on github (including all the logs)


Enterprises rely on mTLS for inbound API calls into their Private Network (in addition to credentials / oAuth Tokens). And it’s a Golden Standard when Regulator Systems want to communicate, where they prefer this.

API Gateways in DMZ’s are configured with the certificate, Root & Intermediate CA’s for every API’s (or context path). Refer my previous blog on API gateway.





Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Java Concurrency - Part 2: High Level Concurrency Objects


Transformation towards Self-healing organisation in the example of Count Basie Orchestra, and…

Using Cookies to Create Realistic Lighting in Unity

Sports Bike Wallpaper 2022

The world 1st ONE character TLD

Validating Urls In Ruby

Zerubbabel vs the JVM

How to use wsl Ubuntu Terminal with Ruby, RUBY ON RAILS, MYSQL AND POSTGRESQL DATABASES ,GIT .

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
Vignesh Thirunavukkarasu

Vignesh Thirunavukkarasu

More from Medium

Build Customised Zeppelin Docker Image

Storage Space Reduced to One-Tenth of MySQL — The Application of TDengine in Monitoring Scenario…

Configure Hadoop and start cluster services using Ansible Playbook

Export all API names from APIGEE Cloud