TLS certificate with wildcard not sent on SMTP and IMAP connections

Your question

I have a TLS certificate with let’s encrypt (which is handled by certbot), and stalwart just takes the certificate from the /etc/letsencrypt/live/ folder.

On HTTPS it is fine (although I don’t really know here because stalwart is behind a reverse proxy ), but SMTP/IMAP clients receive a self-signed certificate.

The SANs of the certificate are just domain and *.domain, while stalwart’s hostname is mail.domain. Is it possible that stalwart doesn’t serve the certificate to clients connecting to mail.domain because mail.domain is not explicitly present in the certificate’s SANs?

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

Your diagnosis is likely correct. A wildcard certificate covering *.domain does match mail.domain — that is standard wildcard semantics — so the certificate itself should be valid for that hostname. However, the more common cause of the symptom you’re seeing (SMTP/IMAP clients receiving a self-signed certificate instead of your Let’s Encrypt one) is that Stalwart has not been configured to use your certbot-managed certificate for those listeners, and is instead falling back to its built-in self-signed certificate.

A few things to check:

  1. Certificate path configuration – Make sure the TLS certificate and private key paths in your Stalwart configuration point to the Let’s Encrypt files (e.g. /etc/letsencrypt/live/<domain>/fullchain.pem and privkey.pem) and are set for all listeners (SMTP on port 25/465/587, IMAP on port 143/993), not just the HTTPS listener.

  2. File permissions – Certbot typically restricts /etc/letsencrypt/live/ to root only. If Stalwart runs as a non-root user it may silently fail to read the certificate and fall back to the self-signed one. Run Stalwart with a user that has read access to those files (or copy/symlink them somewhere accessible and use a certbot deploy hook to refresh them).

  3. Reload after renewal – Stalwart needs to be told to reload its TLS certificates after certbot renews them. You can trigger this via the management API (POST /api/reload) in a certbot deploy hook, or configure automatic reloading in Stalwart’s settings.

  4. SNI matching – Stalwart selects which certificate to serve based on the SNI hostname the client sends. If a client connects without SNI (older clients sometimes do this), Stalwart will serve whichever certificate is marked as the default. Confirm the correct certificate is set as the default in the TLS configuration.

Unfortunately the specific configuration screens for this aren’t fully surfaced in the docs I was able to retrieve right now, so a human community member or staff member should be able to give you the exact field names to check in the WebUI.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong. If you found it useful, mention @helpbot in any reply to ask follow-up questions and the bot will respond again.

Wildcard SANs are supported, but they have to be reflected in the certificate’s stored metadata in Stalwart, not just in the cert file: the SMTP/IMAP listeners pick the certificate by SNI hostname, and the lookup uses the SANs that Stalwart parsed from the leaf at import time. If you uploaded the cert manually, please confirm the SAN list in the UI includes both domain and *.domain; if it doesn’t, that’s where the lookup is missing.

Two things that commonly trip this up:

  1. SNI on the client. SMTP clients on port 25 in particular often don’t set SNI; in that case Stalwart serves the listener’s default certificate, not whatever matches. If you have a default-cert set to a different name, it’ll be served instead of the wildcard.

  2. HTTPS working via your reverse proxy doesn’t tell us anything about Stalwart’s TLS state, since the proxy terminates TLS. So that’s not the comparison we want.

Posting the certificate’s SAN list (openssl x509 -in <pem> -noout -text | grep -A1 "Subject Alternative") and your default-certificate setting under Settings → TLS would let me pin this down.

Thanks for the response

Both the WebUI and the openssl command give *.domain.net, domain.net in the SAN list of the certificate. Also the default certificate is the one with the wildcard.

Actually I know that it is not a problem of the SMTP/IMAP client because also the HTTPS listener proposes a self-signed certificate behind the reverse proxy. Could that be unintended behaviour?

Is your reverse proxy forwarding the TLS client helo? If not Stalwart won’t know which certificate to choose.

Sorry let me clarify the setting:

  • Stalwart is behind the reverse proxy (nginx) only for the https listener (443 forwards to 10443 held by stalwart with SNI mail.domain). Stalwart’s ports for the other listeners (imap, smtp, submissions) are directly exposed. I need this setup only because I need to serve other websites on other subdomains.
  • I currently have two TLS certificates managed with certbot: the old one (which I want to get rid of) has mail.domain, domain, and other (a lot…) SANs specified explicitly, while the new one only has *.domain and domain.
  • Stalwart’s hostname is mail.domain, and I’ve set the new certificate as the default certificate.

Stalwart works (and always worked) with the old certificate. What I notice is, when only the new certificate is added to the TLS section in the WebUI, Stalwart serves a self-signed certificate on all the listeners. In this case only the HTTPS 443 port gives the new certificate, but just because nginx is proposing it (stalwart’s 10443 port still proposes a self-signed certificate).

Update: the blame is on me! I gave permissions to the /etc/letsencrypt/live files but not the /etc/letsencrypt/archive ones! Certbot removes the read privileges of the private key file from the group.

Now it works