Self-host SMTP TLS to Microsoft 365 broken by DigiCert G1 root distrust (Alpine CA bundle); bwdata/ca-certificates silently ignores DER certs
Steps To Reproduce
- Self-host Bitwarden 2026.7.0 (standard
bitwarden.shDocker deployment) with SMTP configured against Microsoft 365 / Exchange Online Protection (<tenant>.mail.protection.outlook.com, port 25,startTls=true). - Attempt any action that sends mail (e.g. admin portal passwordless login).
Expected Result
Email is sent; admin portal login link arrives.
Actual Result
MailKit.Security.SslHandshakeException on every send:
The server's SSL certificate could not be validated for the following reasons:
• The server certificate has the following errors:
• unable to get certificate CRL
• An intermediate certificate has the following errors:
• unable to get certificate CRL
• The root certificate has the following errors:
• self-signed certificate in certificate chainAdditionally, on the admin portal the failed login POST redirects to /login (missing the /admin path base) which returns 404, masking the underlying SMTP error entirely (see "Related cosmetic bug" below).
Root cause
Not a Bitwarden code bug, but a trust-store regression in the shipped images plus two hardening gaps that made it very hard to diagnose:
- On 15 April 2026 Mozilla and Chrome removed the DigiCert Global Root CA (G1) from their trust stores (industry-wide G1 distrust). Alpine inherits Mozilla's bundle, so the Alpine-based images introduced in newer releases ship a CA bundle without this root (verified: 119 roots in the bundle, G1 absent).
- Microsoft's EOP MX endpoints (
*.mail.protection.outlook.com) still serve a chain anchored to that distrusted G1 root (leaf →DigiCert SHA2 Secure Server CA→DigiCert Global Root CA). See the mozilla dev-security-policy thread "Microsoft MX servers use certificate chaining to obsolete DigiCert Global Root CA" and Microsoft advisory MC1282565. - Result: every Alpine-image self-host instance relaying via M365 fails outbound SMTP TLS validation with
UntrustedRoot. Reproduced outside Bitwarden entirely: a minimal .NET 10SslStreamclient onmcr.microsoft.com/dotnet/sdk:10.0-alpinefails identically against the same relay; adding the G1 root to the trust store makes the same handshake succeed withSslPolicyErrors.None(revocation Online).
Workaround (works, documented here for other affected users)
Place the DigiCert Global Root CA in PEM form into bwdata/ca-certificates and restart:
curl -s http://cacerts.digicert.com/DigiCertGlobalRootCA.crt \
| openssl x509 -inform DER -out ./bwdata/ca-certificates/digicert-global-root-ca.crt
./bitwarden.sh restartNote the DER→PEM conversion is required — see gap 2 below.
Suggested actions for the Bitwarden team
- Release notes / awareness: when a base-image bump changes the shipped CA bundle (especially removals such as the April 2026 G1 distrust), call it out in release notes. This change silently broke outbound SMTP for M365 relays with no configuration change on the user's side.
bwdata/ca-certificatessilently ignores non-PEM files. The .NET certificate-directory loader (OpenSslCachedSystemStoreProvider) reads PEM only. DigiCert (and other CAs) serve roots from their AIA URLs in DER, so the obviouscurlof the cert into that directory produces a file that is silently skipped — no log line, no error. The previous root-entrypoint flow (cp ... && update-ca-certificates) tolerated this; the current non-root entrypoint performs no processing at all. Suggestion: at container start, attempt to parse each file in/etc/bitwarden/ca-certificatesand log a clear warning for any file that yields no certificates (or auto-convert DER). This would have reduced a multi-hour diagnosis to a one-line log read.- Docs: the Certificate Options page only covers inbound NGINX TLS. The outbound-SMTP trust mechanism (
bwdata/ca-certificates, PEM requirement, which containers consume it) is undocumented. - Related cosmetic bug: when
LoginController.Indexthrows (any unhandled mail exception), the admin portal ends up redirecting to/loginwithout the/adminpath base, producing a 404 that hides the real error. The 404 sent this investigation down a reverse-proxy rabbit hole before the SMTP exception was found in container logs.
Environment
- Self-hosted 2026.7.0 (standard Docker /
bitwarden.shdeployment) - SMTP:
<tenant>.mail.protection.outlook.com:25,startTls=true(M365/EOP direct-send) - Reproduced independently on
mcr.microsoft.com/dotnet/sdk:10.0-alpinewith a minimalSslStreamclient
References
- Mozilla dev-security-policy: Microsoft MX servers use certificate chaining to obsolete DigiCert Global Root CA
- Microsoft 365 advisory MC1282565 (April 2026 industry-wide DigiCert Global Root CA (G1) distrust)
- Parallel downstream report: home-assistant/operating-system#4775
- DigiCert: G1 root removal advisory
Troubleshooting and issue write-up assisted by Claude Code (Anthropic). Root cause isolated by reproducing the failure on a clean mcr.microsoft.com/dotnet/sdk:10.0-alpine image with a minimal SslStream client, then bisecting trust-store configurations against the live EOP relay.
Source: bitwarden/server