bg-tutorials

How to Install an ACME SSL Certificate on Mail Servers

A mail server needs a publicly trusted certificate for the hostname clients connect to, and it usually needs it in two or three places at once: Postfix for SMTP, Dovecot for IMAP and POP3, sometimes Exim instead of Postfix. This guide issues one certificate with acme.sh from a commercial certificate authority using External Account Binding, then wires it into each service so renewals apply themselves.

Under CA/Browser Forum ballot SC-081v3, publicly trusted TLS certificates have been capped at 200 days since March 15, 2026, dropping to 100 days on March 15, 2027 and to 47 days on March 15, 2029. Mail is where a missed renewal is least visible and most damaging, because clients fail quietly in the background rather than showing anyone a browser warning.

Before you start

  • A mail hostname such as mail.example.com, with an A or AAAA record, and your MX pointing at it. The certificate must carry the name clients actually enter in their mail settings, not the domain in the email address.
  • Shell access with sudo or root on the mail server.
  • Your CA’s ACME directory URL, EAB Key ID and EAB HMAC Key.
  • An API credential for your DNS provider. This guide validates over DNS, and doing it through the provider’s API is what makes renewals unattended.
  • Your mail stack: Postfix or Exim for SMTP, Dovecot for IMAP and POP3. Paths and service names vary by distribution, so check them as you go.

Step 1: Install acme.sh on the mail server

curl https://get.acme.sh | sh -s [email protected]
source ~/.bashrc
acme.sh --version

Running the client on the mail server itself keeps things simple: the renewed files land where the services already read them, with no copying between machines. The installer also adds a daily cron entry, which is what drives everything after Step 6. If the source line has no effect, log out and back in.

Step 2: Register the ACME account with EAB

acme.sh --register-account \
  --server https://acme.yourca.example/v2/DV \
  --eab-kid "YOUR_EAB_KEY_ID" \
  --eab-hmac-key "YOUR_EAB_HMAC_KEY" \
  --accountemail "[email protected]"

Once per certificate authority. Paste the HMAC key exactly as the CA gave it, since it is already base64url encoded and re-encoding it produces a failure that looks like wrong credentials. Pass --server on every later command too, because acme.sh has defaulted to ZeroSSL since version 3.0.

Step 3: Issue the certificate over DNS

DNS-01 is the right challenge for mail. A mail server frequently runs no public website, so there is nothing to serve an HTTP challenge from, and DNS validation does not care whether port 80 is open or where the host sits.

Give acme.sh an API credential for your DNS provider so it publishes and removes the record itself. The example uses Cloudflare; acme.sh ships integrations for most providers, each with its own variables.

export CF_Token="your_cloudflare_api_token"
acme.sh --issue --dns dns_cf \
  -d mail.example.com \
  --server https://acme.yourca.example/v2/DV

Do not use manual DNS mode for a mail server. acme.sh will not renew a certificate validated by hand, and says so in its own error text: manual mode cannot renew automatically and the certificate has to be issued again by a person. On mail that is the worst possible failure mode, because nothing visibly breaks until clients start refusing to connect. If your DNS provider genuinely has no API, point _acme-challenge.mail.example.com at a zone that does with a CNAME and use acme.sh’s alias mode through --challenge-alias.

Issue for the hostname clients connect to. If users reach the server as both imap.example.com and smtp.example.com, add each with its own -d so all of them are on the certificate.

Step 4: Put the files where the services can read them

acme.sh keeps working copies in its own directory and its documentation asks you not to point other software at those paths, because the layout is internal and can change. Install to a stable location instead, and let acme.sh maintain it:

sudo mkdir -p /etc/ssl/mail

acme.sh --install-cert -d mail.example.com \
  --key-file       /etc/ssl/mail/mail.key \
  --fullchain-file /etc/ssl/mail/mail-fullchain.pem \
  --reloadcmd      "systemctl reload postfix dovecot"

This is the step that makes renewals work, so it is worth understanding rather than copying. acme.sh records these destinations and the reload command against the certificate, and repeats all of it after every renewal. Pointing the install paths back at acme.sh’s own directory, as some guides do, achieves nothing at all: acme.sh compares source and destination and skips the copy when they are the same file.

Then lock down the private key. Both Postfix and Dovecot read their key while still running as root, so restricting it to root is correct and does not break them. Dovecot’s documentation puts it plainly: the certificate file can be world readable because it contains nothing sensitive, while the key file’s permissions should be restricted to root only.

sudo chown root:root /etc/ssl/mail/mail.key
sudo chmod 600 /etc/ssl/mail/mail.key

Step 5: Configure Postfix

Postfix has two ways to be told about a certificate, and the modern one is preferable. From Postfix 3.4 onward, Postfix’s own documentation calls smtpd_tls_chain_files the preferred way to configure server keys and certificates, and describes it as obsoleting the older per-algorithm settings.

That parameter takes a single PEM file holding the private key first, immediately followed by the certificate and its chain. Build it from the files acme.sh installed:

cat /etc/ssl/mail/mail.key /etc/ssl/mail/mail-fullchain.pem | sudo tee /etc/ssl/mail/mail-chain.pem > /dev/null
sudo chown root:root /etc/ssl/mail/mail-chain.pem
sudo chmod 600 /etc/ssl/mail/mail-chain.pem

Note the order: key then certificate. Reversing it is the usual reason Postfix rejects the file. Then set the parameters:

sudo postconf -e 'smtpd_tls_chain_files = /etc/ssl/mail/mail-chain.pem'
sudo postconf -e 'smtpd_tls_security_level = may'
sudo postconf -e 'smtp_tls_security_level = may'

On Postfix older than 3.4, use the legacy pair instead:

sudo postconf -e 'smtpd_tls_cert_file = /etc/ssl/mail/mail-fullchain.pem'
sudo postconf -e 'smtpd_tls_key_file = /etc/ssl/mail/mail.key'

Do not set both. When smtpd_tls_chain_files is non-empty Postfix ignores the legacy parameters entirely and logs a warning, which is a confusing thing to debug if you left the old lines in place while migrating.

The smtpd_ parameters govern mail arriving at your server and smtp_ those it sends onward, which is why both appear above. Apply the change:

sudo systemctl reload postfix

Reload rather than restart wherever it works, so mail in flight is not interrupted. If a service still presents the old certificate afterwards, then restart it.

Step 6: Configure Dovecot

Check your version before editing anything, because the setting names changed:

dovecot --version

On Dovecot 2.4 and later, the settings are ssl_server_cert_file and ssl_server_key_file, and they take a plain path:

ssl = yes
ssl_server_cert_file = /etc/ssl/mail/mail-fullchain.pem
ssl_server_key_file = /etc/ssl/mail/mail.key

On Dovecot 2.3, they are ssl_cert and ssl_key, and the value needs a leading <, which tells Dovecot to read the file rather than treat the path as the value itself:

ssl = yes
ssl_cert = </etc/ssl/mail/mail-fullchain.pem
ssl_key = </etc/ssl/mail/mail.key

The file to edit is normally /etc/dovecot/conf.d/10-ssl.conf. Be aware that the change in 2.4 goes well beyond these two names: the configuration format changed, a 2.3 configuration will not load unchanged, and 2.4 requires dovecot_config_version as the first setting in dovecot.conf. Dovecot publishes an online configuration upgrader for that migration, which is a separate job from installing a certificate.

Either way, point the certificate setting at the full chain file rather than the leaf alone. Dovecot’s documentation asks for the certificate followed by each intermediate in order, and notes that a full chain file from your CA already contains exactly that.

sudo systemctl reload dovecot

Optional: Exim instead of Postfix

Exim uses its own two options, and the same files work unchanged:

tls_certificate = /etc/ssl/mail/mail-fullchain.pem
tls_privatekey = /etc/ssl/mail/mail.key

Where those lines go depends on the distribution: often /etc/exim4/exim4.conf.template on Debian and Ubuntu, or a split configuration under /etc/exim4/conf.d/. The service name differs too, commonly exim4 on Debian-based systems and exim elsewhere, so check your distribution’s packaging before adjusting the reload command from Step 4.

Step 7: Test what the server presents

Test from another machine, not from the mail server itself. For submission over STARTTLS:

openssl s_client -starttls smtp -connect mail.example.com:587 </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates

For IMAP over STARTTLS, and for implicit TLS on 993 and 465, the same pattern applies with the port and protocol changed:

openssl s_client -starttls imap -connect mail.example.com:143 </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
openssl s_client -connect mail.example.com:993 </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates

One caution that trips people up here. These commands do not check that the certificate matches the hostname. They print what the server sent, and a certificate for the wrong name prints just as happily as the right one, so “it connected” is not evidence the certificate is correct. Read the names on it yourself:

openssl s_client -connect mail.example.com:993 </dev/null 2>/dev/null | openssl x509 -noout -text | grep -A1 "Subject Alternative Name"

The hostname your clients type must appear in that list. Clients match against the subject alternative names and ignore the common name, so a certificate whose only correct name is in the CN will still be rejected.

Step 8: Confirm renewal reaches the services

Because Step 4 registered both the destination paths and a reload command, a renewal now copies the new files into place and reloads the services on its own. Nothing else needs scheduling: the cron entry from Step 1 runs daily and acts only when a certificate is due.

acme.sh’s default is to renew 30 days after issuance, which is often described the other way round as 30 days before expiry. A negative value for --days is what expresses days before expiry, and where the CA publishes ACME Renewal Information, current acme.sh follows the window the CA suggests instead.

Prove the whole chain of events once, rather than discovering a broken link in three months:

acme.sh --renew -d mail.example.com --force

This performs a real renewal and counts against your CA’s rate limits, so run it once rather than repeatedly. Then re-run the test from Step 7 and confirm the dates moved.

Common problems

  • Validation fails as unauthorized or invalid response. The CA could not read the TXT record. Confirm the name is exactly _acme-challenge under the host being validated, that the value has no stray quotes, and that it is visible publicly with dig TXT _acme-challenge.mail.example.com +short before retrying.
  • EAB or account errors. Re-check the directory URL for production against staging, and the two EAB values for whitespace. Some CAs issue credentials valid for a single registration, so a retry after a failure may need a fresh pair.
  • The service still presents the old certificate. Confirm the paths in the configuration are the ones acme.sh installs to, that the reload actually ran, and that nothing else is answering on that port: a proxy, a container, or a second instance will happily serve its own older certificate.
  • Postfix logs a warning about ignored parameters. You have both smtpd_tls_chain_files and the legacy pair set. Remove the legacy lines.
  • Dovecot fails to start after editing. Check the version against Step 6. Using 2.3 setting names on 2.4, or omitting the leading < on 2.3, are the two common mistakes.
  • You need more detail. Add --debug to any acme.sh command, and read the mail service’s own log, which names the file it could not load.

For error strings that come from the ACME protocol rather than from acme.sh or your mail server, such as badNonce, unauthorized or a CAA record refusing issuance, see our guide to fixing ACME SSL certificate errors.

Frequently Asked Questions

Which hostname should the certificate cover?

The one clients type into their mail settings, which is usually the MX hostname such as mail.example.com, not the domain part of the email address. If people connect using several names, put all of them on the certificate with a -d each. A certificate for example.com alone does nothing for a client configured to reach mail.example.com.

Why does my Dovecot configuration stop working after an upgrade?

Because Dovecot 2.4 renamed the TLS settings to ssl_server_cert_file and ssl_server_key_file and changed the configuration format more broadly, so a 2.3 configuration does not load unchanged. Version 2.4 also requires dovecot_config_version as the first setting in the main configuration file. Dovecot publishes an online upgrader for converting an existing configuration.

Should I use smtpd_tls_chain_files or the older cert and key parameters?

smtpd_tls_chain_files on Postfix 3.4 and later, which Postfix documents as the preferred way and which supersedes the older settings. It takes one file with the private key first and the certificate chain immediately after. Setting both it and the legacy parameters is the mistake to avoid: the legacy ones are then ignored and a warning is logged.

Can I use HTTP validation instead of DNS?

Only if the same host also serves a website on port 80 for that hostname, which many mail servers do not. DNS validation avoids the question entirely and works behind NAT or a firewall that exposes only mail ports. It is also the only route to a wildcard, if you would rather cover several mail hostnames with one certificate.

Is it safe to point the mail services straight at acme.sh’s own directory?

It works, but acme.sh asks you not to, because that folder is internal and its layout can change between versions. Installing to a path of your own with --install-cert costs nothing, keeps the service configuration stable, and gives you a natural place to hang the reload command that renewals need anyway.

Do I need to restart, or is a reload enough?

Try reload first, since it avoids dropping connections and interrupting mail in flight, and it is generally sufficient for a certificate change. If a service still presents the old certificate after a reload, restart it and use that in the reload command from Step 4 instead. What matters is that whichever command you choose is the one acme.sh runs after every renewal.

For more on the protocol behind all of this, see what the ACME protocol is and our ACME SSL certificates page. For working with the certificate files directly, see our OpenSSL guides, and to check a public host from outside, the SSL Checker.

Save 10% on SSL Certificates when ordering from SSL Dragon today!

Fast issuance, strong encryption, 99.99% browser trust, dedicated support, and 25-day money-back guarantee. Coupon code: SAVE10

A detailed image of a dragon in flight
Written by

I've been writing for SSL Dragon for over 10 years, focusing entirely on SSL certificates and digital security. My job is to take complex cybersecurity topics and strip away the jargon, making sure you get the clear, practical information you need to keep your website safe.