Blog post

Hardening Caddy and Mailcow with HSTS and modern TLS

How I hardened my Caddy webserver and Mailcow mailserver by enforcing HTTPS and restricting TLS protocols, ciphers and signature algorithms.

5 min. read
Ward Pieters
caddymailcowtlshstssecuritypostfixdovecot

Intro

To improve the security of my webserver and mailserver, I made a few changes to enforce HTTPS and restrict TLS protocols, ciphers and signature algorithms.

The webserver is Caddy, which automatically obtains and renews TLS certificates. The mailserver is Mailcow, which uses Postfix for SMTP and Dovecot for IMAP.

Enforcing HTTPS with Caddy

Caddy automatically obtains and renews certificates and redirects HTTP traffic to HTTPS. I added an HSTS snippet so browsers remember that the site must only be accessed over HTTPS:

(hsts) {
    header / Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
}

mail.ward.nl {
    ...
    import hsts
    ...
}

Hardening Mailcow

Mailcow lets me add Postfix and Dovecot overrides in its data/conf directory. These files are preserved as configuration inputs and are applied when the mailcow stack is recreated.

Postfix TLS settings

I added the following to data/conf/postfix/extra.cf:

tls_high_cipherlist = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
tls_preempt_cipherlist = yes
tls_ssl_options = NO_COMPRESSION

smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_ciphers = high
smtp_tls_mandatory_ciphers = high

smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_ciphers = high
smtpd_tls_mandatory_ciphers = high

tls_config_file = /opt/postfix/conf/openssl.cf
tls_config_name = postfix

The smtp settings control Postfix as a TLS client when it delivers mail. The smtpd settings control incoming SMTP connections. Both reject SSLv2, SSLv3, TLS 1.0 and TLS 1.1, leaving TLS 1.2 and newer protocols available.

The explicit cipher list uses authenticated encryption with forward-secret key exchange. tls_preempt_cipherlist lets the server choose the cipher, and disabling compression avoids TLS compression-related attacks. Postfix's high cipher policy is used for the broader SMTP settings.

Postfix also needs an OpenSSL configuration for the signature algorithms used during TLS negotiation. I created data/conf/postfix/openssl.cf:

postfix = postfix_settings

[postfix_settings]
ssl_conf = postfix_ssl_settings

[postfix_ssl_settings]
system_default = postfix_tls_defaults

[postfix_tls_defaults]
SignatureAlgorithms = rsa_pss_rsae_sha512:rsa_pss_rsae_sha384:rsa_pss_rsae_sha256:RSA+SHA512:RSA+SHA384:RSA+SHA256:ECDSA+SHA512:ECDSA+SHA384:ECDSA+SHA256

This keeps certificate signature negotiation to RSA-PSS, RSA and ECDSA algorithms with SHA-256 or stronger hashes.

Dovecot TLS settings

For IMAP and POP3, I added the following to data/conf/dovecot/extra.conf:

ssl_min_protocol = TLSv1.2
ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305

Dovecot now requires TLS 1.2 or newer and uses authenticated-encryption cipher suites, with DHE suites retained for clients that need them.

Applying and testing the changes

After adding the override files, I applied the Mailcow configuration from the Mailcow directory:

docker compose restart mailcow-postfix mailcow-dovecot

I then checked the HSTS response header:

curl -I https://mail.ward.nl/

The response should contain the Strict-Transport-Security header. For the mail services, I tested the public SMTP and IMAP endpoints with OpenSSL and verified that old protocol versions are rejected:

openssl s_client -connect mail.ward.nl:25 -starttls smtp -tls1
openssl s_client -connect mail.ward.nl:25 -starttls smtp -tls1_2
openssl s_client -connect mail.ward.nl:993 -tls1
openssl s_client -connect mail.ward.nl:993 -tls1_2

The TLS 1.0 connections should fail, while TLS 1.2 connections should negotiate successfully. I also checked that normal mail delivery, IMAP login and certificate validation still worked with real clients.

Problems?

When a mail client stops connecting after a TLS change, check its supported protocol and cipher suites first.

For Mailcow issues, inspect the Postfix and Dovecot logs after recreating the stack. For HSTS issues, remember that a browser may cache the policy for up to two years, so test on a domain whose subdomains are all ready for HTTPS.

Sources