-
What happened?I access stalwart over HTTPS and IMAP using an internal domain (mail.intra.domain). For this purpose I've configured a wildcard certificate (*.intra.domain) in Stalwart referencing a file ( For external SMTP TLS I have set up an LetsEncrypt ACME provider with a subject name matching the external hostname of the server (x.domain). This hostname is also what is set under Network settings. This setup has worked well and the right certificates are presented on both access paths. Today, the internal wildcard cert (*.intra.domain) had expired giving warnings in my client. I assume this is to be expected, i.e. Stalwart does not automatically reload a What wasn't expected though, was that when I did a TLS check (using checktls.com) on the external SMTP the internal wildcard cert (*.intra.domain) was presented instead of the external cert (x.domain). The check specifically complained that After restarting Stalwart, I am now seeing the right cert presented externally again. (The restart also fixed the expired cert, and I'm not sure if that has influenced this.) Since everything is back to working again, I am unfortunately unable to troubleshoot the issue further. It seems like a bug that External SMTP connections do have this warning logged with every connection (both before and after restart)
How can we reproduce the problem?Unable to reproduce after restart. Versionv0.13.x What database are you using?RocksDB What blob storage are you using?RocksDB Where is your directory located?Internal What operating system are you using?Docker Relevant log output# Connection from check tls before restart - internal wildcard cert presented
2025-10-23T00:28:09Z INFO SMTP EHLO command (smtp.ehlo) listenerId = "smtp", localPort = 25, remoteIp = xxx, remotePort = 42806, domain = "xxx.checktls.co
m"
2025-10-23T00:28:09Z INFO SPF EHLO check failed (smtp.spf-ehlo-fail) listenerId = "smtp", localPort = 25, remoteIp = xxx, remotePort = 42806, domain = "xxx.checktls.com", result = No SPF record (spf.none), elapsed = 89ms
2025-10-23T00:28:09Z WARN Multiple TLS certificates available (tls.multiple-certificates-available) total = 2
2025-10-23T00:28:09Z INFO TLS handshake (tls.handshake) listenerId = "smtp", localPort = 25, remoteIp = xxx, remotePort = 42806, listenerId = "smtp", version = "T
LSv1_3", details = "TLS13_AES_256_GCM_SHA384"
# Restart stalwart
2025-10-23T00:32:28Z INFO Network listener started (network.listen-start) listenerId = "http", localIp = ::, localPort = 8080, tls = false
2025-10-23T00:32:28Z INFO Network listener started (network.listen-start) listenerId = "https", localIp = ::, localPort = 443, tls = true
2025-10-23T00:32:28Z INFO Network listener started (network.listen-start) listenerId = "imap", localIp = ::, localPort = 143, tls = false
2025-10-23T00:32:28Z INFO Network listener started (network.listen-start) listenerId = "imaptls", localIp = ::, localPort = 993, tls = true
2025-10-23T00:32:28Z INFO Network listener started (network.listen-start) listenerId = "pop3", localIp = ::, localPort = 110, tls = false
2025-10-23T00:32:28Z INFO Network listener started (network.listen-start) listenerId = "pop3s", localIp = ::, localPort = 995, tls = true
2025-10-23T00:32:28Z INFO Network listener started (network.listen-start) listenerId = "sieve", localIp = ::, localPort = 4190, tls = false
2025-10-23T00:32:28Z INFO Network listener started (network.listen-start) listenerId = "smtp", localIp = ::, localPort = 25, tls = false
2025-10-23T00:32:28Z INFO Network listener started (network.listen-start) listenerId = "submission", localIp = ::, localPort = 587, tls = false
2025-10-23T00:32:28Z INFO Network listener started (network.listen-start) listenerId = "submissions", localIp = ::, localPort = 465, tls = true
2025-10-23T00:32:28Z INFO Processing ACME certificate (acme.process-cert) id = "letsencrypt-xxx-http", hostname = ["x.domain"], validFrom = 2025-09-19T09:01:28Z, validTo = 2025-12-18T09:01:27Z, due = 2025-11-18T09:01:27Z
# Connection from check tls after restart - x.domain cert presented
2025-10-23T00:32:51Z INFO SMTP EHLO command (smtp.ehlo) listenerId = "smtp", localPort = 25, remoteIp = xxx, remotePort = 44242, domain = "xxx.checktls.com"
2025-10-23T00:32:51Z INFO SPF EHLO check failed (smtp.spf-ehlo-fail) listenerId = "smtp", localPort = 25, remoteIp = xxx, remotePort = 44242, domain = "xxx.checktls.com", result = No SPF record (spf.none), elapsed = 2ms
2025-10-23T00:32:51Z WARN Multiple TLS certificates available (tls.multiple-certificates-available) total = 2
2025-10-23T00:32:51Z INFO TLS handshake (tls.handshake) listenerId = "smtp", localPort = 25, remoteIp = xxx, remotePort = 44242, listenerId = "smtp", version = "TLSv1_3", details = "TLS13_AES_256_GCM_SHA384"Code of Conduct
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
|
Two things to check:
Macros are not evaluated for settings stored in the database. Make sure macros appear only in local settings defined in the TOML.
This means that the client is not providing a hostname during the TLS handshake and, since you have multiple certificates, Stalwart does not know which one to choose. |
Beta Was this translation helpful? Give feedback.
Got it!
I guess what I need to do to make sure the certificate selection is deterministic in this case is to set default for my acme provider:
I had missed this option before.
Thanks for your help (and for Stalwart!)