A TLS 1.3 Question That Probably Cost Me a Job

Got asked about TLS 1.3 health monitors in an interview. Blanked. Walked out knowing I didn’t have it.

So I went home and labbed it until I understood every layer of what was actually happening — and why nobody’s blog post explains it clearly.

This is for the engineers who, like me, have been heads-down on operations and missed a few laps around the F5 feature track.

I was bummed, so I brought it up with a good friend of mine named Mike. He said “This may look like a failure, but you are in the right mindset. You are looking to fill that gap with knowledge so you will never be tripped up by that question again. Let me explain a few things to you…” So he did, and a lightbulb came on. Everything made sense. He also gave me some advice I think I will be taking to my grave: the best way to remember something is to teach it. That is the inspiration of this article.


Scenario: You are an F5 LTM administrator and your business asks you to update the server-side SSL connections to TLS 1.3 because regulations require end-to-end encryption with a fast, robust protocol. No problem! You metaphorically head over to the staging lab where the server team has already done their part — all the servers now require TLS 1.3 connections and the pool members are down. You make the needed adjustments to your Server SSL profiles and… everything is still down. Hmm. You confirm the server team set everything up correctly. Still a red ball. You are searching the web, reading docs, and nothing is really standing out — maybe you are asking the wrong questions, or you found the answer and it worked, but you don’t exactly know why.


The Problem: bigd and TLS 1.3 Don’t Get Along

Here is the simple, quick answer — especially if you are using SNI. The problem is with the limited SSL stack running in bigd, the process that handles F5 health monitors by default. Even if you could update the SSL stack in bigd, there is a second, separate problem: bigd only knows IP addresses. It has no hostname context to put in the SNI field. So the SNI field in the ClientHello comes out blank, and your backend server — which requires strict SNI — fires back a fatal alert and slams the door. You can clearly see both of these failures in tshark, and I will show you exactly what that looks like.

I had to understand this so I don’t end up in the position I was in at the beginning of this story. So I spent the weekend setting up a lab with a few nginx servers running a couple of pages, offering only TLS 1.3 with strict server name enforcement. I configured my 443 Virtual Server with SSL offloading so I was sending traffic to the servers on port 80. I then started my scenario.

I wanted to make sure there was no downtime to the site (to speak of). I built out my Server SSL profile with only TLS 1.3 support and the server-name field set to my website name (www.lab.davsmith.com). I then set up my HTTPS monitor applying my newly created Server SSL profile. Continuing, I built out my 443 pool with the previously mentioned monitor. The pool did not come up. I then set up a couple of tshark captures to see the traffic in flight. (described in detail below)


Step 1 — Enable In-TMM Monitoring and Watch the Pool Come Up

The fix is to enable In-TMM monitoring. This moves the monitoring process out of the bigd daemon into TMM — the Traffic Management Microkernel — where it has the full TLS stack, knows about hostnames, and can properly populate the SNI field. It also happens to be faster and more efficient without the bigd overhead.

I asked myself: “Why is this not enabled by default?” The main reason is that it moves processing into TMM and could — and has — destabilized traffic processing. There are other reasons too, like a known bug with monitor timeouts, backwards compatibility concerns, and stricter HTTP compliance that can break poorly formatted existing monitors. The possibility of destabilizing traffic flow is a pretty big one for most folks, so F5 ships it off and lets you opt in.

The command is a global database variable — it applies to all monitors on the system, not just one:

tmsh modify sys db bigd.tmm value enable

⚠️ Side Quest Note: The server-name field in your Server SSL profile must have the full hostname associated with the cert (wildcard or exact), AND your servers must actually answer to that name. In my case I had to set www.lab.davsmith.com up as an alias on the backend servers. This cost me a few hours of troubleshooting. I am not a web server admin… yet. Also — and this one stings — the Host: header in your monitor send string has to match that same hostname. If it doesn’t, you will get a 200 from tshark and a red ball from the pool. Ask me how I know.


The tshark Proof

This is where it gets good. Let me show you exactly what is happening on the wire, before and after.

Testing Protocol

  1. BIG-IP LTM is doing SSL offloading on Virtual Server lab.davsmith.com-443. Backend pool is on port 80.
  2. Create new pool going to backend servers using port 443.
  3. Create new Server SSL profile with TLS 1.3 only (S-TLS_1.3-only).
  4. Create new monitor MON-HTTPS-TLS13 and apply the new Server SSL profile.
    • Check pool → down
    • Run tshark → find the reason
  5. Enable In-TMM monitoring.
    • Check pool → UP
    • Run tshark → see what the communication should look like
  6. Apply Server SSL profile and new pool to the Virtual Server.
  7. Test with cURL or your favorite browser.

4.1 — In-TMM Monitoring Disabled. Pool is Down.

root@(bigip201)(cfg-sync In Sync)(Active)(/Common)(tmos)# list sys db bigd.tmm
sys db bigd.tmm {
    value "disable"
}

Ltm::Pool: pool-443-tls13-lab
  Availability : offline
  Reason       : The children pool member(s) are down
  Available Members : 0

  | Ltm::Pool Member: web-221.lab.davsmith.com:443
  |   Availability : offline
  |   Reason       : MON-HTTPS-TLS13: No successful responses received before deadline.
  |   Monitor Status : down

4.2 — tshark Shows Why the Health Checks Are Failing

10.0.2.201 = BIG-IP 201 Self IP (the monitor / client side) 10.0.2.221 = Pool member web-221 (the server side)

# Command:
tshark -i inside -f "host 10.0.2.221 and port 443" \
  -T fields \
  -e frame.time -e ip.src -e ip.dst \
  -e tls.record.content_type \
  -e tls.alert_message.level \
  -e tls.alert_message.desc \
  -e tls.handshake.type \
  -e tls.handshake.ciphersuite \
  -e tls.handshake.extensions_server_name \
  2>/dev/null

Apr 14, 2026 22:12:34.630301000    10.0.2.201 → 10.0.2.221    ← TCP SYN        (1 of 3-way handshake)
Apr 14, 2026 22:12:34.630895000    10.0.2.221 → 10.0.2.201    ← TCP SYN-ACK    (2 of 3-way handshake)
Apr 14, 2026 22:12:34.631186000    10.0.2.201 → 10.0.2.221    ← TCP ACK        (3 of 3-way handshake — connection established)

Apr 14, 2026 22:12:34.631308000    10.0.2.201 → 10.0.2.221    content_type=22, handshake_type=1, ciphers=0xc02f,0xc027,...  SNI=(empty)
                                                                ↑ Content type 22 = TLS Handshake
                                                                ↑ Handshake type 1 = ClientHello
                                                                ↑ Ciphers offered are TLS 1.2 — no TLS 1.3 ciphers (0x1301/1302/1303)
                                                                ↑ SNI field is BLANK — bigd has no hostname context to send

Apr 14, 2026 22:12:34.632043000    10.0.2.221 → 10.0.2.201    ← TCP ACK

Apr 14, 2026 22:12:34.632300000    10.0.2.221 → 10.0.2.201    content_type=21, alert_level=2, alert_desc=112
                                                                ↑ Content type 21 = TLS Alert
                                                                ↑ Alert level 2 = Fatal (this is not a warning — the server is done)
                                                                ↑ Alert description 112 = unrecognized_name (no SNI = no idea who you are)

Apr 14, 2026 22:12:34.632327000    10.0.2.221 → 10.0.2.201    ← TCP FIN-ACK    (server closes the connection)
Apr 14, 2026 22:12:34.632547000    10.0.2.201 → 10.0.2.221    ← TCP ACK
Apr 14, 2026 22:12:34.632593000    10.0.2.201 → 10.0.2.221    ← TCP FIN-ACK    (BIG-IP acknowledges defeat)
Apr 14, 2026 22:12:34.633505000    10.0.2.221 → 10.0.2.201    ← TCP ACK        (session wrapped up)

The same story in the more familiar tshark output:

tshark -i inside -f "host 10.0.2.221 and port 443" 2>/dev/null

  1  0.000000   10.0.2.201 → 10.0.2.221   TCP   [SYN]                     ← 3-way handshake starts
  2  0.013542   10.0.2.221 → 10.0.2.201   TCP   [SYN, ACK]
  3  0.014280   10.0.2.201 → 10.0.2.221   TCP   [ACK]                     ← TCP connected
  4  0.014327   10.0.2.201 → 10.0.2.221   TLSv1 Client Hello              ← bigd sends TLSv1 ClientHello — no SNI, no TLS 1.3 ciphers
  5  0.016008   10.0.2.221 → 10.0.2.201   TCP   [ACK]
  6  0.016050   10.0.2.221 → 10.0.2.201   TLSv1.2 Alert (Fatal, Unrecognized Name)  ← server rejects — doesn't know who you are
  7  0.016060   10.0.2.221 → 10.0.2.201   TCP   [FIN, ACK]                ← server closes the connection
  8  0.016333   10.0.2.201 → 10.0.2.221   TCP   [ACK]
  9  0.016360   10.0.2.201 → 10.0.2.221   TCP   [FIN, ACK]                ← BIG-IP closes its side
 10  0.029553   10.0.2.221 → 10.0.2.201   TCP   [ACK]                     ← done. pool member marked DOWN.

Two problems visible right there on the wire:

  • Line 4: bigd sends a TLSv1 ClientHello — not TLS 1.3. The ciphers it offers are old. The server requires TLS 1.3 and doesn’t care.
  • Line 4: The SNI field is empty — bigd only knows the pool member’s IP address. No hostname, no SNI.
  • Line 6: The server fires back a Fatal Alert 112 (unrecognized_name) and closes the door.

The backend server is perfectly healthy. It just has no idea who is knocking.


5.1 — Enable In-TMM Monitoring. Pool Comes Up.

root@(bigip201)(cfg-sync In Sync)(Active)(/Common)(tmos)# modify sys db bigd.tmm value enable
root@(bigip201)(cfg-sync In Sync)(Active)(/Common)(tmos)# list sys db bigd.tmm
sys db bigd.tmm {
    value "enable"
}

Ltm::Pool: pool-443-tls13-lab
  Availability : available
  Reason       : The pool is available
  Available Members : 3

  | Ltm::Pool Member: web-221.lab.davsmith.com:443  → Monitor Status: up ✅
  | Ltm::Pool Member: web-222.lab.davsmith.com:443  → Monitor Status: up ✅
  | Ltm::Pool Member: web-223.lab.davsmith.com:443  → Monitor Status: up ✅

5.2 — tshark Shows What a Healthy TLS 1.3 Handshake Looks Like

Apr 14, 2026 23:07:44.972299000    10.0.2.201 → 10.0.2.221    ← TCP SYN
Apr 14, 2026 23:07:44.973024000    10.0.2.221 → 10.0.2.201    ← TCP SYN-ACK
Apr 14, 2026 23:07:44.973109000    10.0.2.201 → 10.0.2.221    ← TCP ACK         (3-way handshake complete)

Apr 14, 2026 23:07:44.973145000    10.0.2.201 → 10.0.2.221    content_type=22, handshake_type=1
                                                                ciphers=0xc013,0xc014,0x002f,0x0035,0x1301,0x1302,0x1303,0x00ff
                                                                SNI=www.lab.davsmith.com
                                                                ↑ Content type 22 = TLS Handshake
                                                                ↑ Handshake type 1 = ClientHello
                                                                ↑ 0x1301, 0x1302, 0x1303 = TLS 1.3 cipher suites — now present
                                                                ↑ SNI = www.lab.davsmith.com — TMM knows the hostname. Server knows who's knocking.

Apr 14, 2026 23:07:44.973744000    10.0.2.221 → 10.0.2.201    ← TCP ACK

Apr 14, 2026 23:07:44.974451000    10.0.2.221 → 10.0.2.201    content_type=22,20, handshake_type=2, cipher=0x1302
                                                                ↑ Content type 22 = Handshake, 20 = ChangeCipherSpec (compatibility)
                                                                ↑ Handshake type 2 = ServerHello
                                                                ↑ 0x1302 = TLS_AES_256_GCM_SHA384 — TLS 1.3 cipher selected ✅
                                                                ↑ Handshake completes. Everything after this is encrypted application data.

The more familiar output, this time from web-222:

tshark -i inside -f "host 10.0.2.222 and port 443" 2>/dev/null

  1  0.000000   10.0.2.201 → 10.0.2.222   TCP     [SYN]
  2  0.001812   10.0.2.222 → 10.0.2.201   TCP     [SYN, ACK]
  3  0.001913   10.0.2.201 → 10.0.2.222   TCP     [ACK]                                 ← TCP connected
  4  0.001945   10.0.2.201 → 10.0.2.222   TLSv1   Client Hello                          ← TMM sends ClientHello with SNI + TLS 1.3 ciphers
  5  0.003176   10.0.2.222 → 10.0.2.201   TCP     [ACK]
  6  0.003971   10.0.2.222 → 10.0.2.201   TLSv1.3 Server Hello, Change Cipher Spec, Application Data  ← TLS 1.3 accepted. Done in 1 round trip.

Night and day.


Final Config

ltm profile server-ssl S-TLS_1.3-only {
    cipher-group TLS1.3
    options { dont-insert-empty-fragments no-ssl no-tlsv1 no-tlsv1.1 no-tlsv1.2 no-dtlsv1.2 }
    server-name www.lab.davsmith.com
    defaults-from serverssl
}

ltm monitor https MON-HTTPS-TLS13 {
    interval 5
    timeout 16
    recv 200
    send "HEAD /health HTTP/1.1\r\nHost: www.lab.davsmith.com\r\nConnection: Close\r\n\r\n"
    ssl-profile /Common/S-TLS_1.3-only
    defaults-from https
}

ltm pool pool-443-tls13-lab {
    members {
        web-221.lab.davsmith.com:https { address 10.0.2.221 }
        web-222.lab.davsmith.com:https { address 10.0.2.222 }
        web-223.lab.davsmith.com:https { address 10.0.2.223 }
    }
    monitor MON-HTTPS-TLS13
}

ltm virtual lab.davsmith.com-443 {
    destination 10.0.1.101:https
    pool pool-443-tls13-lab
    profiles {
        C-TLS_1.3-only { context clientside }
        S-TLS_1.3-only { context serverside }
        tcp { }
    }
    serverssl-use-sni enabled
}

sys db bigd.tmm {
    value "enable"
}

End-to-end curl from the workstation confirms the full path is working:

david@xorg:~$ curl -IvL --tlsv1.3 https://www.lab.davsmith.com/health

* Connected to www.lab.davsmith.com (10.0.1.101) port 443
* TLSv1.3 (OUT), TLS handshake, Client hello (1)
* TLSv1.3 (IN), TLS handshake, Server hello (2)
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8)
* TLSv1.3 (IN), TLS handshake, Certificate (11)
* TLSv1.3 (IN), TLS handshake, CERT verify (15)
* TLSv1.3 (IN), TLS handshake, Finished (20)
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey
* Server certificate:
*   subject: CN=*.lab.davsmith.com
*   subjectAltName: host "www.lab.davsmith.com" matched cert's "*.lab.davsmith.com"
*   SSL certificate verify ok.

< HTTP/1.1 200 OK

Takeaway

My overall takeaway from this experience is a good one. I got to spend an hour in an interview with someone who was very knowledgeable and quickly found my weak points. He did not just write me off and end the interview — he spent his valuable time and gave me some clues on where the gaps were and how to fill them. Although I am pretty sure I did not get that job, I will have a positive memory of that interview and how it made me dig a little deeper to start rebuilding the knowledge I lost while working on other projects.

Even though this was a disappointing — but ultimately good — experience, I can apply the same strategy to the bad experiences I will probably have in future interviews. The lab I built will be a great learning tool and I plan to spend a lot of time in it studying for the next chapter in my career.

I only thought I understood these concepts after Mike explained it, but writing this article and wanting to make sure I got it right forced me to dig deeper and make sure I “Got it”. Mike was right. Teaching it is how you own it.


References

Comments

Comments are powered by GitHub Discussions via Giscus. Sign in with your GitHub account to leave a reply or a reaction.