
TLS stands for Transport Layer Security, which is a security protocol designed to allow for secure communication between two parties (machines) over the internet. It is often used interchangeably with SSL (Secure Sockets Layer) which was used prior to TLS. One of the primary use cases of TLS is to encrypt the communication between a web application & servers over the internet with HTTPS (which denotes HTTP connection implemented over TLS). It can also be used to encrypt other communications such as email, messaging, voice over IP (VoIP).
Following are the main objectives that TLS protocol accomplishes:
Use of TLS is essential to enable encrypted communication between client & servers over the internet. Otherwise, the unencrypted data can easily be read by unintended third parties who intercept the data in transit. You definately won't want your users' private information like chats, passwords & even credit card information to be read by any 3rd party. So for any modern day software application that communicates to remote servers over the internet, use of TLS is a must. In the following sections we will explore how TLS works, a hands-on demo to setup TLS on a server, TLS setup with proxy server and mutual TLS, which is an extension of TLS.
In any client-server communication that uses TLS, the first step in establising a secure TLS communication channel is the TLS handshake. This handshake is done right after the TCP connection is established with the TCP handshake. The following diagram gives a high level overview of the steps involved in a TLS handshake.
From a bird's eye-view, following are the steps involved in a TLS handshake:
You can try running the following curl & openssl commands in your CLI to inspect the steps involved in the TLS handshake:
% curl -vI https://sundeep-blogs.vercel.app
* Host sundeep-blogs.vercel.app:443 was resolved.
* IPv6: (none)
* IPv4: 216.198.79.131, 64.29.17.131
* Trying 216.198.79.131:443...
* Connected to sundeep-blogs.vercel.app (216.198.79.131) port 443
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/ssl/cert.pem
* CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF
* ALPN: server accepted h2
* Server certificate:
* subject: CN=*.vercel.app
* start date: Feb 26 06:28:03 2026 GMT
* expire date: May 27 06:28:02 2026 GMT
* subjectAltName: host "sundeep-blogs.vercel.app" matched cert's "*.vercel.app"
* issuer: C=US; O=Google Trust Services; CN=WR1
* SSL certificate verify ok.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://sundeep-blogs.vercel.app/
* [HTTP/2] [1] [:method: HEAD]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: sundeep-blogs.vercel.app]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.7.1]
* [HTTP/2] [1] [accept: */*]
...
...
openssl s_client -connect sundeep-blogs.vercel.app:443 -msg
The above steps merely scratch the surface of TLS handshake process. Refer to the blog post by Cloudflare to dig deeper into the topic.
This is one of the most crucial steps in TLS connection establishment. It helps in preventing man-in-the-middle attacks by verifying that the server, the client is communicating with, is a legitimate one & not an imposter. During the TLS handshake process, once the sever sends the SSL certificate, client starts the verification. As part of this, the client primarily performs the following steps:
The TLS connection is successfully established only if all the above steps are validated successfully. Otherwise the connection is refused. The above section only outlines the steps involved in TLS verification from a high-level. Refer to this blog post by DigitalOcean for a more detailed information on certificate verification.
First of all we need to obtain a TLS certificate. For a production application the certificate is issued by some reputable, broswer-trusted Certificate Authorities (CAs) like Let's Encrypt, AWS, DigiCert, etc. But for our local testing we will use mkcert to generate a certificate. mkcert takes care of creating a 'Local Certificate Authority' on the machine & installs it into the system's truststore, which means the browser & tools like curl will recognize the certificate & won't give TLS validation errors.
First you need to install mkcert using your package manager:
brew install mkcert
Then setup the local CA:
mkcert -install
And, finally generate the certificates for localhost: mkcert localhost 127.0.0.1 ::1
This will generate two files i.e. localhost+2.pem (the certificate) & localhost+2-key.pem (the private key).
Then we need to write our server by loading the .pem files generated in the previous step. I've written the code for a simple HTTPs server in golang below (feel free to use any other language of your choice): package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Hello, World!")
})
port := ":8080"
log.Printf("Starting HTTPS server on https://localhost:%s\n", port)
err := http.ListenAndServeTLS(port, "localhost+2.pem", "localhost+2-key.pem", nil)
if err != nil {
log.Fatalf("Failed to start HTTPS server: %v", err)
}
}
Run the program with the following command: go run main.go
Now the server is ready to handle HTTPS traffic. If you navigate to https://localhost:8080 in your browser you should see the webpage as below. Notice the secure section:
% curl -vI https://localhost:8080
openssl s_client -connect localhost:8080 -msg
Now that we have explored how TLS can be setup in a single server, we are ready to explore the various ways TLS can be configured in a distributed cluster. In any kind of internet facing application, the application usually sits behind a reverse proxy like Nginx, through which it receives all the traffic. In such cases, we have the following options regarding how the TLS connection between the client & the proxy is forwarded to the application server. This process is known as TLS termination:
In mTLS, both the client & server have a TLS certificate which is verified by both the parties. The additional step here is that after the client verifies the server's certificate it presents its TLS certificate to the server. The server then verifies the client's certificate and grants access only if the certificate is valid.
I won't be putting the code to implement mTLS in this post, to keep it short, but its very similar to normal TLS, with the exception that on the client side too a certificate needs to be configured & a quick LLM prompt should give the results.
TLS is one of the most widely used piece of modern technology and is often overlooked by a lot of application developers. The topics we covered in the above sections barely scratch the surface of TLS & related technologies and can easily expand into individual blog posts for each of them. It was a long post & thanks for bearing with me this long. I hope you learnt something new today!