SQL Server ‘Certificate Chain Not Trusted’ Error in .NET

Last updated: October 28, 2026 · Tested with .NET 10.0.12 (SDK 10.0.401), SQL Server 2025 Express 17.0.1000.7, SQL Server LocalDB 17.0.4025.3, Microsoft.Data.SqlClient 3.1.7 / 4.0.0 / 7.1.1, System.Data.SqlClient 4.9.1, EF Core SqlServer 10.0.14, Windows 11

Short answer: “The certificate chain was issued by an authority that is not trusted” means SqlClient tried to encrypt the connection and your SQL Server presented a certificate the client machine doesn’t trust, usually the self-signed certificate SQL Server creates for itself. Since Microsoft.Data.SqlClient 4.0, Encrypt defaults to true, so a connection string that worked for years fails after a package upgrade (or an EF Core 7+ upgrade, which brings the new SqlClient). For a local or development server, add TrustServerCertificate=true. For production, install a certificate the clients trust on the SQL Server instead. I reproduced the error on SQL Server 2025 Express with four client versions; the outputs below are from those runs.

Microsoft.Data.SqlClient.SqlException: A connection was successfully established with the server, but then an error occurred during the login process. (provider: SSL Provider, error: 0 - The certificate chain was issued by an authority that is not trusted.)
Inner: Win32Exception: The certificate chain was issued by an authority that is not trusted.
Connection string option Connects to SQL Express? Encrypted? Use it for
nothing (SqlClient 4.0+) no, this error – –
TrustServerCertificate=true yes yes, server identity not checked local and dev servers
Encrypt=false or Encrypt=Optional yes no (login only) old behaviour; avoid over a network
Encrypt=Strict no (different error, see below) – SQL Server 2022+ with a trusted certificate
trusted certificate on the server yes, with the default Encrypt=true yes, identity checked production

How I Tested It

One small console program reads connection strings from a file, opens each one and asks SQL Server how the session is connected (encrypt_option and net_transport from sys.dm_exec_connections). I compiled the same file against four client packages, each in its own net10.0 project:

using var connection = new SqlConnection(cs);
connection.Open();
using var command = new SqlCommand(
    "SELECT encrypt_option, net_transport FROM sys.dm_exec_connections WHERE session_id = @@SPID", connection);
using var reader = command.ExecuteReader();
reader.Read();
Console.WriteLine($"  OK  encrypt_option={reader.GetString(0)} net_transport={reader.GetString(1)}");

The server is a default SQL Server 2025 Express install on the same machine (TCP is disabled in a default Express install, so these connections use shared memory). The certificate check happens the same way over TCP to a remote server; that’s where most people meet this error.

Why It Broke After an Upgrade

The same connection string, with no Encrypt or TrustServerCertificate setting, against three client versions:

## Mds317
> Server=localhost\SQLEXPRESS;Database=master;Integrated Security=true
  OK  encrypt_option=FALSE net_transport=Shared memory
## Mds400
> Server=localhost\SQLEXPRESS;Database=master;Integrated Security=true
  Microsoft.Data.SqlClient.SqlException: A connection was successfully established with the server, but then an error occurred during the login process. (provider: SSL Provider, error: 0 - The certificate chain was issued by an authority that is not trusted.)
  Inner: Win32Exception: The certificate chain was issued by an authority that is not trusted.
## Sds491
> Server=localhost\SQLEXPRESS;Database=master;Integrated Security=true
  OK  encrypt_option=FALSE net_transport=Shared memory

Microsoft.Data.SqlClient 3.1.7 and the old System.Data.SqlClient 4.9.1 connect without encryption. Microsoft.Data.SqlClient 4.0.0 fails with the same string. The server didn’t change; the default did. Proof that it’s only the default: setting Encrypt=true explicitly makes the old clients fail the same way:

## Mds317
> Server=localhost\SQLEXPRESS;Database=master;Integrated Security=true;Encrypt=true
  Microsoft.Data.SqlClient.SqlException: A connection was successfully established with the server, but then an error occurred during the login process. (provider: SSL Provider, error: 0 - The certificate chain was issued by an authority that is not trusted.)
## Sds491
> Server=localhost\SQLEXPRESS;Database=master;Integrated Security=true;Encrypt=true
  System.Data.SqlClient.SqlException: A connection was successfully established with the server, but then an error occurred during the login process. (provider: SSL Provider, error: 0 - The certificate chain was issued by an authority that is not trusted.)

You probably didn’t upgrade SqlClient on purpose. Common ways it arrives:

  • EF Core 7 or later. Microsoft.EntityFrameworkCore.SqlServer depends on the new SqlClient; the EF Core 7 breaking changes list “Encrypt defaults to true for SQL Server connections”. EF Core 10 shows the same message on the first query (tested below).
  • Moving off System.Data.SqlClient. On .NET 10 it still works but is marked obsolete (warning CS0618: 'SqlConnection' is obsolete: 'Use the Microsoft.Data.SqlClient package instead.'), so the switch is due anyway.
  • A Dependabot or Renovate bump across the 3.x → 4.0 line.
Microsoft.Data.SqlClient.SqlException: A connection was successfully established with the server, but then an error occurred during the login process. (provider: SSL Provider, error: 0 - The certificate chain was issued by an authority that is not trusted.)

That’s the EF Core 10 output, from this call against the same server:

var options = new DbContextOptionsBuilder<DbContext>()
    .UseSqlServer(@"Server=localhost\SQLEXPRESS;Database=master;Integrated Security=true")
    .Options;
using var db = new DbContext(options);

Every Option, Tested on SqlClient 7.1.1

> Server=localhost\SQLEXPRESS;Database=master;Integrated Security=true;TrustServerCertificate=true
  OK  encrypt_option=TRUE net_transport=Shared memory
> Server=localhost\SQLEXPRESS;Database=master;Integrated Security=true;Encrypt=false
  OK  encrypt_option=FALSE net_transport=Shared memory
> Server=localhost\SQLEXPRESS;Database=master;Integrated Security=true;Encrypt=Optional
  OK  encrypt_option=FALSE net_transport=Shared memory
> Server=localhost\SQLEXPRESS;Database=master;Integrated Security=true;Trust Server Certificate=yes
  OK  encrypt_option=TRUE net_transport=Shared memory

What the results mean:

  • TrustServerCertificate=true keeps encryption on (encrypt_option=TRUE) but skips the certificate check. The traffic is encrypted, but the client can’t tell whether it’s talking to your server or to something in between. Fine on your own machine or a dev box; on a network, it gives up the protection Encrypt=true was added for. The spelling Trust Server Certificate=yes works too.
  • Encrypt=false and Encrypt=Optional are the same thing: the pre-4.0 behaviour. Only the login packet is encrypted; queries and results go in clear text (encrypt_option=FALSE). Use it only when you knowingly accept that, for example a container network you control.

Encrypt=Strict Fails With a Misleading Error

Encrypt=Strict (SqlClient 5.0+, TDS 8.0, SQL Server 2022 and later) starts TLS before any SQL Server traffic and can’t be bypassed with TrustServerCertificate. On this machine it failed both with and without it, and the message doesn’t mention certificates at all:

> Server=localhost\SQLEXPRESS;Database=master;Integrated Security=true;Encrypt=Strict;TrustServerCertificate=true
  Microsoft.Data.SqlClient.SqlException: The client was unable to establish a connection because of an error during connection initialization process before login. Possible causes include the following:  the client tried to connect to an unsupported version of SQL Server; the server was too busy to accept new connections; or there was a resource limitation (insufficient memory or maximum allowed connections) on the server. (provider: SSL Provider, error: 0 - No process is on the other end of the pipe.)
  Inner: Win32Exception: No process is on the other end of the pipe.

The server here is SQL Server 2025, so “unsupported version” is wrong. This server had only its self-signed certificate, which Strict won’t accept whatever the other settings say, and it was reached over shared memory because TCP was off; I couldn’t separate those two causes without changing the server. If you switch to Strict and see “No process is on the other end of the pipe”, check the certificate and the protocol before you look for a version problem.

“It Works on LocalDB but Not on the Server”

The same SqlClient 7.1.1 and no options at all:

> Server=(localdb)\MSSQLLocalDB;Database=master;Integrated Security=true
  OK  encrypt_option=FALSE net_transport=Named pipe

LocalDB connects, unencrypted, even with the 4.0+ default. That’s why the error often shows up for the first time on a test server, in Docker, or in CI, while every developer’s machine is fine. Test against a real SQL Server instance (Express is enough) before you deploy a connection string change.

The Production Fix: a Certificate the Client Trusts

Keep Encrypt=true (the default) and TrustServerCertificate=false, and give SQL Server a certificate that the client machines trust: one issued by your company CA or a public CA, whose subject or SAN matches the server name in the connection string, configured in SQL Server Configuration Manager (or with mssql-conf on Linux). Then no connection string change is needed. Two SqlClient options help when that’s not quite possible:

  • HostNameInCertificate=sql-server.contoso.com (SqlClient 5.0+) when you connect through a DNS alias that isn’t in the certificate.
  • ServerCertificate=C:\certs\sql.cer (SqlClient 5.1+) pins the exact certificate file, useful for a self-signed certificate you control.

I didn’t test these two here: provisioning a server certificate needs admin changes to the SQL Server service, which I didn’t make on this machine. The behaviour is documented in Microsoft’s encryption and certificate validation page for SqlClient. Azure SQL Database already has a trusted certificate, which is why the default works there.

Gotcha: the Backslash in appsettings.json

When you add TrustServerCertificate=true to a named-instance connection string in appsettings.json, the backslash in localhost\SQLEXPRESS must be doubled. With a single backslash the app doesn’t start, and the message doesn’t point at the connection string (project path shortened to C:\app):

appsettings.bad.json: InvalidDataException: Failed to load configuration from file 'C:\app\appsettings.bad.json'.
  Inner: Could not parse the JSON file.
appsettings.good.json: Server=localhost\SQLEXPRESS;Database=Shop;Integrated Security=true;TrustServerCertificate=true
{
  "ConnectionStrings": {
    "Default": "Server=localhost\\SQLEXPRESS;Database=Shop;Integrated Security=true;TrustServerCertificate=true"
  }
}

Better still, keep TrustServerCertificate=true out of the shared appsettings.json and put it only in appsettings.Development.json or user secrets, so a production deployment can’t inherit it by accident.

Checklist

  • Error appeared after an upgrade: it’s the SqlClient 4.0 Encrypt=true default, not your server.
  • Local or dev SQL Server: TrustServerCertificate=true in the development connection string only.
  • Production: a trusted certificate on SQL Server, connection string unchanged.
  • DNS alias not in the certificate: HostNameInCertificate.
  • Encrypt=false only when unencrypted traffic is a conscious choice.
  • Encrypt=Strict and “No process is on the other end of the pipe”: check the certificate and the protocol first.
  • Tested on LocalDB only? Test once against Express or the real server.

Related: SQL injection prevention in C# · EF Core 11 breaking changes · EF Core: the model has pending changes · all ASP.NET Core tutorials

Similar Posts

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *