Nuances: TLS to SQL Server
What could surprise you here, why it is so, and what to do. Each entry has a level, 1 (day one) to 5 (expert): the index lists them all by level.
SQL Server connections are verified, and trust_server_certificate is for labs (level 3)
- What: every SQL Server connection is TLS for the whole session, and the engine checks the server's certificate against this server's trusted certificates plus
ca:(acertificates:entry), for the name inhost.trust_server_certificate: trueaccepts any certificate: anyone who can answer at the server's address (a DNS entry, a spoofed address) gets the login and every query. The start log andductus validatewarn about it for a server on another machine, doctor says "not verified (trust_server_certificate)" with what verifying would make of the certificate, and the Connections page shows the lock open. It's refused next toca:orname_in_certificate:(they'd never be checked). There's noencrypt: false: SQL Server would then encrypt only the login and send queries and results readable. - Why: many SQL Servers present the certificate SQL Server generates for itself when none is configured, which nobody can verify: it's self-signed, an old X.509 v1 certificate (refused as such: "an old X.509 v1 certificate, which can't be verified"), and its name is always
SSL_Self_Signed_Fallback, whatever the server is called (seen on 2022 and 2025). So drivers' "trust the server certificate" switch became a habit. - What to do: ask the DBA for a certificate from your CA for the name you connect by (SQL Server Configuration Manager on Windows; mssql-conf
network.tlscertandnetwork.tlskeyon Linux), name the CA inca:unless this server already trusts it, and droptrust_server_certificate. Refusals say what's wrong: "not trusted: its issuer isn't one this server trusts", "the certificate isn't for 10.1.2.3", "has expired".
Connecting by a name the certificate doesn't list: name_in_certificate (level 3)
- What: the certificate must be for the name in
host. An availability group listener, a DNS alias or an IP address often isn't on it.name_in_certificate: sql1.hospital.org(ADO.NET's HostNameInCertificate) checks the certificate for that name instead, while connecting tohost. - What to do: use it rather than
trust_server_certificate: the certificate is still checked, only for another of the server's names. Better still, ask for the listener's name on the certificate.
encrypt: strict is TDS 8: TLS before anything (level 3)
- What: by default (
mandatory), as every SQL Server client, the engine first exchanges PRELOGIN packets in the clear, then makes the TLS handshake inside them; this always negotiates TLS 1.2 (TDS 7 can't carry TLS 1.3).encrypt: strictmakes the handshake first, asking fortds/8.0, with TLS 1.3 when the server allows it. Servers set to force strict accept nothing else. SQL Server accepts strict only with a certificate configured for its own host name, never with its generated one, and SQL Server 2022 on Linux refuses strict whatever it's given (tested with CU27); SQL Server 2025 on Linux accepts it. ADO.NET ignoresTrustServerCertificatewith strict and always verifies; heretrust_server_certificatestill turns verification off (for labs), warned about as without strict. - What to do: leave
encryptout unless the DBA forces strict; then setencrypt: strict. A server that doesn't accept strict closes the connection during the handshake, and the error says to leaveencrypt: strictout.
SQL Server 2022 on Linux doesn't accept encrypt: strict (level 3)
- What: tested against SQL Server 2022 on Linux (CU27, Microsoft's container image): it refuses every TDS 8 strict connection with "A valid TLS certificate is not configured to accept strict (TDS 8.0 and above) connections", whatever it's given: its generated certificate, a CA's certificate for its own host name (CN and subjectAltName, server authentication, key encipherment), TLS 1.2 and 1.3 or 1.3 alone. SQL Server 2025 on Linux (CU10) accepts strict with the same certificate. SQL Server 2022 on Windows isn't tested here (Microsoft documents strict for it). The default (
mandatory: TLS after PRELOGIN, verified) works with both. - Why: not documented: Microsoft's page on encrypted connections for SQL Server on Linux doesn't mention strict for any version. Measured, not explained.
- What to do: against SQL Server 2022 on Linux, leave
encryptout: the connection is still TLS throughout and verified. Useencrypt: strictwith 2025, or on Windows once tried there. The engine's own tests run against 2025 for this reason.
SQL Server's certificate is read through PRELOGIN, and watched when verified (level 3)
- What: doctor and the engine's own check read a SQL Server's certificate as the engine's connection meets it: a PRELOGIN packet asking for encryption (its answer gives the server's version), then the handshake inside PRELOGIN packets (with
encrypt: strict, plain TLS). Nothing logs in, in the scheduled check. A verified connection's certificate is kept and alerted at 30, 14 and 7 days under the entry's name; one withtrust_server_certificateis described, not watched (an expired certificate doesn't stop a connection that checks nothing). A named instance withoutport:is found through the SQL Server Browser first, and its certificate is kept under the port it answered with. The check takes ~35 ms through PRELOGIN (TLS 1.2), ~0.55 s with strict (TLS 1.3: see "Doctor makes the engine's handshake"). - What to do: verify the connection, and the certificate is watched. A SQL Server reads its certificate at start: after the DBA renews it, the server's restart is what makes it count (and "Check now" shows it).
Verification costs nothing measurable (level 4)
- What: opening a SQL Server connection (PRELOGIN, TLS, login, a first query) took ~120–140 ms in every mode against a local SQL Server 2025:
trust_server_certificate, verified, and strict verified (scripts/bench-mssqltls.sh; differences within the noise between rounds). Pools keep their connections (closed after 5 idle minutes), so messages rarely wait for one. - What to do: verify; there's no speed to gain by not.