Three different facts get collapsed into one line in most OpenVPN TLS discussions: what your client build can do, what the profile allows, and what the session actually negotiated. A short TLS 1.3 suite list answers only the middle question halfway, and it says nothing about the protocol version floor. Work through the checks below in order and stop at the first one that does not hold, because each later check assumes the earlier ones are settled.

Check the build before you read any list

A configuration directive cannot add protocol support to a binary that lacks it. Start with the version banner, which names the compiled crypto library:

openvpn --version

A 2.6 build linked against OpenSSL accepts both tls-cipher and tls-ciphersuites. A build against mbed TLS has no TLS 1.3 suite selection at all, and the 2.6 manual marks tls-ciphersuites as an OpenSSL option. If the server was rebuilt with a different backend, the same profile can change behaviour without a single line of configuration changing.

While you are here, run openvpn --show-tls and keep the output. It is the inventory you will compare against later, and it is cheap to capture before anyone starts editing profiles.

Write the banner and the list into the change ticket, not into a chat window. When two people disagree about whether a client could offer a suite six weeks from now, the captured output settles it in one line.

Keep version bounds separate from cipher lists

In the 2.6 manual these are three controls, not one:

  • tls-cipher sets the cipher policy for TLS 1.2 and earlier, passed to OpenSSL's cipher list;
  • tls-ciphersuites selects TLS 1.3 suites;
  • tls-version-min and tls-version-max bound the protocol versions the connection may use.

So a carefully pruned TLS 1.3 suite list proves nothing about an older version still being permitted. If the requirement is "TLS 1.3 only", the version floor carries it and the suite list only narrows choices inside that floor:

tls-version-min 1.3
tls-ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256

That pair does two jobs. Deleting one of the lines leaves the other enforcing less than you think, which is the failure this whole check exists to catch.

The data channel is a fourth control. data-ciphers negotiates the symmetric cipher inside the tunnel, and its result says nothing about the control channel's TLS version. Seeing AES-256-GCM in the session summary is not evidence about TLS 1.3 either way.

Read show-tls as a local inventory

--show-tls enumerates suites supported by the crypto library this binary was linked against, in preference order. It is not a report from the gateway. The manual is explicit that the list comes from the local library, which means a laptop with OpenSSL 3.x and a router with an older build will print different lists while talking to the same server.

EvidenceWhat it actually describes
--show-tls outputCapabilities of this client build and its library
Profile directivesWhat this attempt is allowed to offer
Session log lineWhat this one negotiation selected

Suppose a client supports suites A, B and C and one session selects B. That establishes nothing about whether the server permits A or C, and nothing about whether B is its only allowed choice. Keep the three rows apart and most confusing TLS threads resolve themselves.

The inventory is still worth keeping. When a session fails, comparing the library's list against the profile's list tells you whether the client had any chance of offering what the server wanted. It also shows whether a backend change quietly removed TLS_CHACHA20_POLY1305_SHA256 from the pool that older clients depend on, which is the kind of regression that surfaces as a handful of users failing to connect while everyone else notices nothing.

Ask the gateway a question it can answer

If you administer the service, inspect the effective configuration of the running instance rather than a file on disk. OpenVPN may have been started with a different --config path, may pull includes, and may not have been restarted since the edit. The running daemon's policy is what clients meet.

If you do not administer it, ask the operator for supported client requirements or a scoped acceptance test. A rejected attempt is not automatically a rejected suite: a version floor, certificate identity checks such as remote-cert-tls, or a tls-auth versus tls-crypt mismatch can end the exchange before suite selection matters. Read the server's error before changing the client's list, because "handshake failed" covers four different causes and only one of them is a cipher mismatch.

Pointing openssl s_client at the VPN port does not reproduce the question either. The control channel runs inside OpenVPN's own framing, so a raw TLS probe tests a different protocol on a port that expects something else. Any conclusion drawn from it is about the probe, not the tunnel.

Record two results and keep them apart

A fresh permitted session produces a control channel line naming the protocol version and the selected cipher, and a separate data channel line naming the tunnel cipher. Capture both with the client version and the profile name, and note which server instance answered:

openvpn --config office.ovpn --verb 4 2>&1 | tee session.txt

Then read the two lines out of that file rather than from memory. The pair is the evidence; a suite name alone is a preference, and the version boundary is a separate statement about what the server refuses.

Run the checks in this order. Confirm the build and backend with --version. Capture --show-tls before changing anything. State the required version floor separately from the suite list. Confirm the running instance's effective policy with its owner. Observe one permitted session and, if the plan allows, one refused older-version attempt. Record the control channel and data channel results as separate findings.

If a required client cannot meet a new minimum, make that migration or exception decision explicitly instead of quietly lowering an unrelated acceptance rule to make the rollout look finished.

Ingingo zihuye