TLS certificates

Certificate files, self-signed certificates for testing, automatic certificates over ACME, and changing the key a DANE record pins.

Nixt Server serves nothing in the clear. SMTP, submission, IMAP, POP3, ManageSieve and every HTTPS service on the node use one certificate, set in the [tls] table of the configuration file. The only exception is the plain HTTP listener that answers ACME challenges on port 80, which serves nothing else.

The listeners accept TLS 1.2 and TLS 1.3. Every HTTPS answer carries Strict-Transport-Security: max-age=31536000.

Which names the certificate needs

NameWhy
The server’s hostname, for example mail.example.comEvery mail app and every sending server connects to this name. Always required.
mta-sts.<domain> for each domain with an MTA-STS policySending servers fetch the policy from https://mta-sts.example.com/.well-known/mta-sts.txt and check the certificate.
autoconfig.<domain>Only if you publish this name for Thunderbird-style setup.
autodiscover.<domain>Only if you publish this name for Outlook-style setup.

There is one certificate per node, so every name must be in that one certificate.

Choosing how to get a certificate

MethodUse it whenConfiguration
Certificate filesYou already have certificates from an authority or from your organisation’s own PKI.kind = "files"
Self-signedYou are testing and nobody else needs to trust the server yet.init --self-signed, then kind = "files"
ACMEThe server is reachable on port 80 from the internet and you want certificates obtained and renewed for you.kind = "acme"

Certificate files

This is what init writes:

[tls]
kind = "files"
certificate = "/var/lib/versealx-server/tls/certificate.pem"
key = "/var/lib/versealx-server/tls/key.pem"
KeyWhat to put there
certificateThe certificate chain in PEM form: your certificate first, then any intermediate certificates.
keyThe private key in PEM form.

Both files are read once, when the node starts. The service runs as the versealx user, so that user must be able to read them. Keep the key file private to that user (mode 0600).

The node refuses to start when either file is missing or will not load, and the error begins with tls: followed by the file’s path.

Replacing certificate files

  1. Copy the new certificate chain and key over the old files.
  2. Restart the service: sudo systemctl restart versealx-server.
  3. Run doctor to confirm both files are found.

Self-signed certificates for testing

versealx-server init --self-signed writes a certificate for the host name, signed only by itself, valid for one year from the moment you run it, with its key beside it. init prints the certificate’s SHA-256 fingerprint:

A self-signed certificate is at /var/lib/versealx-server/tls/certificate.pem and its key at /var/lib/versealx-server/tls/key.pem.
Nobody but you will trust it: put a certificate from an authority in its
place, or switch [tls] to kind = "acme", before anyone else connects.
Until then, anyone adding an account in Nixt Server is shown its SHA-256
fingerprint and asked whether to trust it. Give them this, so they can
tell the certificate is yours and not one put in its way:
  <fingerprint, 32 pairs of hexadecimal digits separated by colons>
It is not secret, and can be read again at any time with:
  openssl x509 -in /var/lib/versealx-server/tls/certificate.pem -noout -fingerprint -sha256

Mail apps refuse a self-signed certificate unless the person using them agrees to trust it. Nixt Mail shows the fingerprint and asks; the person compares it with the one you gave them and chooses Trust and connect. Other servers delivering mail to you usually accept any certificate on port 25, but a sender that enforces MTA-STS or DANE for your domain will not.

Automatic certificates with ACME

With ACME, the node obtains a certificate from a certificate authority, keeps it in the store, serves it, and renews it without a restart.

[tls]
kind = "acme"
names = ["mail.example.com", "mta-sts.example.com", "autoconfig.example.com"]
contact = "postmaster@example.com"
KeyTypeDefaultMeaning
nameslist of host namesRequiredEvery name the certificate covers. Must not be empty; each must be a valid host name.
contactstringRequiredThe contact address for the account at the certificate authority.
directoryURLLet’s Encrypt production, https://acme-v02.api.letsencrypt.org/directoryThe ACME directory of another certificate authority, such as a private one.
trustpath to a PEM fileNoneA certificate to trust, in addition to the public roots, when talking to a certificate authority whose root is not public. Verification is never turned off, only widened.

What ACME needs

  • The serve role. The certificate authority proves each name by fetching a challenge from port 80, and the serve role answers it. A node with kind = "acme" and no serve role refuses to start before binding any port.
  • Port 80 reachable from the internet on every name in names. The challenge listener binds 0.0.0.0:80 unless you set its address with [listeners.acme]. It answers /.well-known/acme-challenge/ and nothing else.
  • Every name resolving to this node, or to a node that shares its store.

The node uses the HTTP-01 challenge.

What happens when the node starts

  1. The TLS listeners start at once, but refuse every handshake until there is a certificate. Nothing is served without TLS in the meantime.
  2. If the store already holds a certificate, the node serves it straight away.
  3. If there is no certificate, or the one it has is due, the node orders one: it registers an account, answers the HTTP-01 challenge for each name, and fetches the certificate.
  4. The certificate and its key are kept in the store and served without restarting any listener.

Renewal

  • A certificate is renewed 30 days before it expires.
  • A renewal keeps the certificate’s existing key, so a DANE record that pins the key keeps matching.
  • If an order fails, the node tries again after 5 seconds, then 10, doubling each time up to once an hour, and logs could not obtain a certificate; will try again with the reason.
  • doctor reports the days left. Fewer than 30 days left means renewals are failing: look in the log for the reason.
doctor saysWhat it means
ok tls: ACME for mail.example.com, …: 62 day(s) leftA certificate is in use.
warn tls: ACME for …: no certificate has been obtainedNo order has succeeded. Check that every name resolves to the node and that port 80 is reachable from outside.
warn tls: ACME for …: 12 day(s) left, which is past the day renewal was dueRenewals are failing.
BAD tls: ACME for …: the certificate in the store has expiredEvery client refuses the certificate; nobody can sign in.

ACME with several nodes

Nodes that share a store share the certificate and the challenge answers:

  • The node the certificate authority reaches need not be the node that placed the order. A node answers challenges for its siblings when its serve role runs with ACME, or when it has an [listeners.acme] table even if its own certificate comes from files.
  • Challenge answers expire after an hour.
  • A node picks up a certificate another node renewed at its next check, which happens at least once a day, and every hour while a DANE key rollover is under way.

DANE and your certificate’s key

A DANE record for your mail host (_25._tcp.mail.example.com. IN TLSA 3 1 1 …) tells sending servers exactly which key your certificate must carry. A sender that validates DANE and finds a record that does not match your certificate refuses to deliver rather than falling back to ordinary TLS. That makes the record strong and unforgiving: change the key without updating DNS first, and mail from those senders stops.

versealx-server dns prints the TLSA record for the certificate the node serves now. Publish it only on a DNSSEC-signed zone. doctor compares any published record with the certificate in use; see DNS records.

Changing a DANE key with ACME

ACME renewals keep the key, so you only need to change it on purpose — for example, on a schedule. The dane command does it in the safe order: publish the new key’s record next to the old one, wait long enough for resolvers to see both, switch the certificate to the new key, then withdraw the old record.

  1. Start the rollover:

    sudo -u versealx versealx-server dane roll --config /etc/versealx-server/versealx-server.toml

    It keeps a new key in the store and prints two records on the same name:

    A key rollover is under way (RFC 7671 §8.1). Publish the second record beside the
    first, on the same name:
    
      _25._tcp.mail.example.com. IN TLSA 3 1 1 <current key hash>   ; the key served now
      _25._tcp.mail.example.com. IN TLSA 3 1 1 <next key hash>   ; the next key
  2. Publish the second record beside the first. Do not remove the first.

  3. Wait. The running node looks for the new record every hour. Once it has seen it published for the hold time — 48 hours unless you gave --hold — it obtains its next certificate on the new key, whether or not a renewal was due.

  4. Check progress at any time:

    sudo -u versealx versealx-server dane status --config /etc/versealx-server/versealx-server.toml
    Status saysMeaning
    Begun <time>. The node has not yet seen the next key's record published …Publish the second record. Nothing happens until the node sees it.
    The next key's record was first seen <time>, so the switch is due at <time>.Waiting out the hold.
    The switch has been due since <time> …The node moves to the new key at its next look, within the hour.
    No key rollover is under way.Nothing in progress.
  5. After the switch, withdraw the old record. doctor warns while a record for a key the node no longer holds is still published.

The hold stands in for twice the record’s TTL. If your TLSA record’s TTL is longer than 24 hours, run dane cancel and start again with --hold set to twice the TTL in hours. --hold takes a whole number from 1 to 720.

If no TLSA record pinning the host’s key is published at all, there is nothing to wait for and the node switches at its next look.

To abandon a rollover, run dane cancel. The next key is deleted; if you published its record, withdraw it, because it pins a key nothing holds.

dane roll refuses when a rollover is already under way, and when the node has no certificate yet — the first certificate is obtained on a new key anyway.

Changing a DANE key with certificate files

dane roll works only with ACME. With kind = "files" it prints the manual procedure instead, which is the same order:

  1. Get the new certificate and work out the TLSA record for its key.
  2. Publish that record beside the record versealx-server dns mail.example.com prints.
  3. Wait at least twice the record’s TTL.
  4. Replace the certificate files and restart the service.
  5. Withdraw the old record.

Something unclear or out of date on this page? Tell us.