How to install a complete mailserver on Debian 8/9, featuring Postfix, Dovecot, MySQL, Spamassassin, ClamAV, Roundcube and Fail2ban.

~ the howto that actually works ~

Part 1: Introduction
Part 2: Preparations: Apache, Let’s Encrypt, MySQL and phpMyAdmin
Part 3: MTA: Postfix
Part 4: IMAP server: Dovecot
Part 5: Web interface: Roundcube
Part 6: Spam filtering: SpamAsasssin
Part 7: Antivirus: ClamAV and ClamSMTP
Part 8: Quota and other Roundcube settings
Part 9: Using mail with a remote IMAP client (i.e. Thunderbird)
Part 10: Counter brute-force attacks with Fail2ban
Part 11: Sources, config files, colouring and comments

On this page

Real vs. virtual users
Installation
Configuration
Sending mail
Receiving mail
Setting myhostname
Which domains to accept mail for
Which addresses to accept mail for
Change to maildir
Connect Postfix to the MySQL database
Message size limit

Comments are on the last page.

On this page

On this page

Postfix is the software that listens for incoming mail on port 25 and sends outgoing mail to other mailservers. It stores mail in mailboxes you define.

Real vs. virtual users

Postfix differentiates between ‘real’ and ‘virtual’ users (and real and virtual domains). Real users are entities (people) that have a user account on the server. Virtual users are entities that have only e-mail accounts, not necessarily server accounts.

Read more about virtual vs. real here: http://www.postfix.org/VIRTUAL_README.html

Installation

# aptitude install postfix postfix-mysql

Upon installation you will be replacing Debian’s default mailserver Exim4. Accept the installer’s solution to remove exim4 and related packages.

The following actions will resolve these dependencies:

     Remove the following packages:
1)     exim4
2)     exim4-base
3)     exim4-config
4)     exim4-daemon-light



Accept this solution? [Y/n/q/?] y

Postfix will ask you about your configuration. I’m going to install the ‘Internet site’ version: mail is sent and received directly using SMTP.

Install the Internet Site configuration

Install the Internet Site configuration

System mail name: example.com
If you’ll be setting up your mailserver for multiple domains just pick a main domain; it should be the domain that’s most likely to stay on your server the longest. In technical regards it doesn’t really matter.

Enter your domain name

Enter your domain name

Configuration

Postfix’s mail configuration files are /etc/postfix/main.cf and /etc/postfix/master.cf. Main.cf contains application settings while master.cf contains transport settings (which protocol to use, which port number, etc.).

Sending mail

You don’t need to be change anything to be able to send out mail. Keep on eye on your log file with

# tail -f /var/log/mail.log

and in another session send a test mail with:

# echo "This is a test mail" | mailx -vvv -s "test 01" your@email.here
Mail Delivery Status Report will be mailed to <root>.

Line 2 is the server’s reply if all is well. It is normal and does not indicate an error.

I like to number my test mails so I know which one I received if I need to test more than once. -s specifies the subject and -vvv sets verbosity to high so you can view everything that’s going on.
If the mail doesn’t arrive try sending to a non-Gmail account or an account on some other server you know will not block your mail for spam.

Receiving mail

Test if you can receive mail by sending a mail to vorkbaard@example.com (use your own username). Check /var/mail/vorkbaard (or whatever username you have) for growth. I did that by running

watch -d -n 1 du -cs /var/mail

in a tmux pane.
(Press Ctrl + C to stop.)

tmux with file size in the upper and log file in the lower pane

tmux with file size in the upper and log file in the lower pane


Once you proved your system can send and receive mail you can continue. If one or both do not work troubleshoot until it does. Check your router, firewall, DNS settings, the logfiles (/var/log/mail.log, /var/log/syslog) and mxtoolbox.com for pointers.

Only if sending and receiving works continue with the rest of this article. That way if something doesn’t work further on you will at least know where you don’t need to look. (Namely: your isp (it’s not blocking port 25); your router (port forwarding works); local firewalls (they’re not blocking mail access); etc.)
A succesful mail delivery ends in these logfile lines:

... Successful quit
... removed

Setting myhostname

The myhostname value in Postfix is read by mailservers receiving mail from your system. Your IP address should correspond to your fully qualified domain name (FQDN). If not some spamfilters will raise their eyebrows and add a point to your spam score.

To retrieve your FQDN send a ping to your registered hostname:

# ping mail.example.com
PING mail.example.com (12.34.56.78) 56(84) bytes of data.
64 bytes from 12.34.56.78.dynamic.upc.nl (12.34.56.78): icmp_seq=1 ttl=64 time=0.172 ms

In my case it was 12.34.56.78.dynamic.upc.nl. Enter that as your myhostname value in /etc/postfix/main.cf.

To follow the process you can enter your domain name in mxtoolbox.com and have them perform an SMTP test on your host. Notice that it says ‘Reverse DNS does not match SMTP banner’. After you changed the myhostname do

# postfix reload

and run the test again. It should now say: ‘OK – 12.34.56.78 resolves to 12.34.56.78.dynamic.upc.nl’

On a side note: my ISP gives me semi-static IP address. That is, it’s dynamically assigned but hasn’t changed in years. Because I am on a consumer line using a dynamic address from an ISP pool Spamhaus listed me on their PBL. Spamhaus’s PBL is a list of (among other things) dynamic public IP addresses that have no business running mail servers. However my ISP has allowed me to run a mail server, I don’t run an open relay and take care not to (accidentally) send out spam from my network so I used Spamhauses self-delisting tool to remove my public ip address from the PBL.

SORBS is refusing to remove me from their DUHL list of dynamic IP ranges.

You will need to keep an eye out for things like this when running a mailserver on a consumer line.

Which domains to accept mail for

Open up /etc/postfix/main.cf and look at the mydestination value.

mydestination = example.com, mail.localdomain, localhost.localdomain, localhost

This value tells Postfix which domains to accept mail for. The interesting part of course is ‘example.com’. If you’re going to host more domains add them here. You can do it in a separate file, you can do it in a database. I’m doing it in main.cf.

Which addresses to accept mail for

As discussed, Postfix can handle two types of users: virtual users and real users. Root is a real user and in my case vorkbaard is because they are user accounts on my server. However most the people who are going to get an e-mail account on my server will never log on to the server to work with a local user account. No home folders on the server, no bash preferences, etc. So for mail we’re going to use virtual users.

With real users, the question which user should own the mail had a simple answer: the user. With virtual users this is a bit different. Since no real user exists, who should own the mail? Postfix provides two options: 1 – create virtual uid’s and gid’s to go along with the virtual user accounts or 2 – use one dedicated uid and gid for all users.

Since the virtual users won’t be logging on to the server there’s no harm in them not being the owner of their mail. Option 2 is easier and not less safe so I’ll go with option 2.

More on virtual mailboxes: http://www.postfix.org/VIRTUAL_README.html#virtual_mailbox

in main.cf add:

virtual_mailbox_domains = example.com

This tells Postfix which domains to listen for mail to. This replaces the entry in mydestination so remove example.com from the mydestination value. If you don’t /var/log/mail.log will inform you that you should not list domains both in mydestination and in virtual_mailbox_domains.

virtual_mailbox_base = /var/mail/vmail

This is the folder where mail will be stored.

virtual_mailbox_maps = hash:/etc/postfix/virtual_mailbox_maps.cf

virtual_mailbox_maps tells Postfix where to look for virtual e-mail accounts.

Add information about the owner of the mailboxes:

virtual_gid_maps = static:5000
virtual_uid_maps = static:5000
virtual_minimum_uid = 5000

virtual_minimum_uid is used for setups where you will be using separate users for each mailbox. It prevents receiving mail for system accounts. We will use one owner for all mail and its uid and gid will be 5000 so the virtual_mininum_uid can be 5000 as well.

Let’s create the owner:

# useradd -d /var/mail -U -u 5000 vmail

This sets the user’s home folder to /var/mail, creates a group with the same name as the user, adds the user to it and sets the user’s uid to 5000. The username is vmail.

Note that ‘vmail’ is not a reserved name. It stands for ‘virtual mail’ and is often used in articles explaining how to set up mailservers on Linux. You can choose any name you want and store the mail in any directory you want as long as you have the corresponding uid and gid values in main.cf and deal out the right permissions on the directories you’re using.

Since we’re going to store mail in /var/mail/vmail we should create the folder and give user vmail access to it. Create a folder under /var/mail/vmail for each domain you’ll be receiving mail for.

# mkdir -p /var/mail/vmail/example.com
# chown -R vmail:vmail /var/mail/vmail 

create /etc/postfix/virtual_mailbox_maps.cf and add:

tinus@example.com example.com/tinus

(Tinus is Tinus de Tester, my default test user.)

The format is:

email@example.com  	where_to_store_it

The storage location is relative to virtual_mailbox_base. So mail for tinus@example.com will be stored in /var/mail/vmail/example.com/tinus.

User Tinus’ would be stored in /var/mail/vmail/example.com/tinus.

After the changes do

# postfix reload
# postmap /etc/postfix/virtual_mailbox_maps.cf

That last command will create a file /etc/postfix/virtual_mailbox_maps.cf.db which is faster for Postfix to read.

Now send a mail to tinus@example.com and see if a file is created under /var/mail/vmail/example.com/. If not check your logfile, any non-delivery reports, and so on.

My mailserver (/var/log/mail.log) spat this at me:

Recipient address rejected: User unknown in local recipient table

Hmm… The local recipient table? I’m not doing local recipients, remember? Turned out I forgot to set virtual_mailbox_domains.

Only continue if mail gets delivered (=the file /var/mail/vmail/example.com/tinus is created or increases in size when you send a mail to tinus@example.com).

At this point you have a working mail system which is able to send and receive mail. Anything outside your server will not be the cause of mail not going out or coming in (safe for obvious exceptions such as a failing internet connection).

Change to maildir

By default Postfix uses the mbox format to store mail. In short: mbox stores all mail in one big file. The advantage is it uses just one inode (which more or less means it uses one file) and is easy to back up; the drawback is it depends on lockfiles and doesn’t allow access from more than one source concurrently, and is relatively easy corrupted.

We’ll be changing to the maildir format which uses one file per e-mail item. Its advantages and drawbacks are inverted relative to mbox.

First delete /var/mail/vmail/example.com because the system cannot create a directory if a file exists with the same name in the same parent directory. Also delete its folder; the domain folder should be created automatically.

# rm -rf /var/mail/vmail/example.com

A lot of howtos will tell you to add home_mailbox = Maildir/ and mailbox_command = to your main.cf. However since we’re using virtual users we only need to change one thing and it is not in main.cf: in /etc/postfix/virtual_mailbox_maps add forward slashes to the locations. Change

tinus@example.com example.com/tinus

to

tinus@example.com example.com/tinus/

Do

# postmap /etc/postfix/virtual_mailbox_maps.cf

to rehash the mailbox database.

Verify mail can still be sent and received before you continue. A mailbox structure should have been created under /var/mail/vmail/example.com/tinus/.

Why go to the trouble of setting up Postfix, changing to virtual mailboxes, changing to maildir if we’re going to use a database to handle the mailboxes? Because this way you will know for certain that the permission structure is in order and mail can be sent and received. Again: if this works and you will encounter a problem in the next section you will know where not to look. So, easier troubleshooting.

Connect Postfix to the MySQL database

Instead of using a file to manage the list of users you can have Postfix checking your database. Why? My main reason was because Roundcube, the web interface for mail I am using, has a plugin that allows users to change their IMAP passwords. Which is pretty essential for any mail system if you ask me. There’s also a plugin for PAM but we’re not using real users.

Storing users in a database has other advantages too, the main one that comes to mind is custom fields to select on. To illustrate this I added a boolean field called ‘active’. If set to 0 the account is suspended, if set to 1 it’s active.

Start by deleting /etc/postfix/virtual_mailbox_maps.cf and /etc/postfix/virtual_mailbox_maps.cf.db.
Open main.cf and change

virtual_mailbox_maps = hash:/etc/postfix/virtual_mailbox_maps.cf

to

virtual_mailbox_maps = mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf

The filename mysql_virtual_mailbox_maps.cf is arbitrary however it seems like a good idea to make it somewhat descriptive of its purpose. It is a common name for this kind of file.

# postfix reload

We need to tell Postfix how to connect to the database. Create /etc/postfix/mysql_virtual_mailbox_maps.cf and add:

user = mailman
password = P@ssw0rd
hosts = 127.0.0.1
dbname = postfix
query = SELECT 1 FROM addresses WHERE email = '%s'

Postfix replaces %s by the “input key” (in our case the e-mail address).

More info: http://www.postfix.org/mysql_table.5.html

Enter user Tinus in the database:

# mysql -u root -p
mysql> INSERT INTO `postfix`.`addresses` (`id`, `active`, `email`, `pwd`) VALUES ('1', '1', 'tinus@example.com', '');
mysql> quit

or use phpMyAdmin to do it. You can leave the password for now, we’ll get to that later.

See if it works:

# postmap -q tinus@example.com mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf

This should return 1 for existing addresses and nothing for non-existant addresses.

If it doesn’t work, check these things:
– Did you enter the real e-mail address in the database or did you copy the example.com address from this article?
– Is the mysql service running? (By default it should be.)
– Did you type the correct password in /etc/postfix/mysql_virtual_mailbox_maps.cf and the database?

Now if you send an e-mail to tinus@example.com Postfix will create a file called 1 under /var/mail/vmail. Why? Because we no longer define where to store mail. We’re going to do that with Dovecot via lmtp – the local mail transport protocol. Make sure the file /var/mail/vmail/1 is created though!

Troubleshooting:
– Check your /var/log/mail.log
– verify all files mentioned in /etc/postfix/main.cf actually exist. I had a log file entry “ warning: mysql:/etc/postfix/virtual_mailbox_maps.cf lookup error for “tinus@example.com””; it turned out in main.cf I had virtual_mailbox_maps = mysql:/etc/postfix/virtual_mailbox_maps.cf instead of virtual_mailbox_maps = mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf. See the difference?

Message size limit

The default maximum message size Postfix allows is 10MB. Is you need to change it open /etc/postfix/main.cf and enter

message_size_limit=20480000

for 20MB for example. The value is in bytes. More info: http://www.postfix.org/postconf.5.html#message_size_limit