Let’s do Postfix slowly and properly – Part 7: Encryption

Encrypting MTA-to-MTA encryptions for private and secure relay of email

This post continues my Postfix series. In the last post I wrote about how to use my Dovecot setup to authenticate me when I want to use Postfix (e.g. to send out email). Since I can now authenticate using a password, it’s high time to focus on encryption.

One of the many ways that talk of encryption trips me – and probably others – up is not just the maths and the intricacies of encryption technology. It’s the simple fact that with any service there isn’t just one thing to encrypt or not. You can encrypt communications but leave everything plaintext on the server, trusting that only trusted people ever gain access. You can encrypt password transmission but then switch to plain http because https is too costly in terms of server load. And so on and so forth.

With email I have already talked about encryption for submission. On a pure gut feeling level this may seem to be the priority: Submission is where your mail passes from your hands into the server and it’s where it’s most easily identifiable as emanating from you. Any sysadmin on your work network will be able to see that your MUA is connecting to your server’s port 587 and know what you’re doing. If you’re not encrypting that transmission they will also be able to read it.

Compared to that what happens once email leaves or arrives on a mail server may feel less pressing. However, your private MTA identifies you just as much as your private MUA. So today we’re going to do as much as we can to secure SMTP relay, i.e. the passing of mail from one SMTP server (or MTA) to another.

The first thing to note is that there is an important difference between relay and submission: Unlike submission we are not in control of all the involved parties. Either some ‘foreign’ MTA is calling on us with mail to deliver or we are calling on them to hand over our own missives. It takes two to tango, and the same goes for establishing encrypted communications. The other party may not support encryption which leaves us with the choice of either refusing the connection or accepting that it will not be encrypted. I’m going to assume in the following that we are willing to compromise: We want encryption whenever possible but we would rather drop the demand than miss out on delivering or receiving a message.

The other thing to keep in mind here is the subtle difference between two very similar looking types of settings: those beginning with smtp_ and those beginning with smtpd_. Now, the difference between the two roles involved in relay – the sender and the receiver – isn’t as distinct as those involved in submission because both parties are MTAs, however they do take on either a client role or a server role in any given connection. So Postfix allows you to set different policies for incoming and outgoing connections. All settings beginning with smtpd_ govern the former while smtp_ settings determines how to handle the latter.

Turning it on

Turning on encrypted SMTP traffic is actually quite simple. The two settings are

smtpd_tls_security_level = may
smtp_tls_security_level = may

and relate to incoming and outgoing connections respectively. I have set both to “may” which means that in both cases we will use “opportunistic” encryption. That means that I encrypt whereever possible – i.e. if both MTAs agree on it – but I will accept unencrypted connections. This is still the recommendation, although encryption is increasingly the expectation. Technically, it means that connections on port 25 will be initially unencrypted but the server should inform the incoming MTA that it offers encryption. The client can then proceed with a STARTTLS command which should eventually upgrade the connection.

Aft that I need to tell Postfix where to find my certificates. The certificate should cover the domain name that my server operates as when it comes to email, so that would be the name that I have entered into my MX setting. For that name I have a let’s encrypt certificate so I use that again for both incoming and outgoing traffic:

smtpd_tls_cert_file = /etc/letsencrypt/live/brokkr.net/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/brokkr.net/privkey.pem

Note the ‘d’ at the end. I use my server’s certificate for incoming connections (i.e. when I take on the server role as outlined above) but not for outgoing connections. It is possible to set up client certificates (the same settings as above minus the ‘d’) but it is not part of a standard setup. Think of how the web works: It is the responsibility of the web server to provide encryption using it’s certificate, not yours as the visitor. Likewise, it’s the web server’s certificate that is used to ensure the visitor that they are talking to the legitimate server. The same goes for mail servers.

My current Postfix version (3.4) has disabled all versions of SSL and allows all versions of TLS (1.0 to 1.3). TLS versions 1.0 and 1.1 are currently out of favour due to various vulnerabilities. It is possible to disallow those by using the smtpd_tls_protocols setting:

smtpd_tls_protocols = !TLSv1, !TLSv1.1

The preceding exclamation point means that these versions are excluded. This is the recommended way to do it. I am not sure that there is much point to this, however, as a) I am still allowing unencrypted connections and b) I believe the servers will always pick the best available option that they can agree on. So it might just court encryption failure. If the exchange then reverts to unencrypted would be up to the other MTA. Personally, I will take weak encryption over no encryption any day of the week. It’s not about shutting out the NSA, it’s about not allowing more information than necessary to be casually readable (i.e. in plaintext) to any and all network points.

After turning on encryption I did find that I had problems following connections in the logs. It may be a little information overload depending on your curiosity level but for me I found it helped to set a higher level of logging for TLS:

smtpd_tls_loglevel = 2
smtp_tls_loglevel = 2

Level 2 gives me a peek into the exchange, enough to see whether it’s working or not. The various levels are described in the Postfix TLS readme.


When I set smtpd_tls_security_level to “may” I changed something that may (pardon the pun) or may not be immediately apparent. The setting not only applies to incoming MTA connections on port 25, it also applies to submission on port 587. In other words, encryption is not mandatory but optional for MUAs as well. While it’s difficult to make demands on other servers, I can make demands of the MUAs that I use myself. So I will want to change submission to require encryption.

Sadly, there is no flip switch in main.cf that allows me to make exceptions for submission. There is however, a separate service defined in master.cf called submission that I can load with overriding parameters.

The items defined in master.cf can be a bit confusing, so it’s worth making explicit what exactly the submission line refers to. Each line in master.cf refers to a service, short or longlived, that is under the control of the Postfix master service, the one running as root. Most of them are small onejob programs residing in Postfix’s daemon_directory, but some are references to standards defined in the /etc/services file. This is a reference file that any internet facing program can see what port a service is expected to run on.

If we look at the predefined (but commented out) submission line in master.cf, it looks like this:

# submission inet n       -       y       -       -       smtpd
#    -o syslog_name=postfix/submission
#    -o smtpd_tls_security_level=encrypt
#    -o smtpd_sasl_auth_enable=yes
#    -o smtpd_tls_auth_only=yes
#    -o smtpd_relay_restrictions=permit_sasl_authenticated,reject

What that essentially tells Postfix, once I uncomment it, is to run another smtpd service in addition to the default one. Only this one it should a) call submission (and log the output with a ‘submission’ tag) and b) run on port 587 (thanks to the services file).

Now the whole point of port 587 and this post is of course to encrypt. So I use the exact same option as before, smtpd_tls_security_level, and override the main.cf value of may with encrypt. This means that encryption is longer opportunistic but mandatory. To get technical, if a client connects on port 587 and does not use the STARTTLS command it should get reprimanded.

This raises the question if I should be even stricter and use the protocol restrictions here that dismissed earlier when talking about MTA-to-MTA encryption. It might be considered a neat way to be told if a client I am using does not support newer versions of encryption. Or it might just be a PITA when I need my email to get sent the most. Anyway, the option is there in setting smtpd_tls_mandatory_protocols to exclude old TLS versions similar to before. Note the “mandatory” in the name – it will not affect the opportunistic connection on port 25. I checked my logs and noticed that without any prompting TLS 1.3 was being used by my webmail client, so I don’t think I need to worry too much.

I also uncomment the SASL option in order to allow SASL specifically for this service. This mean that I no longer need to globally allow SASL in main.cf with the smtpd_sasl_auth_enable option. In fact, I should absolutely disable it. In order to completely avoid attempts to get permission to relay from SASL authentication (I have seen a few in the logs), I should also remove SASL from the global smtpd_relay_restrictions in main.cf and instead uncomment the last submission “-o” line, namely

-o smtpd_relay_restrictions=permit_sasl_authenticated,reject

This way authentication is only available on port 587 which requires encryption. The only way to use relayal on port 25 is if Postfix considers itself the ultimate destination for the mail recipient.


As always the easiest way to test the stup is with telnet:

telnet mailserver.tld 25
telnet mailserver.tld 587

In both cases – once I have ehlo’ed it – the server should list 250-STARTTLS as an option. It should also not list AUTH PLAIN as an option on port 587 before the connection is encrypted.

Moving on

Now that the user facing parts are coming together, it’s time to look more outward and focus on how an MTA interacts with another MTA, a hugely important topic for any selfhoster that fears getting isolated from Gmail and Outlook users. It is certainly not an unfounded fear but it tends to get slightly blown out of proportion due to general open web doom-mongering. Once that is taken care of, we will get back to looking at some internals that will facilitate sorting, filtering and the like (LMTP, Rspamd and Sieve).

Door Closed with Padlock in Close Up © Maurijn Pach, Pexels license


Hej Michael, tak skal du have – glad for at høre, at den kunne bruges 🙂 Og SEO er ikke min styrke, så den er desværre ikke så højt i søgeresultaterne, som jeg kunne ønske 😀

Hi, nice article. One remark concerning mandatory TLS for submission:

submissions 465/tcp ssmtp smtps urd # Submission over TLS [RFC8314]
(from /etc/services)

(aka “implicit TLS”) should normally be preferred these days over mandating STARTTLS over port 587.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.