ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 71

  1. Blog
  2. /Observability & SRE

OpenSSL TLS Handshake Lab: Full vs Resume on Localhost

Hands-on TLS handshake lab: Python full HS p50 TLS1.3 1.51 ms vs TLS1.2 1.20 ms (~72× TCP); openssl resume ~1.5× / 4.1×. Localhost RSA numbers, no Docker.

Aditya Challa·30 September 2026·5 min read

Hands-on
On this page
  1. Intro — what this post promises
  2. What we compared
  3. Lab topology
  4. Arm A — Python full handshake (p50, n=50)
  5. Arm B — openssl s\_time new vs reuse (3 s wall)
  6. Python session\_reused stayed false
  7. When resume / keepalive still win the design
  8. Pitfalls we hit (or avoided)
  9. Practical checklist
  10. Verdict

Intro — what this post promises

Every new HTTPS connection pays a TLS handshake before the first byte of app data. Session resume (tickets / IDs) is supposed to cut that cost. On a real WAN the 1-RTT story dominates; on localhost you mostly see crypto + userspace.

This is a hands-on lab with measured numbers:

  1. Python ssl.wrap_socket full handshake p50 for TLS 1.3 vs TLS 1.2 vs plain TCP connect.
  2. openssl s_time -new vs -reuse connection rates on the same self-signed cert.
  3. How much resume helped on this OpenSSL 3.5 stack — and where Python’s session_reused stayed false.

Related links:

  • HTTP Keep-Alive vs Connection: close localhost lab
  • TCP_NODELAY vs Nagle localhost lab
  • Unix domain socket vs TCP localhost lab
  • Why your average latency graph is lying (p50 / p95 / p99)

Lab honesty (30 Sep 2026 IST): Shared Linux lab box (8 cores, kernel 6.12). Python 3.13.5, OpenSSL 3.5.7. Self-signed RSA-2048 CN=localhost. Loopback only. No Docker. No GPU. No API keys. Affiliates: 0.

Verdict up front: Python full HS TLS1.3 p50 = 1.51 ms, TLS1.2 = 1.20 ms, plain TCP 0.021 ms (~72× / ~57×). openssl s_time resume: TLS1.3 ~1.5×, TLS1.2 ~4.1× more conn/real-s than -new.


What we compared

ArmToolWhat it measures
APython sslFull handshake wall p50 (n=50)
Bopenssl s_time -newFresh handshakes / real second
Copenssl s_time -reuseSession-resume handshakes / real second
Baselinebare connect()TCP-only connect p50

Related links:

  • openssl s_time man page
  • ssl — TLS/SSL wrapper (Python)

Lab topology

Client ──TCP──► Server (127.0.0.1)
         └─ TLS wrap (full HS) or openssl s_time -new/-reuse
Cert: self-signed RSA-2048 CN=localhost
Versions pinned: TLSv1.3-only and TLSv1.2-only arms

Arm A — Python full handshake (p50, n=50)

Setupp50 msp95 msvs plain TCP
TLS 1.3 full1.5072.948~72×
TLS 1.2 full1.2002.908~57×
plain TCP connect0.0210.0551×

On this localhost RSA stack, a full TLS handshake is tens of microseconds of TCP plus ~1.2–1.5 ms of crypto/userspace. TLS 1.2 edged TLS 1.3 here — that is a localhost RSA result, not a WAN 1-RTT claim.


Arm B — openssl s_time new vs reuse (3 s wall)

VersionModeConnectionsConn / real svs -new
TLS 1.3-new18526171×
TLS 1.3-reuse2768923~1.49×
TLS 1.2-new21437141×
TLS 1.2-reuse88402947~4.13×

Resume helped both versions. TLS 1.2 resume was the big win on this cert/stack (~4×). TLS 1.3 resume still ~1.5× over full — smaller relative gain when the full handshake is already cheaper in RTT terms on the wire (here we only see local CPU).


Python session_reused stayed false

We tried passing SSLSession back into wrap_socket(..., session=...). session objects existed, but session_reused remained False for both TLS 1.2 and 1.3 on Python 3.13.5 / OpenSSL 3.5.7. Resume throughput numbers above are therefore openssl s_time -reuse only — labeled as such, not invented Python wins.

Related links:

  • HTTP Keep-Alive vs Connection: close localhost lab

When resume / keepalive still win the design

  • Short-lived clients that reconnect often (CI curls, health probes, serverless cold paths).
  • HTTP/1.1 without keep-alive — you pay handshake × request; pair this lab with the keep-alive post.
  • TLS 1.2 estates where resume still shows multi-× local gains (Arm B).
  • Not a substitute for measuring WAN RTT — localhost hides the 1-RTT advantage TLS 1.3 is famous for.

Pitfalls we hit (or avoided)

  1. Calling localhost TLS a WAN result — we measure crypto/userspace, not cross-city RTT.
  2. Assuming Python session= resumes — it did not here; verify session_reused.
  3. Comparing TLS 1.3 vs 1.2 without pinning — arms forced single versions.
  4. Ignoring the TCP baseline — 0.021 ms shows almost all cost is above TCP.
  5. RSA-2048 self-signed only — ECDSA / different ciphers will move absolute ms.

Practical checklist

  • Prefer connection reuse / HTTP keep-alive / HTTP/2+ before chasing cipher micro-wins.
  • Confirm resume with session_reused, openssl s_time -reuse, or server metrics — do not assume.
  • Report version + cert type + new vs reuse + localhost vs WAN with every handshake claim.
  • On localhost, expect ~1 ms-class full HS for RSA; treat sub-ms claims with suspicion unless ECDSA/hardware offload.
  • Pair with keep-alive / NODELAY posts when the question is request RPS, not handshake alone.

Verdict

Localhost self-signed RSA-2048: Python full handshake p50 TLS1.3 = 1.51 ms, TLS1.2 = 1.20 ms, plain TCP 0.021 ms. openssl s_time resume boosted real conn/s by ~1.5× (TLS1.3) and ~4.1× (TLS1.2). Python session_reused did not flip true — resume evidence is openssl-only on this stack.

Evidence path on the lab box: lab-evidence/26-openssl-tls-handshake/results/. Affiliates: 0.

tls handshake latencyopenssl s_timetls session resumetls 1.3 vs 1.2localhost tls labsrepython sslconnection reuse

Lab evidence

What I found running this

Lab 30 Sep 2026 IST. Python 3.13.5 + OpenSSL 3.5.7. Self-signed RSA-2048 CN=localhost. Python wrap_socket full HS p50 (n=50): TLS1.3 1.507 ms; TLS1.2 1.200 ms; plain TCP connect 0.021 ms (~72× / ~57×). openssl s_time 3s wall real/s: TLS1.3 new 617.3 reuse 922.7 (~1.49×); TLS1.2 new 714.3 reuse 2946.7 (~4.13×). Python session_reused stayed False — resume from openssl -reuse only. No Docker. Affiliates: 0. Evidence: lab-evidence/26-openssl-tls-handshake/.

Notes when a lab post goes up

Occasional email for new hands-on reviews. No sequence and no sponsors.

Related links

  • Plate 12

    islice vs list Slice Windows: Localhost Lab

    Hands-on itertools.islice vs list slice window lab: real ops/s taking ranges from sequences, measured on Linux localhost in this hands-on lab for SREs.

    Observability & SRE · 1 Oct 2026

  • Plate 07

    heapq.merge vs sorted(chain): Localhost Lab

    Hands-on heapq.merge vs sorted(chain) multi-way merge: real records/s on pre-sorted lists, measured on Linux localhost today in this hands-on lab for SREs.

    Observability & SRE · 1 Oct 2026

  • Plate 88

    mmap Write vs pwrite Region: Localhost Lab

    Hands-on mmap MAP_SHARED write+msync vs pwrite region update: real MB/s with durability labels, measured on Linux localhost in this hands-on lab for SREs.

    Observability & SRE · 1 Oct 2026

On this page

  1. Intro — what this post promises
  2. What we compared
  3. Lab topology
  4. Arm A — Python full handshake (p50, n=50)
  5. Arm B — openssl s\_time new vs reuse (3 s wall)
  6. Python session\_reused stayed false
  7. When resume / keepalive still win the design
  8. Pitfalls we hit (or avoided)
  9. Practical checklist
  10. Verdict
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove