Have a look at the 3-way TCP and the TLS handshake packets locally
Have a look at the 3-way TCP and the TLS handshake packets locally
Have you ever wanted to really understand what’s happening when you visit a secured website—down to each handshake and message exchanged? Here’s a developer-friendly, step-by-step lab that lets you stand up real HTTPS with your own CA, capture the raw TLS handshake, and dissect it packet-by-packet with modern CLI tools.
Generate a Local CA, Certificate, and Private Key
The first step to a realistic HTTPS lab is a certificate that your browser trusts. Rather than fighting with self-signed “unsafe” alerts, we’ll use mkcert, a simple tool that generates and installs a locally-trusted Certificate Authority (CA).
# Install dependenciesbrew install mkcertbrew install nss # to support Firefox trust store
# Install the local CA in the system trust store so your browser will recognize its signaturesmkcert -installmkcert -CAROOT # to see where your CA’s root cert and key are stored
# Create a wildcard cert and key for your domain (e.g., lab.local, *.lab.local)# Note: X.509 wildcard certs only support one level of depthmkdir ~/.mkcertmkcert \ -cert-file ~/.mkcert/lab.local.crt \ -key-file ~/.mkcert/lab.local-key.pem \ "lab.local" "*.lab.local"
# Confirm your certificate contains all intended Subject Alternative Names (SANs)openssl x509 -in ~/.mkcert/lab.local.crt -text -noout | grep -A 1 "Subject Alternative Name"
# Map lab.local and its subdomains to 127.0.0.1 for local developmentecho "127.0.0.1 lab.local test.lab.local" | sudo tee -a /etc/hosts
# Double-check your hostnames resolveping -c 2 test.lab.localIf you need wildcard subdomains beyond /etc/hosts, set up dnsmasq for local DNS:
address=/.lab.local/127.0.0.1This ensures any *.lab.local hostname points to your local machine.
Run a Simple Nginx Server on Port 443 with HTTPS
With your trusted cert and key ready, let’s create a minimal but functional Nginx HTTPS web server. We’ll use Docker to avoid polluting your host environment.
First, write a suitable nginx.conf:
cat <<EOF > ~/nginx.confevents {}
http { server { listen 443 ssl; server_name lab.local;
ssl_certificate /etc/nginx/certs/lab.local.crt; ssl_certificate_key /etc/nginx/certs/lab.local-key.pem;
location / { root /usr/share/nginx/html; index index.html; } }}EOFNow run the server—mounting both your config and your generated certs directory:
docker run --name nginx-https -p 443:443 \ -v ~/nginx.conf:/etc/nginx/nginx.conf:ro \ -v ~/.mkcert:/etc/nginx/certs:ro \ -d nginx
# (Optional) See what port is exposeddocker inspect --format='{{json .NetworkSettings.Ports}}' nginx-https | jq
# Test your HTTPS endpoint; you should see headers and no certificate warningcurl -I https://lab.local
# Clean up when finisheddocker stop nginx-httpsdocker rm nginx-httpsrm ~/nginx.confThis configuration means any HTTPS request to lab.local on port 443 will be handled by nginx, using your trusted dev certificate for encryption.
Capture the TCP Traffic Using tcpdump
The next goal is to observe the raw network traffic, including both the TCP handshake and the start of TLS negotiation.
First, identify which interface represents your local network loopback (often lo0 on macOS, lo on Linux):
ifconfig -aThen run tcpdump to show the traffic as it happens:
sudo tcpdump -i lo0 host lab.local and port 443
# Or save a copy to inspect later with Wireshark/tsharksudo tcpdump -i lo0 host lab.local and port 443 -w lab-local-443.pcapNow, by connecting to your server from a browser or curl, you’ll generate encrypted traffic—perfect for analysis!
Analyze the 3-way TCP and TLS Handshake Packets with tshark
With the capture file ready, it’s time for deep protocol inspection using tshark, the CLI-powered cousin of Wireshark. This is where all the magic becomes visible.
First, ensure tshark is installed and ready:
brew install wiresharkwhich tsharktshark --version1. Review the TCP 3-way handshake:
The handshake between client and server establishes a TCP connection:
echo -e "No.\tTime\t\tSource\t\tDestination\tProto\tLength\tInfo"tshark -r lab-local-443.pcap -Y "tcp.stream==0" \ -T fields \ -e frame.number \ -e frame.time_relative \ -e ip.src \ -e ip.dst \ -e _ws.col.Protocol \ -e frame.len \ -e _ws.col.Info \ | head -n 3Sample output:
No. Time Source Destination Proto Length Info1 0.000000000 127.0.0.1 127.0.0.1 TCP 68 60630 → 443 [SYN] Seq=0 Win=65535 Len=0 MSS=16344 WS=64 TSval=775283515 TSecr=0 SACK_PERM2 0.000285000 127.0.0.1 127.0.0.1 TCP 68 443 → 60630 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=16344 WS=64 TSval=4055471285 TSecr=775283515 SACK_PERM3 0.000360000 127.0.0.1 127.0.0.1 TCP 56 60630 → 443 [ACK] Seq=1 Ack=1 Win=408320 Len=0 TSval=775283516 TSecr=4055471285These are, respectively, the SYN, SYN-ACK, and ACK packets showing TCP successfully connecting your client and server.
2. Inspect the TLS handshake itself:
After TCP is established, tshark lets you follow the TLS handshake messages, revealing each phase of the session negotiation:
echo -e "No.\tTime\t\tSource\t\tDestination\tProto\tLength\tInfo"tshark -r lab-local-443.pcap -Y "tcp.stream==0 && tls.handshake" \ -T fields \ -e frame.number \ -e frame.time_relative \ -e ip.src \ -e ip.dst \ -e _ws.col.Protocol \ -e frame.len \ -e _ws.col.InfoYou might see:
No. Time Source Destination Proto Length Info5 0.001147000 127.0.0.1 127.0.0.1 TLSv1 375 Client Hello (SNI=lab.local)7 0.008839000 127.0.0.1 127.0.0.1 TLSv1.3 1697 Server Hello, Change Cipher Spec, Application Data, Application Data, Application Data, Application DataThis details the start of the encrypted negotiation. In modern TLS, most of the handshake moves into encrypted payloads (shown as Application Data).
Clean Up
Remove the packet capture to keep your system tidy:
rm lab-local-443.pcapConclusion
By walking through these steps, you’ve built a modern local HTTPS environment and unpacked the nitty-gritty of both TCP and TLS. You created your own CA, learned how browsers trust certificates, observed the mechanics of establishing encrypted connections, and witnessed the handshake as it truly occurs in the wild.
These skills are invaluable for debugging complex HTTPS issues, understanding how SSL/TLS works, and building your intuition about packets and protocols—all with just core open source tools. You can easily adapt this setup for more advanced experiments, from HTTP/2 over TLS to client certificates and beyond.
Happy packet hunting!