Cybersecurity / Networking

How to Fix "curl: (35) schannel: SEC_E_UNTRUSTED_ROOT" in Windows

6 min read by DebuggedIt

Quick answer

When running HTTP requests using curl on Windows, you might encounter the SSL/TLS handshake error curl: (35) schannel: SEC_E_UNTRUSTED_ROOT. This error occurs...

When running HTTP requests using curl on Windows, you might encounter the SSL/TLS handshake error curl: (35) schannel: SEC_E_UNTRUSTED_ROOT. This error occurs because curl is utilizing the native Windows Secure Channel (Schannel) library, which failed to verify the SSL/TLS certificate chain against the Windows Local Computer Trusted Root Certification Authorities store. Understanding how to register missing intermediate certificates, update root CAs, or instruct curl to use an alternative backend like OpenSSL resolves this issue without sacrificing network security.

The Problem

When issuing a GET or POST request to a HTTPS endpoint via Command Prompt, PowerShell, or automated deployment scripts on Windows, curl terminates immediately with exit code 35. The error output appears exactly as follows:

curl: (35) schannel: SEC_E_UNTRUSTED_ROOT (0x80090325) - The certificate chain was issued by an authority that is not trusted.

In some automated build environments or custom scripting pipelines, you may also encounter related variants of this exact Schannel initialization failure, such as:

curl: (35) schannel: Certificate status could not be determined.
curl: (35) schannel: failed to receive handshake, SSL/TLS connection failed
schannel: SEC_E_UNTRUSTED_ROOT (0x80090325)

This error prevents CLI tools, Git hooks, package managers, and automated CI/CD scripts on Windows from communicating securely with remote REST APIs, internal development servers, or repository mirrors.

Windows Client curl.exe (Schannel API) Windows Root CA Store Missing Root/Inter. CA 1. TLS Handshake Request 2. Returns Server Certificate Schannel Validation Failed SEC_E_UNTRUSTED_ROOT Remote HTTPS Server api.example.com Custom / Enterprise SSL Self-Signed or Incomplete

Why It Happens

On Microsoft Windows, standard builds of curl use Schannel (the native Windows SSPI provider for TLS/SSL) rather than OpenSSL or LibreSSL. When curl initiates an outbound HTTPS connection, Schannel intercepts the server certificate and validates its hierarchy against the local system certificate store.

The SEC_E_UNTRUSTED_ROOT (hex code 0x80090325) error specifically indicates that the cryptographic validation chain broke at the top level or at an intermediate level. Common root causes include:

  • Self-Signed Certificates or Private CAs: The target server uses an internal corporate Certificate Authority or a self-signed certificate that has not been imported into the Windows Trusted Root Certification Authorities store.
  • Incomplete Intermediate Certificate Chains: The web server sends its leaf certificate but fails to send the required intermediate CA certificates during the TLS handshake, and Windows is unable to dynamically download the missing intermediate bundle.
  • Outdated Windows Root Certificates: The Windows Automatic Root Certificates Update feature (via Windows Update) is disabled, blocked by corporate group policy, or out of sync on an isolated enterprise host.
  • Corporate SSL Inspection Firewalls: Deep Packet Inspection (DPI) proxies (such as Zscaler, Fortinet, or Palo Alto Networks) intercept HTTPS traffic and re-sign connections using a custom local firewall CA that is not installed on the local developer machine.

The Fix

Choose one of the three verified solutions below depending on whether you need system-wide trust, an enterprise CA installation, or a command-line override for curl.

Solution 1: Import the Root/Intermediate CA into the Windows Certificate Store

If you are interacting with a internal API, a self-signed dev environment, or a corporate proxy, import the issuer certificate into the Windows ROOT store using PowerShell as Administrator.

1. Download or locate the .crt or .pem file for your Certificate Authority.

2. Open PowerShell with Administrative privileges and execute the Import-Certificate cmdlet:

Import-Certificate -FilePath "C:\certs\corporate-root-ca.crt" -CertStoreLocation "Cert:\LocalMachine\Root"

3. Verify that the certificate is installed in the Trusted Root store:

Get-ChildItem -Path "Cert:\LocalMachine\Root" | Where-Object { $_.Subject -like "*YourCAName*" }

Once imported, Schannel will automatically recognize the trust anchor, and standard curl https://api.example.com commands will succeed immediately without error curl: (35) schannel: SEC_E_UNTRUSTED_ROOT.

Solution 2: Use an External CA Bundle with curl

If you prefer not to alter system-wide root certificates across Windows, instruct curl to utilize a dedicated Mozilla CA bundle (such as the standard cacert.pem provided by cURL) via the --cacert flag.

1. Download the latest official CA certificate bundle from cURL's website:

curl.exe --insecure -o C:\certs\cacert.pem https://curl.se/ca/cacert.pem

2. Pass the custom bundle directly into your curl execution:

curl --cacert "C:\certs\cacert.pem" https://api.example.com

3. To enforce this permanently without passing --cacert in every terminal session, create or edit your _curlrc configuration file in your user home directory (%USERPROFILE%\_curlrc):

cacert = "C:/certs/cacert.pem"

Solution 3: Switch curl to Use OpenSSL via Git Bash / Chocolatey

Windows native curl.exe (located in C:\Windows\System32\curl.exe) is compiled against Schannel. However, the OpenSSL-backed version of curl handles custom CA bundles and Linux-style certificate paths more predictably in dev workflows.

If you have Git for Windows installed, run the OpenSSL build of curl directly from Git Bash or explicitly execute its binary in PowerShell:

& "C:\Program Files\Git\usr\bin\curl.exe" https://api.example.com

To verify which SSL backend your curl binary is currently running, execute:

curl -V

If Schannel is active, the output displays Schannel in the features list:

curl 8.4.0 (x86_64-w64-mingw32) libcurl/8.4.0 Schannel zlib/1.3
Protocols: dict file ftp ftps gopher gophers http https imap imaps...
Features: AsynchDNS HTTPS-proxy IPv6 Largefile NTLM SSL SSPI...

If you install curl via Chocolatey or Scoop, you can install the OpenSSL version to replace Schannel behavior:

choco install curl --params "/OpenSSL"

Still Not Working?

If you continue to encounter curl: (35) schannel: SEC_E_UNTRUSTED_ROOT (0x80090325) after applying the fixes above, check for these two uncommon scenarios:

1. Stale Windows CRL / OCSP Revocation Checking

Schannel strictly checks Certificate Revocation Lists (CRL) and OCSP endpoints. If your network blocks outbound access to the CA's revocation endpoint, Schannel rejects the certificate chain by default. Test whether revocation checking is causing the failure by running:

curl --ssl-no-revoke https://api.example.com

If --ssl-no-revoke allows the connection to succeed, your firewall is blocking OCSP/CRL traffic or the server certificate lacks accessible revocation HTTP endpoints.

2. Windows Automatic Root Update Disabled

In locked-down enterprise environments, Windows might fail to dynamically download public root certificates from Microsoft's Update servers. Check whether Crypt32 event logging reports missing root trust anchors or manually force a root update via PowerShell:

Certutil -generateSSTFromWU C:\certs\roots.sst
Import-Certificate -FilePath C:\certs\roots.sst -CertStoreLocation Cert:\LocalMachine\Root

As a last resort for temporary local testing only, you can bypass TLS verification using curl -k or curl --insecure, but never hardcode --insecure into production deployment scripts or build pipelines.