Posted:

E-Mail Done My Way, Part 1 - postfix

11 minute read

0. The Journey - The basics and outlook (on the series, not the Microsoft mail client ;)
1. Postfix - the in and out, so to say. The robust, battle-hardened connection point for other mail servers on the internet to send emails to and receive emails from your domain(s). Also known as the MTA, the Mail Transfer Agent.
2. Dovecot - where you and your users talk to to get emails to their mail client, be it your smartphone, a mail client on your computer or just even the command line. It’s the IMAP server.
3. DKIM/DMARC/SPF - Just having postfix and dovecot up and running isn’t enough. We will also look at user authentication, letsencrypt certificates, DKIM, DMARC, SPF and the daily checks to make sure everything is humming along nicely.
4. The final stuff - How to make sure my e-mail server is happy and can do its job. Some simple checks, how to use fail2ban to keep bad servers and users away, checking log files, all those little things.

I hope you are just coming here after reading part 0 as we are now going in the details of my postfix configuration.

WARNING: This series is not for people trying to set up their first ever email server. I expect readers to know the basics and have some experience with postfix. Stuff like having to run postmap to create the correct hash files etc.

If you didn’t read part 0, here the TL;DR of what postfix does in my setup. It does the smtp part for all my domains. That’s mainly it. Authentication is handled by dovecot, which I will explain in part 2. Dovecot also does the sorting of incoming mails to the intended mailboxes.

But doing smtp means a lot of things. It includes rejecting connections from bots, spam servers, it means handling secure connections, checking SPF/DKIM/DMARC, signing outgoing emails with DKIM so it’s quite some heavy lifting.

I will explain all of this by going through the various blocks of my config file - the good ole /etc/postfix/main.cf file. Are you ready? OK. Let’s start. So I installed postfix on my Linux server. left as many defaults as possible untouched and grouped my specific setup at the top of the config file, so I can easily make changes. We will start with the first group:

# General important stuff
mydestination = localhost
myorigin = wildeboer.net
local_recipient_maps =
recipient_delimiter = +
smtpd_tls_security_level = may
smtpd_tls_cert_file = /etc/letsencrypt/live/mailhub.wildeboer.net/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mailhub.wildeboer.net/privkey.pem
smtp_tls_security_level = may
smtpd_tls_loglevel = 1
smtp_tls_loglevel = 1

As I’ve said, all user authentication and mail distribution stuff is handled by dovecot, so postfix doesn’t have to care about such things. Hence I can get away with the first two lines - mydestination = localhost looks weird, but as I treat all my domains as virtual domains, it works. This also means that I don’t need to set local_recipient_maps - which is empty for that reason.

I still have the myorigin entry in the config, even though it is hardly ever relevant.

The recipient_delimiter = + allows for subaddressing on the local user part of the e-mail address. So you can add any part to your e-mail address with a + symbol and all mails still land in your inbox. If your normal e-mail address is user@domain.tld you can now also use user+google@domain.tld , user+ignore@domain.tld etc. Very useful to use with signup pages so you can later see where that e-mail address has been also used. Typically for unsolicited newsletters etc.

The smtpd_tls_security_level = may tells postfix that we really like to have encrypted connections but if the other side can’t do that, we are also accepting their unencrypted connection.

Next we set up our letsencrypt certificates. Just the standard setup. As I’ve explained in my DNS article all emails for all my domain have mailhub.wildeboer.net as their mx, so we only have to care about certificates for that single hostname. Makes life a lot simpler!

We want to make sure we use rather modern TLS settings and avoid old and possibly broken things:

#Force TLSv1.3 or TLSv1.2
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

Next we tell postfix to not care about delivery of received e-mails but instead hand all of that to dovecot via the lmtp connection:

# transport
mailbox_transport = lmtp:unix:private/dovecot-lmtp
smtputf8_enable = no

As I’ve said, I don’t use any local accounts on the mailserver. Zero. Nada. Everything, without exception, is treated as a virtual domain and a virtual mail user. This again makes life a lot easier. No messing around with pam configs, LDAP etc. Just dovecot and postfix.

# Virtual domains and stuff
virtual_mailbox_base = /var/mail/vmail/
virtual_mailbox_maps = hash:/etc/postfix/vuser
virtual_transport = lmtp:unix:private/dovecot-lmtp
virtual_uid_maps = static:1003
virtual_gid_maps = static:1003
virtual_alias_maps = hash:/etc/postfix/valias
virtual_mailbox_domains=
        stallmanism.com,
        stallmanism.net,
        stallmanism.org,
        tcpid.net,
        tcpid.org,
        wildeboer.net

I left out a bunch of my other domain names to not make this list too long. What this part does is telling postfix that all mails live in subdirectories of /var/mail/vmail/ and that the exact mappings can be found in the vuser file.

The vuserfile has lines like this for each vaild email address:

user@tcpid.org tcpid.org/user/Maildir/

(the important part there is the Maildir/at the end — and yes, that trailing / is VERY important, as this tells postfix we are using the Maildir format, not mbox)

The virtual_transport line tells postfix to call dovecot to take care of final delivery. It’s none of postfix business.

All incoming emails thus go through one single user on the server called vmail, which has a user-ID and group-ID of 1003. This vmail user ofcourse doesn’t have a login shell or anything, it’s only purpose is to own the vmail folder and files.

In valias I have some simple forwarding aliases configured, lots of lines like this:

abuse@stallmanism.com postmaster
abuse@stallmanism.net postmaster
abuse@stallmanism.org postmaster

It is considered good behaviour to have a working abuse@ for every domain. I forward them all to postmaster@ so I can see them and act if needed.

Finally the list of all virtual domains that postfix is responsible for. And that’s mainly it for the virtual domain setup. If I buy a new domain, I add the domain here, make sure the aliases are added and, if needed, set up some user maildirs. And yes, I do all of that manually, no database or fancy web console. I only have a few users, so this setup is GoodEnough™ for me.

OK. Now we can integrate DKIM and DMARC. What postfix does here is calling out to opendkim and opendmarc through the milter system. Opendkim and opendmarc check incoming mail and add headers with the test results for, obviously, DKIM and DMARC. Opendmarc also does the SPF check on incoming mails.

# DKIM and DMARC
milter_default_action = accept
milter_protocol = 6
smtpd_milters = inet:127.0.0.1:8891,inet:127.0.0.1:8893
non_smtpd_milters = $smtpd_milters

How this is working exactly will be in part 3 of this little series. For now you just need to know it works :)

Are we done? Almost. Last but definitely not least we deal with security, probing bots and spammers. By telling postfix what to look for when some smtp server or mail client wants to talk to us. Think of this as a sort of smart firewall:

# Keep connections clean.
smtpd_delay_reject = yes
smtpd_helo_required = yes

smtpd_client_restrictions =
        permit_mynetworks,
        permit_sasl_authenticated,
        reject_unauth_pipelining,
        reject_unknown_client_hostname

smtpd_helo_restrictions =
        permit_mynetworks,
        permit_sasl_authenticated,
        check_helo_access hash:/etc/postfix/helo_access,
        reject_invalid_helo_hostname,
        reject_non_fqdn_helo_hostname,
        reject_unknown_helo_hostname

smtpd_recipient_restrictions=
        permit_mynetworks,
        permit_sasl_authenticated,
        reject_unauth_pipelining,
        reject_invalid_hostname,
        reject_unknown_recipient_domain,
        reject_non_fqdn_recipient,
        reject_unauth_destination,
        check_sender_access pcre:/etc/postfix/sender_access,
        reject_rbl_client zen.spamhaus.org,
        reject_rhsbl_client dbl.spamhaus.org,
        permit

Postfix defines different stages of a smtp transaction. First the other server opens a connection to us. We tell it what we can handle and it will tell us what it wants to do. Already at that point postfix can make some smart decisions on continuing the conversation or to end it right there.

That’s the smtpd_client_restrictions part. Here we allow connections from our own network with permit_mynetworks, we also allow authenticated users to continue with permit_sasl_authenticated. But if you try to do spammy things, you’re out - reject_unauth_pipelining. Especially if you connect with an IP address that doesn’t have a hostname in DNS - reject_unknown_client_hostname.

This already stops quite a lot of bots and spammers. And as it is early in the transaction, every reject here saves CPU cycles.

Once you’ve made it past these checks, we are ready to talk to you. Tell us what you want to send us. After that we check again for a few more things.

That’s the smtpd_recipient_restrictions part. Are you on our network - permit_mynetworks ? Fine. Trying to do nasty things - reject_unauth_pipelining ? You’re out. Your host- or domain name isn’t in DNS - reject_invalid_hostname, reject_unknown_client_hostname ? Out. Trying to abuse us to relay - reject_unknown_recipient_domain, reject_non_fqdn_recipient, reject_unauth_destination ? Get outta here. Are you on our own little blacklist - check_sender_access ? Gone. And we will ask some blacklists if you’re good or bad - reject_rbl_client, reject_rhsbl_client.

My own little blacklist is some regexes that tell some TLDs that they are not welcome on my mail server. Right now it’s three top level domains and whenever they try to send me emails, they get a nice reject message:

/\.ru.com$/ REJECT Mail from .ru.com is not accepted SLAVA UKRAINI
/\.ru$/ REJECT Mail from .ru is not accepted SLAVA UKRAINI
/\.su$/ REJECT Mail from .su is not accepted SLAVA UKRAINI

If you’ve passed all checks, we are happy to receive your e-mail(s) and hand them over to dovecot for final distribution.

That’s going to be in part 2. As you can see, postfix is a mighty beast. Battle hardened, since many, many years. Which makes configuration quite complex, but not impossible. I am sure we will see a lot of comments with (I hope) productive proposals and solid reasonings to make changes here and there. I am by no means an expert. But I have been running my own mail server for many years and picked up some knowledge on the way.

Comments and tips for an even better configuration thus are very welcome! Either via Mastodon (see below) or, if you prefer, as issue or maybe even a pull request at the repository that generates this blog:

https://codeberg.org/jwildeboer/jwildeboersource

This is NOT a definitive guide. This is a description of my setup. With all its good and bad things. Help me make it better, so we can all learn! So, on to

Part 2: Dovecot

COMMENTS

You can use your Mastodon or other ActivityPub account to comment on this article by replying to the associated post.