Skip to content

Certificates

A TLS certificate lets clients verify that they have reached the right server before they send credentials or mail over the connection. Stalwart keeps every certificate it uses as a Certificate object, whether the certificate was issued through ACME or installed by an administrator. The objects are found in the WebUI under Settings › TLS › Certificates.

Certificate objects are stored in the data store with the rest of the configuration. A cluster therefore shares them, and backups of the data store include them.

This page describes how to install a certificate by hand, how the server chooses a certificate for each connection, and what happens when certificates are renewed or expire.

Certificates obtained through ACME need no manual installation. Installing a certificate by hand is the only way to use an RSA certificate, a certificate from an authority without ACME support, or one issued by an internal authority. ACME certificates use ECDSA P-256 keys.

A Certificate has two fields to fill in:

  • certificate holds the certificate chain in PEM format: the server certificate first, followed by the intermediate certificates.
  • privateKey holds the private key in PEM format. The key must be unencrypted. PKCS#8 (BEGIN PRIVATE KEY), PKCS#1 RSA (BEGIN RSA PRIVATE KEY) and SEC1 EC (BEGIN EC PRIVATE KEY) keys are accepted.

A key protected with a passphrase is not rejected at once. It is accepted when the object is saved, but it cannot be used: when the certificates are loaded, the certificate is reported with Invalid certificate: No private keys found.

Both fields take their value in one of three forms: inline (Text), from an environment variable (EnvironmentVariable) or from a file (File). The file form suits certificates that another tool maintains on disk. The server reads the file when it loads the certificates, so a file replaced on disk takes effect only after a reload, as described below. In a cluster, the file must exist on every node.

The remaining fields, subjectAlternativeNames, notValidBefore, notValidAfter and issuer, are not filled in. The server reads them from the certificate.

The following Certificate reads both values from files maintained by another tool:

{
"certificate": {"@type": "File", "filePath": "/etc/stalwart/tls/fullchain.pem"},
"privateKey": {"@type": "File", "filePath": "/etc/stalwart/tls/privkey.pem"}
}

Errors are reported at two moments. A certificate that cannot be parsed is refused when it is saved, with Failed to read certificate: <reason>. A certificate whose key cannot be used is saved, but reported as a configuration error on its object when the certificates are loaded, for example Invalid certificate: Failed to read private key PEM: <reason>. Such a certificate is not used.

A server that hosts several domains holds several certificates, and must present the right one on each connection. The client names the host it wants through Server Name Indication (SNI), a field of the TLS handshake. From that name, the server selects one certificate, trying the following in order:

  1. A certificate whose subject alternative names contain the requested name exactly.
  2. Otherwise, a wildcard certificate for the parent domain: *.example.com covers mail.example.com.
  3. Otherwise, or when the client sends no name, the default certificate. That is the certificate selected by SystemSettings.defaultCertificateId. When that field is not set, it is the certificate that covers the server hostname, defaultHostname, exactly or through a wildcard for its parent domain.
  4. When there is no default certificate, the choice depends on what is loaded. If the loaded certificates cover exactly one name, the certificate for that name is used. If they cover several names, an arbitrary one is used and a tls.multiple-certificates-available warning is logged. If no certificate is loaded, the server presents a self-signed certificate for localhost.

A request for a name without a matching certificate also logs a tls.certificate-not-found event. When several certificates cover the same name, the one with the latest expiry date is used for that name.

In most deployments the default needs no configuration. Because the certificate for the server hostname becomes the default when defaultCertificateId is not set, an ACME certificate that covers the hostname is presented to clients that send no name, and to clients that request an unknown name. Set defaultCertificateId only when another certificate must take that role, or when no certificate covers the hostname.

Clients reject the self-signed certificate, and every certificate that does not cover the name they connected to. A server reached under several names therefore needs certificates that cover all of them. TLS certificates lists the names an ACME certificate covers.

When ACME renews a certificate, it creates a new Certificate object instead of changing the old one. If the certificate it replaces was the default certificate and covers the same names, the default moves to the new object. The old object is kept until it expires.

The server checks validity dates only when it loads the certificates. That happens at startup, after each ACME renewal, and at a ReloadTlsCertificates action. Expired certificates are deleted at that point, and each deletion logs a tls.expired-certificate-removed event.

There is no periodic check, which has three consequences for manual certificates:

  • A certificate that expires while it is loaded is still presented until the next load. Replace a manual certificate before it expires.
  • A certificate whose notValidBefore lies in the future is skipped, and becomes usable only at the first load after that date. Reload the certificates after the start date of a certificate installed in advance.
  • A file-backed certificate whose object was deleted because it expired must be created again. Replacing the file is not enough.

Certificates renewed by ACME are loaded automatically. Every other change takes effect only when the certificates are reloaded, or at the next restart. This covers a Certificate object created, changed or deleted through the WebUI, the CLI or the API, and a certificate file replaced on disk.

The certificates are reloaded with the ReloadTlsCertificates action, from the WebUI or with the CLI:

Terminal window
stalwart-cli create Action/ReloadTlsCertificates

A reload does not interrupt existing connections, and new connections use the reloaded certificates. In a cluster, changes and the reload reach the other nodes only when a coordinator is configured.