19. Install the mail server

by Double Bastion - Updated September 20, 2023

A complete mail server, or, more formally, a complete ‘electronic mail system’, is made up of 6 components:

1. The SMTP server: this is the server that handles the Simple Mail Transfer Protocol (SMTP) traffic. It is usually referred to as a Mail Transfer Agent (MTA) and its main function is to send emails to external mail servers and to receive emails from external mail servers. Accepted emails are included into the MTA’s queue. The best free and open source SMTP server, in the sense of the one that offers the most while asking for the least, is Postfix.

2. The IMAP and POP3 server: this is the server that allows email clients installed on the users’ computers (like Thunderbird), or web-based email clients (like Roundcube), to access the emails stored on the server and read them. The Internet Message Access Protocol (IMAP) is the protocol that allows the users to download the emails, while the original messages remain on the remote server, while the Post Office Protocol version 3 (POP3) is the protocol that typically downloads the emails to the clients’ computers and then deletes them from the remote server. Usually, the IMAP and POP3 server also acts as a Mail Delivery Agent (MDA) that is invoked by the MTA to fetch the incoming emails from the MTA and save them in the individual mailboxes. The best free and open source IMAP and POP3 server is Dovecot.

3. The antivirus application: the antivirus application is the program that scans all incoming and outgoing emails and their attachments (even compressed attachments) to find viruses and other types of malware. Once it detects a known virus/malware, it either moves the email to quarantine or deletes it. It has to be properly connected to the SMTP server, so that it can scan emails promptly, the very moment they arrive. Apart from real-time scanning of incoming and outgoing emails, the antivirus can be also used to scan periodically or on demand any sensitive directory on the server, such as the directory used to store emails. A virus that wasn’t detected when the email arrived can be detected during periodic scanning after an antivirus automatic update. The best free and open source antivirus application, both for mail servers and for general use on a server is ClamAV.

4. The spam filter application: the spam filter is the program that scans all incoming emails for spam characteristics. If it detects that an incoming email is spam, it marks it as such and the Mail Delivery Agent sends it to the Spam folder. It needs to be properly connected to the SMTP server, that will invoke it whenever an email arrives. The best free and open source spam filter is Apache SpamAssassin.

5. The web-based email accounts management application: this is the program that allows the administrator to add email domains, to add, modify or remove email accounts, to add email aliases, to implement quota limits for email accounts, etc., in an easy to use web interface. The best free and open source application for this purpose is Postfix Admin.

6. The web-based IMAP email client: This is the web application that allows the user to interact with the remote server to read/write/send emails. It is sometimes called a Mail User Agent (MUA). It has to be easy to use, responsive, it has to allow editing emails in HTML or plain text format, it has to allow adding attachments, changing passwords, configuring filters for incoming emails, configuring out-of-office replies, configuring canned emails, etc. The best free and open source application for this purpose is Roundcube.

Of course, some sysadmins will say that the 5th component is optional, because you can add email domains and create email accounts ‘by hand’ editing configuration files directly. However, if you try to do this for a large number of accounts, you will soon understand that the 5th component is actually needed in order to have a usable, complete mail server. The 6th component may also seem optional, since you can connect your locally installed Thunderbird to your remote mail server, and avoid using a web-based email client. Yet, in real life, a web-based email client like Roundcube, proves to be indispensable, since at least occasionally you will want to log in to your email account from different computers or devices that don’t have Thunderbird or an equivalent application installed and configured. So, for a complete mail server, the 6th component is also mandatory.

The Internet is full of truncated, outdated and wrong information about installing a mail server. You will hardly find a single article that mentions all the 6 components listed above, let alone to explain how to install and configure them properly. Following such incomplete, outdated and misleading guides, many beginner sysadmins ran into problems and eventually gave up installing and maintaining their own mail servers. Even websites that promote software self-hosting, with large collections of do-it-yourself guides, ended up suggesting to their readers that they might not want to install their own mail servers, because it’s … complicated. The truth is that it’s complicated to install a mail server when you have bad documentation and it’s very easy when you have complete and correct documentation.

To the 6 mandatory components listed above, we’ll also add Postgrey to implement greylisting, a method used to reduce spam, Mailman, to manage classic mailing lists, and phpList, to send mass emails such as newsletters, marketing emails, announcements, etc.

19.1. IP reputation

When maintaining a mail server it’s of utmost importance to have an IP with a good reputation at all times.This means an IP that is not included on any blacklist. Otherwise, many mail servers that will receive emails sent from your IP will reject them or will send them to the Spam/Junk folder.

Generally, when you rent a server, you will be assigned a ‘clean’ IP. However, there is a slight probability that a previous user of that IP sent spam and caused the IP to be included on a public blacklist. This is why the first thing you should do after renting a server is to check its IP against the main public blacklists, by using this online tool: https://mxtoolbox.com/blacklists.aspx

If you find that the IP of your newly rented server has been included on a public blacklist, you can contact the maintainers of that blacklist (whose contact details you can find on the official web page of the blacklist) and ask that they delist your IP, since you are the new user of that IP and you are not responsible for what the previous users have done using that IP. If they refuse, the next step is to contact the hosting provider of your server, explain the situation and ask them to contact the blacklist maintainers and require that they delist the IP. This is almost guaranteed that it will work. If it doesn’t, the last method is to ask the hosting provider to assign you a different IP, since you want to install a mail server and you can’t risk having your emails rejected because the previous users of that IP sent spam and the IP was listed on a public blacklist.

You can also check if the IP has been reported for malicious activities, on https://www.abuseipdb.com/ . If you find that your IP has been reported, you can take the same steps mentioned above, to try to delist the IP.

There is also the possibility that after you install your mail server and start using it, some blacklist maintainers will include your IP on their public blacklist. If they do it because you sent real spam emails becuase you didn’t know what qualified as spam, you can contact them, explain the situation and ask that they delist your IP, since you now know what qulifies as spam and you changed your email sending policy. If they do it because your mail server has been used as a relay by real spammers, you have to investigate the breach, recheck all the mail server configurations, fix the problem, make sure that no unauthorized person can use your mail server again, then contact the blacklist maintainers and explain that the server has been reconfigured and the problem fixed. This is why you have to closely follow the instructions from this guide in order to configure a secure mail server and prevent any attackers to access it.

In fact, from all the components that one might install on a server, the mail server will receive the greatest number of attacks, mostly those recorded in logs as ‘postfix-sasl authentication failure’. This means that the attackers will try to log in to the Postfix server in order to send their spam.

It can also happen that the maintainers of a public blacklist include in their blacklist a whole range of IPs, just because they noticed that some spammers used the ‘snowshoe spamming’ method. You may find that your own IP has been included on the public blacklist just because it was part of the IP range considered suspicious by the blacklist maintainers. In this situation, to delist your IP, you should contact the company that hosts your server, explain the situation and ask them to contact the blacklist maintainers and request that they delist your IP.

The idea is to always keep an eye on the server’s IP reputation. If the IP is included on a blacklist, this will seriously impact your email deliverability.

19.1.1. Spam definition

The best definition of spam is probably the definition given by spamhaus.org here. We reproduce it below.

Spam is generally understood to be Unsolicited Bulk E-mail (UBE).

  • Unsolicited: the recipient has not granted verifiable permission for the message to be sent.
  • Bulk: the message is sent as part of a larger collection of messages with identical content.”

Therefore, to avoid sending spam, you should make sure that when you send mass emails (one email is sent to tens, hundreads, or thousands of recipients at once) you have the explicit approval of the recipients to receive emails from you. For this, it’s necessary to make each potential recipient explicitly opt-in to your newsletter/mass emailing service. You can also use a double opt-in method, by asking the users to enter their email address in a form or check the option to subscribe to your newsletter/emailing service, and then send them an email and ask them to reconfirm their subscription by clicking a link or a button included in the email. Also, each email that is sent to a list of subscribers has to contain the unsubscribe link at the bottom, so that every receiver can have their email address removed from the mailing list at any moment.

If you live in the US or the recipients of your emails live in the US, your emails must comply with the CAN-SPAM Act. For an introduction to CAN-SPAM, CASL (Canadian Anti-Spam Law) and the European Union’s GDPR (General Data Protection Regulation) see this article.

19.2. Mail server ports

To run a mail server, you will need to open the following ports in the firewall:

  • 25 (SMTP)
  • 587 (STARTTLS over SMTP)
  • 993 (IMAPS)
  • 995 (POP3S)

You can open the port for SMTP by running:

ufw allow 25

Open all the ports mentioned above in a similar manner.

In order to use the web-based email client, you will also need to have ports 80 and 443 open, but you already opened them when configuring Nginx.

Ports 25, 465 and 587 can be blocked by your hosting provider on their servers, in order to prevent spam. As we mentioned at the beginning of this guide, you should check if the hosting provider blocks these ports before renting the server. In general, even if they block these ports, the hosting providers are willing to unblock them after you open a support ticket and explain that you intend to host a mail server that needs those ports open and that you intend to send only legitimate emails that comply with all the anti-spam regulations. Here is an example of a hosting provider explaining what to do, to have those ports unblocked.

If you are not sure whether your hosting provider blocks port 25 for outgoing traffic, you can run:

telnet smtp.mail.com 25

If the port is open, the output will look like this:

Trying 74.208.5.15...
Connected to smtp.mail.com.
Escape character is '^]'.
220 mail.com (mrgmxus005) Nemesis ESMTP Service ready

If the port is blocked, the output will look like this:

Trying 74.208.5.15...
telnet: Unable to connect to remote host: Connection timed out

(To end the telnet session press Ctrl + ] and then type quit and press Enter.)

After making sure that all the necessary ports are open, the next step is to add the proper configurations in the /etc/hosts and the /etc/hostname files. We explained how to do this in the Configure the hosts and the hostname chapter. So, if you followed the guide, you have these configurations in place and it’s time to move to the next step.

19.3. Set default timezone for PHP time functions

In order to see the correct time associated with the sent/received emails in your web-based email client, you have to set the correct timezone in the /etc/php/7.4/fpm/php.ini file. Open this file:

nano /etc/php/7.4/fpm/php.ini

Change the date.timezone directive in /etc/php/7.4/fpm/php.ini, to look like this:

date.timezone = Europe/London

where Europe/London is the timezone your server belongs to. If you live in a different timezone than that of your server and you consider that your location’s timezone is more appropriate to use, you can set it instead of the server’s timezone but don’t forget to also change the server’s clock to match your location’s timezone, as described in the Adjust the server’s clock, if needed chapter. For a complete list of date.timezone options, please see this official PHP documentation page.

19.4. Create a new database and user for the mail server

Log in to phpMyAdmin and create a new database called mail and a new user with the same name, on localhost. Enter a strong password for the user mail and copy it somewhere safe, to use it later. Then give user mail all the privileges over the mail database, except ‘grant’, which is not necessary.

19.5. Create a user to handle virtual mail directories

The mail server will use special ‘virtual’ users, which are not regular users or system users. The emails will be stored in subdirectories of the /var/vmail directory, per domain and per email account. For example, admin@example.com will have the mail directory /var/vmail/example.com/admin. All of these mail directories will be owned by a single user called vmail, and the IMAP server, Dovecot, will use the vmail user to create and update the mail files.

First create the vmail directory:

mkdir /var/vmail

Then create the vmail user:

useradd -u 5000 -d /var/vmail -s /bin/false vmail

The meaning of the options used above is the following:

-u 5000: set 5000 as the user id (UID)

-d /var/vmail: set /var/vmail as home directory for the vmail user

-s /bin/false: add a user without shell access

When creating a new user without specifying the group name or the group id, by default, a group with the same name as the username is created, and the group id (GID) of the new group is the same as the user id (UID). Therefore, the command from above creates the vmail user with the UID 5000, and the vmail group with the GID 5000.

Change ownership for the /var/vmail directory:

chown vmail:vmail /var/vmail

19.6. Install Postfix

To install Postfix run:

apt-get install postfix postfix-mysql

When Postfix installation begins, you will be asked to choose a general type of mail configuration:

Select Internet site, then press Enter.

In the next screen you will be asked to enter the ‘system mail name’:

The ‘system mail name’ should be the domain appended to email addresses that don’t have a domain name specified. To allow other applications, like Mailman, to function properly, you should enter here the fully qualified domain name (FQDN) of your server (mail.example.com). So, the ‘system mail name’ will be mail.example.com, where example.com is the main domain hosted on your server. Enter mail.example.com and press Enter. You can change the ‘system mail name’ later by manually editing the /etc/mailname file.

The Postfix server will be installed and automatically started. To check the Postfix version run:

postconf mail_version

The output will look like this:

mail_version = 3.5.6

19.7. Configure Postfix

Navigate to /etc/postfix:

cd /etc/postfix

Create the following files with the following content in /etc/postfix (replace as6d516a51df6asdf with the actual password of the user mail, that you set up earlier):

nano mysql-sender-login-maps.cf
hosts = localhost
user = mail
password = as6d516a51df6asdf
dbname = mail
query = SELECT username AS allowedUser FROM mailbox WHERE username="%s" AND active = 1 UNION SELECT goto FROM alias WHERE address="%s" AND active = 1
nano mysql-virtual-alias-maps.cf
hosts = localhost
user = mail
password = as6d516a51df6asdf
dbname = mail
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'
nano mysql-virtual-domains-maps.cf
hosts = localhost
user = mail
password = as6d516a51df6asdf
dbname = mail
query = SELECT domain FROM domain WHERE domain='%s' AND active = '1'
nano mysql-virtual-mailbox-maps.cf
hosts = localhost
user = mail
password = as6d516a51df6asdf
dbname = mail
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'

Change permissions as follows:

chmod 640 /etc/postfix/mysql-*

19.7.1. Edit main.cf

Make a copy of the original /etc/postfix/main.cf file:

cd /etc/postfix
cp main.cf main.cf_orig

Then delete all the content inside main.cf and open it for editing:

cat /dev/null > main.cf
nano main.cf

Add the following content replacing the values in red with your own values. Some parameters that are commented out for the moment will be enabled later on, when you’ll install additional applications to which those parameters refer:

# See /usr/share/postfix/main.cf.dist for a commented, more complete version

# Debian specific:  Specifying a file name will cause the first
# line of that file to be used as the name.  The Debian default
# is /etc/mailname.

compatibility_level=2

inet_protocols = ipv4, ipv6
smtp_bind_address6 = 2b03:8df0:a24b:6eb::1
smtp_address_preference = ipv4

smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

readme_directory = no

# TLS parameters
smtpd_tls_key_file=/etc/letsencrypt/live/mail.example.com/privkey.pem
smtpd_tls_cert_file=/etc/letsencrypt/live/mail.example.com/cert.pem
smtpd_tls_CAfile=/etc/letsencrypt/live/mail.example.com/fullchain.pem

smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_tls_security_level = may

smtp_tls_security_level = may
smtpd_tls_loglevel = 2

# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.

myhostname = mail.example.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = localhost

mynetworks = 127.0.0.0/8 [::1]/128 [2b03:8df0:a24b:6eb::]/64
mailbox_size_limit = 0
message_size_limit = 62914560
recipient_delimiter = +
inet_interfaces = all

# a bit more spam protection
disable_vrfy_command = yes

# Auth
# Allow plaintext mechanisms, but only over a TLS-encrypted connection
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous

smtpd_sasl_type=dovecot
smtpd_sasl_path=private/auth_dovecot
smtpd_sasl_auth_enable = yes
smtpd_sasl_authenticated_header = yes
broken_sasl_auth_clients = yes

proxy_read_maps = $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks $smtpd_sender_login_maps

smtpd_sender_login_maps = proxy:mysql:/etc/postfix/mysql-sender-login-maps.cf

#mailman
#relay_domains = mailman.example.com
#relay_recipient_maps = hash:/var/lib/mailman/data/virtual-mailman
#transport_maps = hash:/etc/postfix/transport
#mailman_destination_recipient_limit = 1

# Virtual mailboxes
virtual_alias_maps = proxy:mysql:/etc/postfix/mysql-virtual-alias-maps.cf
#virtual_alias_maps = proxy:mysql:/etc/postfix/mysql-virtual-alias-maps.cf,
#                     hash:/var/lib/mailman/data/virtual-mailman
virtual_mailbox_base = /var/vmail/
virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql-virtual-domains-maps.cf
virtual_mailbox_limit = 0
virtual_mailbox_maps = proxy:mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_minimum_uid = 104
virtual_transport = lmtp:unix:private/dovecot-lmtp
local_transport = virtual
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000

milter_protocol = 2
milter_default_action = accept

#policyd-spf_time_limit = 3600

smtpd_helo_required = yes
disable_vrfy_command = yes
strict_rfc821_envelopes = yes
invalid_hostname_reject_code = 554
multi_recipient_bounce_reject_code = 554
non_fqdn_reject_code = 554
relay_domains_reject_code = 554
unknown_address_reject_code = 554
unknown_client_reject_code = 554
unknown_hostname_reject_code = 554
unknown_local_recipient_reject_code = 554
unknown_relay_recipient_reject_code = 554
unknown_virtual_alias_reject_code = 554
unknown_virtual_mailbox_reject_code = 554
unverified_recipient_reject_code = 554
unverified_sender_reject_code = 554

#authorized_submit_users = !sshusername, !ftpusername, vmail, root, www-data, asterisk

smtpd_recipient_restrictions =
#   check_client_access hash:/etc/postfix/client_checks,
   reject_sender_login_mismatch,
   permit_mynetworks,
   permit_sasl_authenticated,
   reject_invalid_hostname,
   reject_unknown_recipient_domain,
   reject_unknown_sender_domain,
   reject_unauth_destination,
   reject_unauth_pipelining,
   reject_unknown_reverse_client_hostname,
   check_policy_service unix:private/policyd-spf,
   reject_rbl_client zen.spamhaus.org,
#   check_policy_service inet:localhost:60000,
   permit

smtpd_relay_restrictions =
   reject_invalid_hostname,
   reject_unknown_recipient_domain,
   reject_unauth_pipelining,
   permit_mynetworks, permit_sasl_authenticated,
   reject_unauth_destination,
   permit

header_checks = regexp:/etc/postfix/header_checks
mime_header_checks = regexp:/etc/postfix/header_checks

Replace example.com with the main domain hosted on your server. Replace sshusername with your SSH user and ftpusername with your FTP user. They won’t be allowed to send emails because they don’t need to. Also replace 2b03:8df0:a24b:6eb:: with the /64 subnet allocated to your server by your hosting provider (if your server hasn’t been allocated a /64 subnet from the start, you will need to contact technical support and ask that they allocate a /64 subnet to your machine, so that you can enable IPv6 connectivity for your mail server). Replace 2b03:8df0:a24b:6eb::1 with the specific IPv6 that you configured in /etc/network/interfaces (it should belong to the 2b03:8df0:a24b:6eb::/64 subnet mentioned above).

If your hosting provider allocated from the start a /128 subnet to your server, which means a single IPv6 address, and after you open a support ticket they refuse to change the IPv6 allocation from /128 to /64, it’s recommended to disable IPv6 connectivity for the mail server by replacing inet_protocols = ipv4, ipv6 with inet_protocols = ipv4 and restarting Postfix. spamhaus.org uses to blacklist an entire /64 subnet of addresses when one single IPv6 address in that range sends spam. This is why, your IPv6 address can get blacklisted just because neighboring IPs in the same range, which belong to other clients, send spam. In this way, if the receiving mail server is configured to reject emails from IPs blacklisted by spamhaus.org and your server sends an email using the IPv6 address, your email will be rejected. Therefore, in this case in which the server is allocated a /128 subnet and the hosting provider refuses to change the allocation type to a /64 subnet, to avoid the situation described above, it’s recommended to disable IPv6 connectivity for the mail server and use the IPv4 address exclussively, which is sufficient for effective email communication.

Please note that the message_size_limit = 62914560 directive sets a maximum size limit of 62914560 bytes (75MB) to any email (email text plus the attachments). This doesn’t mean that you can send emails up to 75MB in size to other email providers. Unfortunately, many mail servers have a maximum attachment size limit of 25MB, while others have a limit of 50MB. So, to avoid the situation in which the emails sent from your server get rejected by other mail servers because of their size, it’s recommended to keep the size of all your attachments below 25MB, or 50MB if you know that the recipient’s email provider accepts attachments of up to 50MB.

Next, create the /etc/postfix/header_checks file, to remove some sensitive information from the header of outgoing emails:

nano /etc/postfix/header_checks

Add the following content inside this file:

/^Received:/                IGNORE
/^X-Originating-IP:/        IGNORE
/^X-Mailer:/                IGNORE
/^User-Agent:/              IGNORE
/^X-Spam-Status:/           IGNORE
/^X-Spam-Checker-Version:/  IGNORE

Then use the postmap command to create the Postfix lookup table file for the header_checks file:

postmap header_checks

The lines included in the /etc/postfix/header_checks file are meant to prevent the mail server to include certain information in the header of emails, to increase security.

Then restart Postfix:

systemctl restart postfix

The authorized_submit_users = !sshusername, !ftpusername, vmail, root, www-data, asterisk directive allows the vmail, root, www-data and asterisk users to send emails by running PHP scripts containing the mail() function; it also allows them to send emails in command line by running commands like:

echo -e "Subject: Testing \n\n Hello! This is the body of the email." | /usr/sbin/sendmail contact@domain.com

It also denies the right to send emails for the sshusername and ftpusername users.

‘phpList’, that we’ll describe later, won’t be configured to use the PHP mail() function. It will use the PHPMailer class which it includes, to connect by TLS on port 587 to the SMTP server which is Postfix. Therefore, it won’t need to send emails using the mail() function. Nevertheless, many WordPress plugins, such as various contact forms, need to send emails using the mail() function, as the www-data user. So, the www-data user has to be allowed to send emails in the authorized_submit_users parameter.

If you have other users that you want to prevent from sending emails from the server in command line or using the mail() function, just add them like this:

authorized_submit_users = !sshusername, !ftpusername, !user1, !user2, vmail, root, www-data, asterisk

The user root is allowed to send emails because ‘System Health and Security Probe’ needs this to send periodic email reports to the system administrator, as we’ll show later. The vmail user is allowed to send emails because Roundcube’s auto-responder needs this to send ‘out of office’ replies. The user asterisk is allowed to send emails because Asterisk needs it in order to send voicemail messages.

19.7.2. Edit master.cf

Next, make a copy of the original /etc/postfix/master.cf file:

cp /etc/postfix/master.cf /etc/postfix/master.cf_orig

Delete all the content in /etc/postfix/master.cf:

cat /dev/null > /etc/postfix/master.cf

Open /etc/postfix/master.cf:

nano /etc/postfix/master.cf

Add the following content inside this file. Please note that some lines that have been commented out will be enabled later, when we’ll install additional software:

#
# Postfix master process configuration file.  For details on the format
# of the file, see the master(5) manual page (command: "man 5 master").
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
smtp      inet  n       -       -       -       -       smtpd
#smtp      inet  n       -       -       -       1       postscreen
#smtpd     pass  -       -       -       -       -       smtpd
#dnsblog   unix  -       -       -       -       0       dnsblog
#tlsproxy  unix  -       -       -       -       0       tlsproxy
submission inet n       -       -       -       -       smtpd
# -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING
smtps     inet  n       -       -       -       -       smtpd
#  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
#  -o smtpd_sasl_auth_enable=yes
#  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
#628       inet  n       -       -       -       -       qmqpd
pickup    fifo  n       -       -       60      1       pickup
cleanup   unix  n       -       -       -       0       cleanup
qmgr      fifo  n       -       n       300     1       qmgr
#qmgr     fifo  n       -       n       300     1       oqmgr
tlsmgr    unix  -       -       -       1000?   1       tlsmgr
rewrite   unix  -       -       -       -       -       trivial-rewrite
bounce    unix  -       -       -       -       0       bounce
defer     unix  -       -       -       -       0       bounce
trace     unix  -       -       -       -       0       bounce
verify    unix  -       -       -       -       1       verify
flush     unix  n       -       -       1000?   0       flush
proxymap  unix  -       -       n       -       -       proxymap
proxywrite unix -       -       n       -       1       proxymap
smtp      unix  -       -       -       -       -       smtp
relay     unix  -       -       -       -       -       smtp
#       -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
showq     unix  n       -       -       -       -       showq
error     unix  -       -       -       -       -       error
retry     unix  -       -       -       -       -       error
discard   unix  -       -       -       -       -       discard
local     unix  -       n       n       -       -       local
virtual   unix  -       n       n       -       -       virtual
lmtp      unix  -       -       -       -       -       lmtp
anvil     unix  -       -       -       -       1       anvil
scache    unix  -       -       -       -       1       scache
#
# ====================================================================
# Interfaces to non-Postfix software. Be sure to examine the manual
# pages of the non-Postfix software to find out what options it wants.
#
# Many of the following services use the Postfix pipe(8) delivery
# agent.  See the pipe(8) man page for information about ${recipient}
# and other message envelope options.
# ====================================================================
#
# maildrop. See the Postfix MAILDROP_README file for details.
# Also specify in main.cf: maildrop_destination_recipient_limit=1
#
maildrop  unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
#
# ====================================================================
#
# Recent Cyrus versions can use the existing "lmtp" master.cf entry.
#
# Specify in cyrus.conf:
#   lmtp    cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
#
# Specify in main.cf one or more of the following:
#  mailbox_transport = lmtp:inet:localhost
#  virtual_transport = lmtp:inet:localhost
#
# ====================================================================
#
# Cyrus 2.1.5 (Amos Gouaux)
# Also specify in main.cf: cyrus_destination_recipient_limit=1
#
#cyrus     unix  -       n       n       -       -       pipe
#  user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
#
# ====================================================================
# Old example of delivery via Cyrus.
#
#old-cyrus unix  -       n       n       -       -       pipe
#  flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
#
# ====================================================================
#
# See the Postfix UUCP_README file for configuration details.
#
uucp      unix  -       n       n       -       -       pipe
  flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
#
# Other external delivery methods.
#
ifmail    unix  -       n       n       -       -       pipe
  flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp     unix  -       n       n       -       -       pipe
  flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix  -       n       n       -       2       pipe
  flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}

#dovecot   unix  -       n       n       -       -       pipe
#  flags=DRhu user=vmail:vmail argv=/usr/bin/spamc -u debian-spamd -e /usr/lib/dovecot/deliver -d ${recipient}

#policyd-spf  unix  -       n       n       -       0       spawn
#    user=policyd-spf argv=/usr/bin/policyd-spf

#mailman unix  -       n       n       -       -       pipe
#  flags=FR user=list:list argv=/var/lib/mailman/bin/postfix-to-mailman.py ${nexthop} ${recipient}

Change ownership for all the files and directories inside the /etc/postfix directory like this:

chown -R root:postfix /etc/postfix/*

Check Postfix configuration:

postfix check

If you see this warning:

postfix/postfix-script: warning: symlink leaves directory: /etc/postfix/./makedefs.out

you can safely ignore it, because it is harmless. It just mentions that /etc/postfix/makedefs.out is a symlink that points to a directory outside of the /etc/postfix directory, namely /usr/share/postfix/makedefs.out

Please note that at this point, Postfix is not ready to send test emails.

19.8. Configure Fail2ban to protect Postfix against brute-force attacks

To configure Fail2ban to protect Postfix against brute-force attacks open the /etc/fail2ban/jail.local file:

nano /etc/fail2ban/jail.local

Search for the [postfix] block, comment out all the lines in this block and add other lines, as follows:

[postfix]
# To use another modes set filter parameter "mode" in jail.local:
#mode    = more
#port    = smtp,465,submission
#logpath = %(postfix_log)s
#backend = %(postfix_backend)s

enabled  = true
port     = 25,465,submission
filter   = postfix
logpath  = /var/log/mail.log
findtime = 10800
maxretry  = 4
bantime = 2419200

Then search for the [postfix-sasl] block, comment out all the lines in this block and add other lines, as follows:

[postfix-sasl]
#filter   = postfix[mode=auth]
#port     = smtp,465,submission,imap,imaps,pop3,pop3s

#logpath  = %(postfix_log)s
#backend  = %(postfix_backend)s

enabled   = true
port      = 25,465,143,993,110,995,submission
filter    = postfix-sasl
logpath   = /var/log/mail.log
findtime = 10800
maxretry  = 2
bantime = 604800

Create the /etc/fail2ban/filter.d/postfix-sasl.conf file, so that Fail2ban can recognize all the attacks against the postfix-sasl jail:

nano /etc/fail2ban/filter.d/postfix-sasl.conf

Add the following content inside this file:

# Fail2Ban filter for postfix authentication failures
#

[INCLUDES]

before = common.conf

[Definition]

_daemon = postfix(-\w+)?/(?:submission/|smtps/)?smtp[ds]

failregex = warning: [-._\w]+\[<HOST>\]: SASL LOGIN authentication failed
            warning: unknown\[<HOST>\]: SASL PLAIN authentication failed
            RCPT from unknown\[<HOST>\]: .* Relay access denied
            ^%(__prefix_line)swarning: [-._\w]+\[<HOST>\]: SASL ((?i)LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(:[ A-Za-z0-9+/:]*={0,2})?\s*$
            warning: hostname .* does not resolve to address\[<HOST>\]: Name or service not known
ignoreregex = authentication failed: Connection lost to authentication server$

[Init]

journalmatch = _SYSTEMD_UNIT=postfix.service

# Author: Yaroslav Halchenko

Reload Fail2ban:

systemctl reload fail2ban

19.9. Upgrading Postfix

Since Postfix has been installed from the official Debian repository, to upgrade it, all you need to do is to run apt-get update && apt-get dist-upgrade with a specific frequency, as described in the Maintenance steps chapter. This command will upgrade Postfix if there is a new version available. During these upgrades, the configuration changes implemented as described above, will be preserved.

19.10. Install Dovecot

To install Dovecot run:

apt-get install dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-mysql

19.10.1. Configure Dovecot

Make a copy of the /etc/dovecot/dovecot.conf file:

cp /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf_orig

Empty dovecot.conf:

cat /dev/null > /etc/dovecot/dovecot.conf

Edit dovecot.conf:

nano /etc/dovecot/dovecot.conf

Add the following content inside this file:

# Enable installed protocols
!include_try /usr/share/dovecot/protocols.d/*.protocol

listen = *,[::]

log_path = /var/log/dovecot.log
auth_mechanisms = cram-md5
disable_plaintext_auth = yes

log_timestamp = "%Y-%m-%d %H:%M:%S "

passdb {
  args = /etc/dovecot/dovecot-mysql.conf
  driver = sql
}

protocols = imap pop3 lmtp sieve

service auth {
  unix_listener /var/spool/postfix/private/auth_dovecot {
    group = postfix
    mode = 0660
    user = postfix
  }
  unix_listener auth-master {
    mode = 0600
    user = vmail
  }
  user = root
}

ssl = required
ssl_min_protocol = TLSv1.2
ssl_client_ca_dir = /etc/ssl/certs
ssl_dh = </etc/dovecot/dh.pem

userdb {
  args = /etc/dovecot/dovecot-mysql.conf
  driver = sql
}

protocol pop3 {
  pop3_uidl_format = %08Xu%08Xv
  pop3_client_workarounds = oe-ns-eoh
}

protocol lda {
  postmaster_address = admin@example.com
  mail_plugins = $mail_plugins sieve
  mail_location = maildir:/var/vmail/%d/%n/
  mail_home = /var/vmail/%d/%n/
}

protocol lmtp {
  postmaster_address = admin@example.com
  mail_plugins = $mail_plugins sieve
  log_path = /var/log/dovecot-lmtp-errors.log
  mail_location = maildir:/var/vmail/%d/%n/
  mail_home = /var/vmail/%d/%n/
}

plugin {
  mail_home = /var/vmail/%d/%n/
  sieve = /var/vmail/%d/%n/dovecot.sieve
  sieve_dir = /var/vmail/%d/%n/
  sieve_after = /etc/dovecot/conf.d/custom-sieves/global_after.sieve
}

# Sieve configuration
protocol sieve {
  managesieve_implementation_string = dovecot
  log_path = /var/log/dovecot-sieve-errors.log
}

service managesieve-login {
  inet_listener sieve {
      port = 4190
  }
}

!include conf.d/*.conf
!include_try local.conf
!include_try ssl-keys.conf

Replace example.com with the main domain hosted on your server and admin@example.com with an email address that you want to use as postmaster address.

Create a separate file for the default SSL certificate and private key parameters:

nano /etc/dovecot/ssl-keys.conf

Enter the following content inside this file:

ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.example.com/privkey.pem

Replace example.com with the main domain hosted on your server. Change permissions for this file:

chown root:root /etc/dovecot/ssl-keys.conf
chmod 640 /etc/dovecot/ssl-keys.conf

Install the haveged package to increase the amount of available entropy of your server, which will be necessary when generating the Diffie-Hellman Parameters with openssl:

apt-get install haveged

Before generating the Diffie-Hellman Parameters, you can check the available entropy by running:

cat /proc/sys/kernel/random/entropy_avail

The entropy level should be above 2000 in order to allow rapid generation of random numbers. If it’s below 2000, you can perform various activities on the server, that would increase the entropy, like running the apt-get update, apt-get dist-upgrade or htop commands.

If the entropy level is above 2000, generate the Diffie-Hellman Parameters file by running:

openssl dhparam -out /etc/dovecot/dh.pem 4096

The process of generating the dh.pem file with the command from above can take even 45 minutes on a server with 1 CPU core, and 2 GB of RAM.

Since you included the ssl directives (ssl, ssl_min_protocol, ssl_client_ca_dir, ssl_dh, ssl_key, ssl_cert) in this main configuration file, there is no need to include them again in the /etc/dovecot/conf.d/10-ssl.conf file. So, first make a copy of this file:

cp /etc/dovecot/conf.d/10-ssl.conf /etc/dovecot/conf.d/10-ssl.conf_orig

Then open the file for editing:

nano /etc/dovecot/conf.d/10-ssl.conf

Comment out all the parameters that are not commented out in this file.

Since you specified auth_mechanisms = cram-md5 in the /etc/dovecot/dovecot.conf configuration file, there is no need to specify it in /etc/dovecot/conf.d/10-auth.conf, so, first make a copy of this file:

cp /etc/dovecot/conf.d/10-auth.conf /etc/dovecot/conf.d/10-auth.conf_orig

Open the file for editing:

nano /etc/dovecot/conf.d/10-auth.conf

Comment out the auth_mechanisms parameter, to make it look like this:

#auth_mechanisms = plain

The only uncommented line in this file should be:

!include auth-system.conf.ext

Since you mentioned mail_location = maildir:/var/vmail/%d/%n/ in the /etc/dovecot/dovecot.conf file, you need to comment out the mail_location parameter in the /etc/dovecot/conf.d/10-mail.conf file. First make a copy of the original file:

cp /etc/dovecot/conf.d/10-mail.conf /etc/dovecot/conf.d/10-mail.conf_orig

Then open the file for editing:

nano /etc/dovecot/conf.d/10-mail.conf

Comment out the mail_location line to make it look like this:

#mail_location = mbox:~/mail:INBOX=/var/mail/%u

Also, edit the mail_privileged_group parameter, to mke it look like this:

mail_privileged_group = vmail

Ths file should also contain the following lines:

namespace inbox {
  inbox = yes
}

Next, make a copy of the /etc/dovecot/conf.d/10-master.conf file:

cp /etc/dovecot/conf.d/10-master.conf /etc/dovecot/conf.d/10-master.conf_orig

Open the /etc/dovecot/conf.d/10-master.conf file:

nano /etc/dovecot/conf.d/10-master.conf

It should have the following content (pay attention to the lines in blue):

#default_process_limit = 100
#default_client_limit = 1000
# Default VSZ (virtual memory size) limit for service processes. This is mainly
# intended to catch and kill processes that leak memory before they eat up
# everything.
#default_vsz_limit = 256M
# Login user is internally used by login processes. This is the most untrusted
# user in Dovecot system. It shouldn't have access to anything at all.
#default_login_user = dovenull
# Internal user is used by unprivileged processes. It should be separate from
# login user, so that login processes can't disturb other processes.
#default_internal_user = dovecot

service imap-login {
  inet_listener imap {
    #port = 143
  }
  inet_listener imaps {
    port = 993
    ssl = yes
  }

  # Number of connections to handle before starting a new process. Typically
  # the only useful values are 0 (unlimited) or 1. 1 is more secure, but 0
  # is faster. <doc/wiki/LoginProcess.txt>
  #service_count = 1
  # Number of processes to always keep waiting for more connections.
  #process_min_avail = 0
  # If you set service_count=0, you probably need to grow this.
  #vsz_limit = $default_vsz_limit
}
service pop3-login {
  inet_listener pop3 {
    #port = 110
     port = 0
  }
  inet_listener pop3s {
    port = 995
    ssl = yes
  }
}

#service submission-login {
#  inet_listener submission {
    #port = 587
#  }
#}

service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    #mode = 0666
    user = postfix
    group = postfix
    mode = 0600
  }
  # Create inet listener only if you can't use the above UNIX socket
  #inet_listener lmtp {
    # Avoid making LMTP visible for the entire internet
    #address =
    #port =
  #}
}
service imap {
  # Most of the memory goes to mmap()ing files. You may need to increase this
  # limit if you have huge mailboxes.
  #vsz_limit = $default_vsz_limit

  # Max. number of IMAP processes (connections)
  #process_limit = 1024
}
service pop3 {
  # Max. number of POP3 processes (connections)
  #process_limit = 1024
}

#service submission {
  # Max. number of SMTP Submission processes (connections)
  #process_limit = 1024
#}

#service auth {
  # auth_socket_path points to this userdb socket by default. It's typically
  # used by dovecot-lda, doveadm, possibly imap process, etc. Users that have
  # full permissions to this socket are able to get a list of all usernames and
  # get the results of everyone's userdb lookups.
  #
  # The default 0666 mode allows anyone to connect to the socket, but the
  # userdb lookups will succeed only if the userdb returns an "uid" field that
  # matches the caller process's UID. Also if caller's uid or gid matches the
  # socket's uid or gid the lookup succeeds. Anything else causes a failure.
  #
  # To give the caller full permissions to lookup all users, set the mode to
  # something else than 0666 and Dovecot lets the kernel enforce the
  # permissions (e.g. 0777 allows everyone full permissions).
#  unix_listener auth-userdb {
#    mode = 0666
#    user = 
#    group = 
#  }


  # Postfix smtp-auth
  #unix_listener /var/spool/postfix/private/auth {
  #  mode = 0666
  #}

  # Auth process is run as this user.
  #user = $default_internal_user
#}

service auth-worker {
  # Auth worker process is run as root by default, so that it can access
  # /etc/shadow. If this isn't necessary, the user should be changed to
  # $default_internal_user.
  #user = root
  user = vmail
}

service dict {
  # If dict proxy is used, mail processes should have access to its socket.
  # For example: mode=0660, group=vmail and global mail_access_groups=vmail
  unix_listener dict {
    #mode = 0600
    #user =
    #group =
    user = vmail
  }
}

Next, navigate to /etc/dovecot:

cd /etc/dovecot

Create a file called dovecot-mysql.conf:

nano dovecot-mysql.conf

Add the following content inside this file:

driver = mysql
connect = host=localhost dbname=mail user=mail password=as6d516a51df6asdf
default_pass_scheme = CRAM-MD5
password_query = SELECT password FROM mailbox WHERE username = '%u'
user_query = SELECT CONCAT('maildir:/var/vmail/',maildir) AS mail, 5000 AS uid, 5000 AS gid FROM mailbox WHERE username = '%u'

Replace as6d516a51df6asdf with the actual password of the user mail, that you set up earlier.

Change ownership and permissions:

chown root:vmail dovecot-mysql.conf
chmod 640 dovecot-mysql.conf

You will also have to add the user www-data to the group dovecot, so that www-data has read and write access to the /run/dovecot/stats-writer file, which is necessary for Postfix Admin:

adduser www-data dovecot

19.10.2. Configure Fail2ban to protect Dovecot against brute-force attacks

To configure Fail2ban to protect Dovecot against brute-force attacks open the /etc/fail2ban/jail.local file:

nano /etc/fail2ban/jail.local

Search for the [dovecot] block, comment out all the lines in this block and add other lines, as follows:

[dovecot]
#port    = pop3,pop3s,imap,imaps,submission,465,sieve
#logpath = %(dovecot_log)s
#backend = %(dovecot_backend)s

enabled = true
port    = 465,143,993,110,995,submission,sieve
filter  = dovecot
logpath = /var/log/mail.log
maxretry = 5
bantime = 604800

Reload Fail2ban:

systemctl reload fail2ban

19.10.3. Configure logrotate to rotate Dovecot logs

Create a new file called dovecot inside the /etc/logrotate.d directory:

nano /etc/logrotate.d/dovecot

Add the following content inside this file:

/var/log/dovecot*.log {
  missingok
  rotate 4
  compress
  notifempty
  delaycompress
  size 2M
  sharedscripts
  postrotate
    /bin/kill -USR1 `cat /var/run/dovecot/master.pid 2>/dev/null` 2> /dev/null || true
  endscript
}

Before restarting Dovecot, you will have to install a few more packages, as described below.

19.10.4. Set up Sieve Mail Filtering

Install the dovecot-sieve and dovecot-managesieved packages:

apt-get install dovecot-sieve dovecot-managesieved

Since you entered the sieve configuration settings in the /etc/dovecot/dovecot.conf file, you have to comment out the corresponding configuration lines that exist by default in the /etc/dovecot/conf.d/90-sieve.conf file. First make a copy of the original file:

cp /etc/dovecot/conf.d/90-sieve.conf /etc/dovecot/conf.d/90-sieve.conf_orig

Then open the file:

nano /etc/dovecot/conf.d/90-sieve.conf

Comment out the following three lines, like this:

#plugin {
....

#  sieve = file:~/sieve;active=~/.dovecot.sieve
....

#}

All the lines in /etc/dovecot/conf.d/90-sieve.conf should be commented out.

Please note that installing dovecot-sieve without SpamAssassin is not enough to enable Dovecot spam filtering. Only after installing SpamAssassin, as we describe further down below, the spam filtering functionality will work.

The dovecot-sieve plugin has many other uses apart from spam filtering. It can be used to set up custom filters, so that incoming emails will go to certain folders, if they contain specific words in the Subject line, in the email body, etc.

19.10.4.1. Add global rules for sieve filtering

When you will install Roundcube, each user will be able to add their personal custom filter rules in the ‘Settings’ > ‘Filters’ section of their account. However, you will need to add global sieve filter rules that will apply to all mailboxes, in addition to the individual custom rules. We’ll describe below how to add a global rule for spam filtering, so that after you install SpamAssassin, all the incoming emails recognized as spam will be sent to the ‘Junk’ folder.

Create the global sieve rules directory and switch to it:

mkdir /etc/dovecot/conf.d/custom-sieves
cd /etc/dovecot/conf.d/custom-sieves

Create the global sieve rules file:

nano global_after.sieve

Add the following content inside this file:

require ["fileinto"];
if allof (header :contains "X-Spam-Flag" "YES")
{
        fileinto "Junk";
}

This rule sends spam emails to the ‘Junk’ folder automatically, when the X-Spam-Flag header contains the string YES . The X-Spam-Flag header will be added by SpamAssassin to each incoming email.

Compile sieve rules by running:

sievec global_after.sieve

This command will create the compiled file global_after.svbin.

Verify again that in the /etc/dovecot/dovecot.conf file, in the plugin block, you have included the sieve_after parameter, as follows:

plugin {
  mail_home = /var/vmail/%d/%n/
  sieve = /var/vmail/%d/%n/dovecot.sieve
  sieve_dir = /var/vmail/%d/%n/
  sieve_after = /etc/dovecot/conf.d/custom-sieves/global_after.sieve
}

To be able to create custom filters using the web interface, you will need to install Roundcube and the managesieve Roundcube plugin, as we describe further down below.

Finally, you can restart Dovecot for all the configuration changes to take effect:

systemctl restart dovecot

You can test if Dovecot sieve is working by running:

telnet localhost 4190

The output should be similar to this:

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
"IMPLEMENTATION" "dovecot"
"SIEVE" "fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext"
"NOTIFY" "mailto"
"SASL" "CRAM-MD5"
"STARTTLS"
"VERSION" "1.0"
OK "Dovecot (Debian) ready."

To end the telnet session press Ctrl + ] and then type quit and press Enter.

19.10.5 Upgrading Dovecot

Since Dovecot has been installed from the official Debian repository, to upgrade it, all you need to do is to run apt-get update && apt-get dist-upgrade with a specific frequency, as described in the Maintenance steps chapter. This command will upgrade Dovecot if there is a new version available. During these upgrades, the configuration changes implemented as described above, will be preserved.

19.11. Install Postfix Admin

As mentioned above, Postfix Admin is needed to manage email domains, email addresses, aliases, user quota etc., in a time-effective manner. You can find the latest release of Postfix Admin on the official GitHub Releases page. If you right-click on the zip archive of the latest version and choose ‘Copy Link Location’, you can copy the URL of the archive to clipboard.

First download the archive of the latest version to the /tmp directory, uncompress it, move it into a subdirectory of the /var/www directory and change ownership and permissions for all its files and folders, as follows:

cd /tmp
wget https://github.com/postfixadmin/postfixadmin/archive/postfixadmin-3.2.4.zip

unzip postfixadmin-3.2.4.zip
rm postfixadmin-3.2.4.zip
mv postfixadmin-postfixadmin-3.2.4 /var/www/net-pstfxdmn

Replace 3.2.4 with your version number. Replace net-pstfxdmn with a custom name, that will be also the name of the Postfix Admin login page. Choose a name that is easy to remember for you, but difficult to guess for an attacker. Definitely something different than postfixadmin, or postfixadmin-login, etc. Change ownership and permissions for Postfix Admin files and directories:

chown -R www-data:www-data /var/www/net-pstfxdmn
find /var/www/net-pstfxdmn -type d -exec chmod 750 {} +
find /var/www/net-pstfxdmn -type f -exec chmod 640 {} +

Next create the directory templates_c inside /var/www/net-pstfxdmn and set the right permissions and ownership, otherwise accessing the setup page will generate an error:

cd /var/www/net-pstfxdmn
mkdir templates_c
chown -R www-data:www-data templates_c
chmod 750 templates_c

Then copy the config.inc.php file to config.local.php and change ownership and permissions:

cp config.inc.php config.local.php
chown www-data:www-data config.local.php
chmod 640 config.local.php

Open the config.local.php file for editing:

nano config.local.php

Search for the following line:

// error message from $PALANG (see languages/*) will be displayed.

Remove the * sign after languages/, because it will cause a big portion of the lines that follow, to be considered as PHP comments, which is not the case. Then, set the following parameters as follows:

$CONF['configured'] = true;
$CONF['database_type'] = 'mysqli';
$CONF['database_host'] = 'localhost';
$CONF['database_user'] = 'mail';
$CONF['database_password'] = 'as6d516a51df6asdf';
$CONF['database_name'] = 'mail';
$CONF['admin_email'] = 'admin@example.com';
$CONF['smtp_server'] = 'mail.example.com';
$CONF['smtp_client'] = 'mail.example.com';
$CONF['encrypt'] = 'dovecot:CRAM-MD5';
$CONF['dovecotpw'] = "/usr/bin/doveadm pw";

Replace example.com with the main domain hosted on your server, as6d516a51df6asdf with the actual password of the user mail that you set up earlier, and admin@example.com with the email address that you want to use for superadmin.

Comment out the following lines, to make them look like this:

//if(@file_exists('/usr/bin/doveadm')) { // @ to silence openbase_dir stuff; see https://github.com/postfixadmin/postfixadmin/issues/171
//    $CONF['dovecotpw'] = "/usr/bin/doveadm pw"; # debian
//}

Also, set up the following parameters like this:

$CONF['default_aliases'] = array (
    'abuse' => 'admin@example.com',
    'hostmaster' => 'admin@example.com',
    'postmaster' => 'admin@example.com',
    'webmaster' => 'admin@example.com'
);

$CONF['domain_path'] = 'YES';
$CONF['domain_in_mailbox'] = 'NO';
$CONF['maildir_name_hook'] = 'NO';
$CONF['footer_text'] = 'Return to mail.example.com';
$CONF['footer_link'] = 'https://mail.example.com';

19.11.1. Configure Nginx for Postfix Admin

Open the /etc/nginx/sites-enabled/0-conf file for editing:

nano /etc/nginx/sites-enabled/0-conf

Add the following block inside the server block for mail.example.com, right below the block for phpMyAdmin (location /net-mrdblog { }):

    location /net-pstfxdmn {
        root /var/www;
        index index.php setup.php;
        auth_basic 'Restricted';
        auth_basic_user_file /etc/nginx/htpass/postfixadmin;
        location ~ ^/net-pstfxdmn/(.+.php)$ {
                       fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
                       fastcgi_param HTTPS on;
                       fastcgi_index index.php;
                       fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                       include /etc/nginx/fastcgi_params;
        }
    }

Replace net-pstfxdmn with your custom name for the Postfix Admin login page. Don’t forget to create the password file for HTTP authentication in order to restrict access to the login page. If you want to allow the user verner to access the Postfix Admin login page, run:

htpasswd -c /etc/nginx/htpass/postfixadmin verner

Change ownership and permissions for the new password file:

chown www-data:root /etc/nginx/htpass/postfixadmin
chmod 400 /etc/nginx/htpass/postfixadmin

Reload Nginx:

systemctl reload nginx

Next open up a web browser and access:

https://mail.example.com/net-pstfxdmn/public/setup.php

Replace example.com with your main domain and net-pstfxdmn with your custom Postfix Admin login page.

You will see a screen similar to this:

In the ‘Setup password’ field enter a setup password, enter it again in the ‘Setup password (again)’ field, copy it somewhere safe to use it later (you will also need it when upgrading Postfix Admin), then click on ‘Generate setup_password hash’.

A hash will be generated and displayed below the ‘Generate setup_password hash’ button, like this:

Copy the long string between quotes to the clipboard.

Open the config.local.php file:

nano /var/www/net-pstfxdmn/config.local.php

Search for $CONF['setup_password'] and add the hash that you copied earlier to it like this:

$CONF['setup_password'] = '$2y$10$Fafxog0Lt7KAIdd0H.BhiOeNFjBQq7uaPFc1xwyRtkj1zpJeKCmZa';

Return to the setup page and refresh the page. The setup script will perform some checks and will update the database, as seen below:

In the ‘Setup password’ field enter the setup password that you used earlier, in the ‘Admin’ field enter the superadmin’s email address, which can be admin@example.com, administrator@example.com, etc., in the ‘Password’ and ‘Password (again)’ fields enter a password for the superadmin, then click ‘Add Admin’.

From now on, you can log in to Postfix Admin with the superadmin email address and password that you have just set up, at:

https://mail.example.com/net-pstfxdmn/public

After logging in, you will see the following screen:

You can use Postfix Admin to:

add new email domains: ‘Domain List’ > ‘Add Domain’ > in the ‘Domain’ field enter the new domain, (Eg.: example.com), you can set ‘Aliases’, ‘Mailboxes’ and ‘Pass expires’ to 0 for unlimited aliases and mailboxes and no password expiration, you can leave everything else as it is, then click ‘Add Domain’.

add mailboxes to each domain: ‘Virtual List’ > ‘Add Mailbox’ > in the ‘Username’ field enter the part before @ of the new mailbox/email address, below the ‘Username’ field select the domain to which the new mailbox/email address will belong, in the ‘Password’ and ‘Password (again)’ fields enter the new password, you can leave the ‘Name’ field empty, you can also leave the ‘Quota’ field empty to assign unlimited storage space to the new mailbox, then click ‘Add Mailbox’.

add aliases for any email address: ‘Domain List’ > click on a domain > click on the ‘Add Alias’ button > in the ‘Alias’ field enter the part before @ of the new alias, in the ‘To’ field enter the complete email address to which the chosen alias is assigned (you can enter multiple email addresses, one per line), leave ‘Active’ checked, then click ‘Add Alias’. For example, if you have configured info@example.com as alias of office@example.com, all the emails sent to info@example.com will go to office@example.com.

After you first log in to Postfix Admin, add the mailbox of the superadmin: admin@example.com or administrator@example.com, etc., as a new mailbox for the example.com domain. Add other necessary mailboxes for your domain(s). For example contact@example.com, info@example.com, sales@example.com, support@example.com, etc.

Also, add root@mail.example.com as alias of the superadmin’s email address, admin@example.com for example, so that when an application installed on the server will send a notification email to the default root@mail.example.com, the email will reach the superadmin’s mailbox. (To add root@mail.example.com as an alias of admin@example.com you should first add mail.example.com as a new domain, in ‘Domain List’ > ‘New Domain’.)

After the installation, it’s a good idea to delete the setup.php file, although in the new Postfix Admin versions it’s not really necessary:

cd /var/www/net-pstfxdmn/public
rm setup.php

19.11.2. Move the configuration file outside the web root

To increase security, copy the config.local.php file to the /srv/scripts directory:

cp /var/www/net-pstfxdmn/config.local.php /srv/scripts/postfixadmin.php

Replace the content of the /var/www/net-pstfxdmn/config.local.php file like this:

cd /var/www/net-pstfxdmn
cat /dev/null > config.local.php
nano config.local.php

Add the following line inside this file:

<?php include(‘/srv/scripts/postfixadmin.php’); ?>

Change ownership and permissions for the /srv/scripts/postfixadmin.php file:

cd /srv/scripts
chown www-data:root postfixadmin.php
chmod 400 postfixadmin.php

19.11.3. Configure Fail2ban to protect Postfix Admin against brute-force attacks

Navigate to the /etc/fail2ban/filter.d directory:

cd /etc/fail2ban/filter.d

Create a new filter file for Postfix Admin:

nano postfixadmin.conf

Add the following content in this file:

[Definition]

failregex =  ^<HOST> .* \"POST /net-pstfxdmn/login.php HTTP/2.0\" 200 15.*$
             ^<HOST> .* \"GET /net-pstfxdmn HTTP/2.0\" 401 195 .*$
ignoreregex =

Replace net-pstfxdmn with your custom name of the Postfix Admin login page. The second line identifies the failed log in attempts against the HTTP authentication window, which is displayed right before loading the Postfix Admin login page, and can be also subject to brute-force attacks. Next, open the /etc/fail2ban/jail.local file:

nano /etc/fail2ban/jail.local

Add the following block above the [phpmyadmin] block:

[postfixadmin]
enabled  = true
filter   = postfixadmin
logpath  = /var/log/sites/mail.example.com/access.log           
port     = 80,443
findtime = 3600
maxretry = 4
bantime = 604800

Replace example.com with your main domain.

Reload Fail2ban:

systemctl reload fail2ban

19.11.4. Upgrading Postfix Admin

Before upgrading Postfix Admin to a new version, it’s recommended to verify if the new version has been tested and confirmed to function well within the suite of applications described in this guide. Once we test an application and confirm that it works well, we include it on this page.

If you check the official GitHub Releases page of Postfix Admin, and you notice that a new version has been released, you can upgrade Postfix Admin by following the steps from below.

First create the backups. Use phpMyAdmin to export the mail database and change the name of the resulting sql file, to make it include the date of the backup (Eg.: mail-2021-02-15.sql). Then create an archive of the Postfix Admin directory (/var/www/net-pstfxdmn in our example), including the date in the name of the archive, like this:

cd /var/bm_archives
tar czf postfixadmin-2021-02-15.tar.gz /var/www/net-pstfxdmn

Replace net-pstfxdmn with the custom name of the Postfix Admin directory, which is also the name of the login page.

Use a FTP client like FileZilla to upload the sql file to the remote server and place it in /var/bm_archives, and to download the tar.gz archive to your local computer, so as to have the two backup files in two separate locations. You can also copy them to a third location on an external storage device, etc.

Next, download the latest version of Postfix Admin from the official GitHub releases page and extract the archive:

cd /tmp
wget https://github.com/postfixadmin/postfixadmin/archive/postfixadmin-3.2.5.zip

unzip postfixadmin-3.2.5.zip
rm postfixadmin-3.2.5.zip

Replace 3.2.5 with your version.

Rename the Postfix Admin directory, /var/www/net-pstfxdmn to /var/www/net-pstfxdmn_old:

Move the new Postfix Admin uncompressed directory to /var/www, changing its name to /var/www/net-pstfxdmn:

mv /tmp/postfixadmin-postfixadmin-3.2.5 /var/www/net-pstfxdmn

Create an empty config.local.php file in the /var/www/net-pstfxdmn directory:

cd /var/www/net-pstfxdmn
nano config.local.php

Add the following line inside this file:

<?php include('/srv/scripts/postfixadmin.php'); ?>

Next create the directory templates_c in /var/www/net-pstfxdmn:

mkdir templates_c

Change ownership and permissions for the /var/www/net-pstfxdmn directory:

chown -R www-data:www-data /var/www/net-pstfxdmn
find /var/www/net-pstfxdmn -type d -exec chmod 750 {} +
find /var/www/net-pstfxdmn -type f -exec chmod 640 {} +

Then access the setup page by going to:

https://mail.example.com/net-pstfxdmn/public/setup.php

Replace example.com with your main domain, and net-pstfxdmn with the custom name of the Postfix Admin login page. You will see the following screen:

Enter the setup password that you saved when you installed Postfix Admin in the ‘Setup password’ field, then click on the ‘Login with setup_password’ button. The setup script will check the dependencies and also update the database. If everything is OK, the next screen will look like this:

Ignore the two warnings.

If you want to add a new superadmin account, you can do it by filling the text boxes under ‘Add Superadmin Account’. If you don’t want a new superadmin account, because you intend to use the old superadmin account(s), just leave the text boxes under ‘Add Superadmin Account’ empty. The upgrade is finished. You can access the login page at:

https://mail.example.com/net-pstfxdmn/public

In case the database upgrade fails, you can access https://mail.example.com/net-pstfxdmn/public/setup.php?debug=1 to see the last executed query.

After the installation finishes, it’s a good idea to delete the setup.php file:

cd /var/www/net-pstfxdmn/public
rm setup.php

After you log in to Postfix Admin and confirm that everything is working, you can delete the /var/www/net-pstfxdmn_old directory:

cd /var/www
rm -r net-pstfxdmn_old

19.12. Set up the MX records for your domains

After installing the components presented up to this point, you have to add the specific DNS records necessary to send/receive emails (the MX records), and the records necessary to legitimate your emails, so that they won’t be rejected as spam or considered forged emails (the SPF, DKIM and DMARC records).

The MX record for the example.com domain, as it should appear in the BIND configuration file for example.com (in the /etc/bind/db.example.com file) will look like this:

@      IN      MX     10    mail.example.com.

where @ is the placeholder for example.com , and 10 is the priority indicator (10 is the default priority). Please note that there is a period at the end of the mail.example.com domain.

The MX record from above has to be added for every domain on your server. However, for your main domain, you will also have to add two additional MX records:

  • a MX record for mail.example.com, which is necessary for receiving emails sent by different applications to the default root@mail.example.com address. For mail.example.com, the MX record in BIND (/etc/bind/db.example.com) will look like this:

    mail IN MX 10 mail.example.com.

  • a MX record for mailman.example.com, which is necessary for sending emails to Mailman mailing lists. For mailman.example.com, the MX record in BIND (/etc/bind/db.example.com) will look like this:

    mailman IN MX 10 mail.example.com.

If you use Hurricane Electric’s DNS services, to add a MX record for the example.com domain, first click on ‘New MX’. Then fill the fields as follows:

You will need to add a MX record like the one for example.com, for any other domain that you host on your server: secondsite.net, thirdsite.info, fourthsite.org, etc. However, the MX records for the mailman subdomain and for the mail subdomain should be added only for the main domain hosted on the server, and not for other domains.

19.13. Set up SPF (Sender Policy Framework) for your domains

Sender Policy Framework (SPF) is a system that allows mail servers to know what hosts are allowed to send emails for a given domain. Setting up SPF records for the domains used to send emails is mandatory if you want to avoid having your emails classified as spam.

The SPF record for the example.com domain, as it should appear in the BIND configuration file for example.com (/etc/bind/db.example.com) will look like this:

@     IN     TXT    "v=spf1 a mx ip4:123.123.123.123 ip6:2b03:8df0:a24b:6eb::1 -all"

where @ is the placeholder for example.com , 123.123.123.123 is the public IPv4 address of your server and 2b03:8df0:a24b:6eb::1 is the IPv6 address used by your mail server (it’s recommended to set here an IPv6 address from the 2b03:8df0:a24b:6eb::/64 subnet allocated to your machine by your hosting provider; if your provider didn’t allocate a /64 subnet to your server and they refuse to do so after you open a support ticket, it’s recommended to disable IPv6 connectivity for your mail server and to remove the ip6:2b03:8df0:a24b:6eb::1 part from the record presented above; otherwise, your emails can be rejected because your IPv6 address can be blacklisted just because it’s part of a /64 range and some IPs in that range sent spam).

Below is a description of the options used:

v=spf1 specifies the SPF version, is required and has to be the first tag.

a authorizes the hosts identified in the domain’s A records to send e-mail.

mx is a shorthand for all the hosts listed in the MX records for the example.com domain.

ip4 and ip6 specify the IP addresses that are allowed to send emails from the example.com domain.

123.123.123.123 is the IPv4 address of your server.

2b03:8df0:a24b:6eb::1 is the IPv6 address of your mail server.

-all indicates that mail from your domain should only come from servers identified in the SPF record. Anything coming from any other source is trying to impersonate your mail server. An alternative is ~all, which shows the same thing but also that mail servers should accept the message and flag it as forged, instead of simply rejecting it. -all makes it more difficult for spammers to send emails that impersonate your domain.

A SPF record like the one from above has to be added for every domain hosted on your server. However, for your main domain, you will also have to add two additional SPF records:

  • a SPF record for mail.example.com, to be able to send emails from command line, which can be useful when testing email deliverability. The emails sent from command line will appear as being sent by root@mail.example.com. For mail.example.com, the SPF record in BIND (/etc/bind/db.example.com) will look like this:

    mail IN TXT "v=spf1 a mx ip4:123.123.123.123 ip6:2b03:8df0:a24b:6eb::1 -all"

  • a SPF record for mailman.example.com, which is necessary for sending emails to Mailman mailing lists. For mailman.example.com, the SPF record in BIND (/etc/bind/db.example.com) will look like this:

    mailman IN TXT "v=spf1 a mx ip4:123.123.123.123 ip6:2b03:8df0:a24b:6eb::1 -all"

To verify the SPF records of incoming mail and thus avoid accepting forged emails, you have to enable two lines that are commented out in the /etc/postfix/master.cf file. Open the file for editing:

nano /etc/postfix/master.cf

Scroll down to the two lines mentioning policyd-spf and remove the comment sign (#), to make them look like this:

policyd-spf  unix  -       n       n       -       0       spawn
    user=policyd-spf argv=/usr/bin/policyd-spf

Also, open the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Uncomment the policyd-spf_time_limit line (by removing the # sign), to increase the Postfix policy agent timeout, which will prevent Postfix from aborting the agent if transactions run a bit slowly. Make that line look like this:

policyd-spf_time_limit = 3600

In the same file, check if you have the following two lines in the smtpd_recipient_restrictions entry:

smtpd_recipient_restrictions =
    ...
    reject_unauth_destination,
    check_policy_service unix:private/policyd-spf,
    ...

Without these settings, SPF verification won’t function properly.

Restart Postfix:

systemctl restart postfix

If you use Hurricane Electric’s DNS services, to add a SPF record for example.com click on ‘New TXT’ and fill the fields as follows:

Replace example.com with your own domain, replace 123.123.123.123 with the IPv4 address of your server and 2b03:8df0:a24b:6eb::1 with the IPv6 address of your mail server.

19.14. Set up DKIM (DomainKeys Identified Mail) for your domains

DKIM (DomainKeys Identified Mail) is an authentication method used to link an email to a domain name. DKIM proves not only that a domain name hasn’t been used falsely by others, but also that the email message hasn’t been changed in transit.

DKIM relies on asymmetric encryption: first you generate a private/public key pair, then enter the public key as a TXT record of the domain used to send emails, then the private key is used to create a signature for each email. The signature is a hash created using the content of the email and the private key. The signature is added as a header to the email. In this way, a receiver mail server that knows the public key from the TXT record of the domain can use it to verify if the email comes from the legitimate domain and that it hasn’t been changed in transit.

Like SPF entries, DKIM entries help a lot in reducing the possibility that your emails might be considered spam by other mail servers.

To implement DKIM for your domains, first you have to install some new packages:

apt-get install opendkim opendkim-tools postfix-policyd-spf-python postfix-pcre

Then create the directories necessary to store the new files:

mkdir -p /etc/opendkim/keys

Make a copy of the /etc/opendkim.conf file:

cp /etc/opendkim.conf /etc/opendkim.conf_orig

Emtpy the /etc/opendkim.conf file:

cat /dev/null > /etc/opendkim.conf

Open the file for editing:

nano /etc/opendkim.conf

Add the following content inside this file:

Mode  sv
Syslog  yes
UserID  opendkim:postfix
UMask  007
Socket  local:/var/run/opendkim/opendkim.sock
PidFile  /var/run/opendkim/opendkim.pid
Selector  default
SendReports  no
SoftwareHeader  yes
Canonicalization  relaxed/simple
SubDomains  no
SignatureAlgorithm  rsa-sha256
MinimumKeyBits  2048
OversignHeaders  From
KeyTable  refile:/etc/opendkim/KeyTable
SigningTable  refile:/etc/opendkim/SigningTable
ExternalIgnoreList  refile:/etc/opendkim/TrustedHosts
InternalHosts  refile:/etc/opendkim/TrustedHosts

Also, in the /etc/opendkim directory, you have to create three files. First create the /etc/opendkim/TrustedHosts file:

nano /etc/opendkim/TrustedHosts

Add the following content inside this file:

127.0.0.1
::1
localhost
mail
mail.example.com
example.com
secondsite.net
thirdsite.info
fourthsite.org
fifthsite.us

Replace example.com with the main domain hosted on your server and secondsite.net, thirdsite.info, fourthsite.org, fifthsite.us, with the other domains hosted on the server.

Then create the /etc/opendkim/SigningTable file:

nano /etc/opendkim/SigningTable

Add the following content inside this file:

*@example.com  default._domainkey.example.com
*@secondsite.net  default._domainkey.secondsite.net
*@thirdsite.info  default._domainkey.thirdsite.info
*@fourthsite.org  default._domainkey.fourthsite.org
*@fifthsite.us  default._domainkey.fifthsite.us

Replace example.com with the main domain hosted on your server and secondsite.net, thirdsite.info, fourthsite.org, fifthsite.us, with the other domains hosted on the server.

Next, create the /etc/opendkim/KeyTable file:

nano /etc/opendkim/KeyTable

Add the following content inside this file:

default._domainkey.example.com  example.com:default:/etc/opendkim/keys/example.com.private
default._domainkey.secondsite.net  secondsite.net:default:/etc/opendkim/keys/secondsite.net.private
default._domainkey.thirdsite.info  thirdsite.info:default:/etc/opendkim/keys/thirdsite.info.private
default._domainkey.fourthsite.org  fourthsite.org:default:/etc/opendkim/keys/fourthsite.org.private
default._domainkey.fifthsite.us  fifthsite.us:default:/etc/opendkim/keys/fifthsite.us.private

Replace example.com with the main domain hosted on your server and secondsite.net, thirdsite.info, fourthsite.org, fifthsite.us, with the other domains hosted on the server.

Navigate to the /etc/opendkim/keys directory:

cd /etc/opendkim/keys

Generate the key for example.com by running:

opendkim-genkey -b 2048 -h rsa-sha256 -r -s default -d example.com

This command will create the files default.private and default.txt. Change the name of these two files as follows:

mv default.private example.com.private
mv default.txt example.com.txt

Repeat the key generation and file renaming for each domain hosted on your server (secondsite.net, thirdsite.info, fourthsite.org, fifthsite.us, etc.)

Change ownership for the /etc/opendkim directory and its content, and permissions for the /etc/opendkim/keys directory:

chown -R opendkim:opendkim /etc/opendkim
chmod 700 /etc/opendkim/keys

To allow the user postfix to have proper access to the /var/run/opendkim/opendkim.sock file, you have to add it to the opendkim group by running:

adduser postfix opendkim

In this way, the /var/run/opendkim directory, which is owned by the opendkim user and the opendkim group, with 750 permissions, will allow the postfix user to access the /var/run/opendkim/opendkim.sock file.

Then make a copy of the /etc/default/opendkim file:

cp /etc/default/opendkim /etc/default/opendkim_orig

Edit the /etc/default/opendkim file:

nano /etc/default/opendkim

Change the group owner for the socket file from opendkim to postfix as follows:

GROUP=postfix

Also edit the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Add the following directives right below the milter_default_action = accept line:

smtpd_milters = local:/var/run/opendkim/opendkim.sock
non_smtpd_milters = local:/var/run/opendkim/opendkim.sock

Restart opendkim and Postfix:

systemctl restart opendkim
systemctl restart postfix

19.14.1. Add the DKIM TXT records for your domains

If you look into the /etc/opendkim/keys/example.com.txt file that you generated earlier for the example.com domain with the opendkim-genkey command, you will see a content similar to this:

default._domainkey	IN	TXT	( "v=DKIM1; h=rsa-sha256; k=rsa; s=email; "
	  "p=MIIBIdCBEhdqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp562Yiy2bRa4xUJgpJlPbk69tWp5aYiZkOYJWIACpcK6TgTS197HavECcZVTu4ZijcmyZqecfytqvjo23mW+7lBc4DMlynYzcNtC2e/i29mKeMT4KPzfz8czBdByjZ76ZKclUkgtVhtsaF2KPUsw6vgn2dYFjMSNQWRtM3LyiWymkwOeKOkbHrQsgb4vxReiq6M8jPd4r0Ad5"
	  "k2FlgfDFqvRgYy3GxQUtAIYdT1G7T7Jc+Q433DG4RPq4WGA6YufkskPbmyP5yquOiJuPNSxzo6srC3zhGzzL6MTMk145iof85keUVgTi3XrlWKPwd8/VOgos2nyVLK5ewd5jnWawJGASZB" )  ; ----- DKIM key default for example.com

Please note that the long string listed after ‘p=‘ is split into two sections. You have to copy this long string in a TXT record added to the example.com domain, in your DNS settings, preserving the two sections of the string.

Therefore, the DKIM record for the example.com domain, as it should appear in the BIND configuration file for example.com (/etc/bind/db.example.com) will look like this:

default._domainkey    IN     TXT    "v=DKIM1; h=sha256; k=rsa; s=email;"  "p=MIIBIdCBEhdqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp562Yiy2bRa4xUJgpJlPbk69tWp5aYiZkOYJWIACpcK6TgTS197HavECcZVTu4ZijcmyZqecfytqvjo23mW+7lBc4DMlynYzcNtC2e/i29mKeMT4KPzfz8czBdByjZ76ZKclUkgtVhtsaF2KPUsw6vgn2dYFjMSNQWRtM3LyiWymkwOeKOkbHrQsgb4vxReiq6M8jPd4r0Ad5"  "k2FlgfDFqvRgYy3GxQUtAIYdT1G7T7Jc+Q433DG4RPq4WGA6YufkskPbmyP5yquOiJuPNSxzo6srC3zhGzzL6MTMk145iof85keUVgTi3XrlWKPwd8/VOgos2nyVLK5ewd5jnWawJGASZB"

Add similar TXT records in the DNS settings for all your other domains.

To test the opendkim configuration run:

opendkim-testkey -d example.com -s default -vvv

The last message should be “key OK”. If you see a “key not secure” message above the “key OK” line, it doesn’t represent an error, it just means that DNSSEC is not in use for opendkim and you can ignore it.

If you use Hurricane Electric’s DNS services, to add a DKIM entry for example.com, click on ‘New TXT’, then fill the fields as follows:

If you want to be extra cautious, you can re-generate the private and public keys with the opendkim-genkey command, then update the TXT records for your domains, once every 3 months.

19.15. Set up a DMARC (Domain Message Authentication, Reporting & Conformance) record for your domains

A DMARC record is used to advise mail servers what you think they should do with emails claiming to be from your domain, that fail validation with SPF and/or DKIM. DMARC also allows you to request reports from other mail servers, about mail that fails to pass one or more validation checks. You should set up DMARC records only after setting up SPF and DKIM records, for each domain.

The DMARC record for the example.com domain, as it should appear in the BIND configuration file for example.com (/etc/bind/db.example.com) will look like this:

_dmarc     IN     TXT    "v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s; fo=1; rf=afrf; rua=mailto:admin@example.com"

where admin@example.com is the address where you want to receive the aggregated reports from other mail servers, for emails coming from you.

Below is a description of the options used:

v specifies the protocol version, in this case DMARC1.

p specifies the policy to be used for emails coming from the example.com domain. The available options are:

  • quarantine instructs that if an email fails validation, the recipient should set it aside for processing.
  • reject requests that the receiving mail server reject the emails that fail validation.
  • none requests that the receiver take no action if an email does not pass validation.

sp specifies the policy for subdomains, such as subdomain.example.com. It accepts the same arguments as the p tag.

adkim specifies the alignment mode for DKIM, which determines how strictly DKIM records are validated. The available options are:

  • r relaxed alignment mode: DKIM authentication is less strictly enforced.
  • s strict alignment mode: only an exact match with the DKIM entry for the root domain will be seen as validated.

aspf specifies the alignment mode for SPF verification. It allows the same options as adkim.

fo allows you to specify which failed authentication methods will be reported. The available options are:

  • 0 requests a report if all authentication methods fail. For example, if an SPF check failed but the DKIM authentication was successful, a report would not be sent.
  • 1 requests a report if any authentication check fails.
  • d requests a report if a DKIM check fails.
  • s requests a report if an SPF check fails.

rf specifies the format used for authentication failure reports. The available options are:

  • afrf uses the ‘Authentication Failure Reporting Format’ as defined by RFC 6591.
  • iodef uses the ‘Incident Object Description Exchange Format’ as defined by RFC 5070 & RFC 6685.

rua specifies the email address that will receive aggregate reports. This option accepts multiple addresses in the following format: rua=mailto:tom@domain1.com,mailto:jerry@domain2.com,mailto:oscar@domain3.com; . Once you receive reports and notice that everything works well, you can disable this feature by removing the rua=mailto:admin@example.com parameter from the DMARC entry, because in time, these reports can become annoying.

Please note that you will need to add a DMARC record like the one for example.com, for any other domain that you host on your server: secondsite.net, thirdsite.info, fourthsite.org, etc.

If you use Hurricane Electric’s DNS services, to add a DMARC record for example.com, click on ‘New TXT’, then fill the fields as follows:

19.16. Install Roundcube

Log in to phpMyAdmin and create a database called roundcubemail and a user called roundcubeuser, on localhost, with a strong password. Save the password somewhere safe, to use it later. Then give user roundcubeuser all the privileges over the roundcubemail database, except the GRANT privilege, which is not necessary.

Then use a browser to access Roundcube’s official download page. Right-click on the ‘Download’ button of the latest ‘Stable Version’ ‘Complete’ of Roundcube, then choose ‘Copy Link Location’ from the context menu, to copy the download link to the clipboard, so that you can use it later, with the wget command.

On your remote server, navigate to /tmp:

cd /tmp

Download the latest version of Roundcube:

wget https://github.com/roundcube/roundcubemail/releases/download/1.4.8/roundcubemail-1.4.8-complete.tar.gz

Replace 1.4.8 with your version number. Uncompress the archive:

tar xf roundcubemail-1.4.8-complete.tar.gz

Remember that you already created the /var/www/mail.example.com directory when installing phpMyAdmin, so now you only need to copy the uncompressed folders and files of Roundcube to /var/www/mail.example.com:

cp -r /tmp/roundcubemail-1.4.8/* /var/www/mail.example.com

Remove the directory and archive from /tmp, because you no longer need them:

rm -r /tmp/roundcubemail-1.4.8 /tmp/roundcubemail-1.4.8-complete.tar.gz

Change ownership and permissions for the /var/www/mail.example.com directory and its content:

chown -R www-data:www-data /var/www/mail.example.com
find /var/www/mail.example.com -type d -exec chmod 750 {} +
find /var/www/mail.example.com -type f -exec chmod 640 {} +

Use a browser to access the Roundcube installer at:

https://mail.example.com/installer/

The first screen will look like this:

The installer checks if all the needed software packages are installed. All the checks should display OK, with the exception of PostgreSQL, and the other DBMS, which are not necessary, since you have MariaDB installed (identified by the installer as MySQL). Click ‘Next’.

In the next screen, leave all the fields and checkboxes as they are, with the exception of the fields/checkbloxes listed below, which you should fill in as follows:

General configuration

Under enable_spellcheck, uncheck Make use of the spell checker.

Logging & Debugging

log_driver file

log_dir /var/log/sites/mail.example.com/

Database setup

Database server localhost

Database name roundcubemail

Database user name roundcubeuser

Database password strongpassword (enter the password of roundcubeuser set up earlier)

IMAP Settings

default_host mail.example.com

SMTP Settings

smtp_server
tls://mail.example.com

Make sure that Use the current IMAP username and password for SMTP authentication is checked.

Display settings & user prefs

In the language* field enter the RFC1766 formatted language name (en_US, de_DE, de_CH, fr_FR, pt_BR, etc.), like:

language* en_US

skin* elastic

prefer_html* check Prefer displaying HTML messages and for Compose HTML formatted messages choose ‘always’

Plugins

Check the following plugins:

additional_message_headers
jqueryui
managesieve
markasjunk
password
show_additional_headers

As mentioned, leave all the other fields/checkboxes as they are, then click ‘CREATE CONFIG’.

The installer will display the following message at the top of the page:

The config file was saved successfully into /var/www/mail.example.com/config directory of your Roundcube installation.

Please note that there is a ‘Continue’ button below the message. Before clicking on that button, leave the browser open and edit the /var/www/mail.example.com/config/config.inc.php file:

nano /var/www/mail.example.com/config/config.inc.php

Add the following line at the top of the file:

$config['enable_installer'] = true;

Also, since you specified that /var/log/sites/mail.example.com should be the log directory, you have to give www-data write access to this directory:

chown www-data:root /var/log/sites/mail.example.com

Next, come back to the installation page opened in your browser and click on the ‘Continue’ button mentioned above.

In the next screen you will see the message ‘DB Schema: NOT OK (Database not initialized)’. Click on the ‘Initialize database’ button below this message. After the initialization, all the checks should display OK.

Next, open the /var/www/mail.example.com/config/config.inc.php file:

nano /var/www/mail.example.com/config/config.inc.php

Below the $config['db_dsnw'] line, add the following line:

$config['log_driver'] = 'file';

Below $config['smtp_server'] = 'tls://mail.example.com'; add the following lines:

$config['smtp_port'] = '587';
$config['smtp_user'] = '%u';
$config['smtp_pass'] = '%p';
$config['auto_create_user'] = true;

Also add the following lines at the end of the file, to tell Roundcube to create the default folders (Sent, Drafts, Junk and Trash) for any new mailbox, when the user first logs in:

// Automatically create the Sent, Drafts, Junk and Trash default folders on user login
$config['create_default_folders'] = true;

To disable the installer which is no longer needed, remove the following line:

$config['enable_installer'] = true;

Now, that Roundcube is installed, for security reasons, delete the installer directory:

cd /var/www/mail.example.com
rm -r installer

19.16.1. Move the configuration file outside the web root

To increase security, copy the config.inc.php file to a directory outside /var/www, like this:

cp /var/www/mail.example.com/config/config.inc.php /srv/scripts/roundcube.php

Empty the configuration file:

cd /var/www/mail.example.com/config
cat /dev/null > config.inc.php

Open the configuration file:

nano config.inc.php

And add the following line inside this file:

<?php include('/srv/scripts/roundcube.php'); ?>

Change the ownership and permissions for the /srv/scripts/roundcube.php file:

cd /srv/scripts
chown www-data:root roundcube.php
chmod 400 roundcube.php

Reload Nginx:

systemctl reload nginx

19.16.2. Enabling debug mode

If you want to enable debug mode to troubleshoot various problems, add the following lines to the configuration file (/srv/scripts/roundcube.php):

// Debug level (from 1 to 4)
$config['debug_level'] = 4;

// Log SQL queries
$config['sql_debug'] = true;

// Log IMAP conversation
$config['imap_debug'] = true;

// Log LDAP conversation
$config['ldap_debug'] = true;

// Log SMTP conversation
$config['smtp_debug'] = true;

Remember to comment these lines out when you don’t need debug mode enabled.

19.16.3. Restrict access to Roundcube’s login page

At this point, you can log in to Roundcube using any of the email addresses and their respective passwords that you have set up previously in Postfix Admin. The login page is:

https://mail.example.com

When you access this page, you will be asked for your HTTP authentication credentials. You have to enter the username and password set up earlier (in the Install phpMyAdmin chapter, in the /etc/nginx/htpass/mail.example.com password file).

Please note that you will have to add to the /etc/nginx/htpass/mail.example.com file a username and password for all the users that you want to allow access to Roundcube’s login page. At this point, the password file should contain the name and the hashed password of only one user (verner in our example). If you want to allow the user albert to access the login page, you should add his name and a password to the password file like this:

htpasswd /etc/nginx/htpass/mail.example.com albert

Repeat this command for all the users that you want to allow access to Roundcube’s login page.

After entering your HTTP authentication credentials, you will see the login page:

Now you can use the email addresses and their respective passwords that you have set up in Postfix Admin, to log in to Roundcube.

Before you can send emails to external domains, make sure that you have properly configured a MX record, a SPF record, an Opendkim record and a DMARC record (apart from the A and AAAA records), as described in the 19.12., 19.13., 19.14. and 19.15. chapters, respectively.

19.16.4. Configure logrotate to rotate the Roundcube errors log

Roundcube will write access data to the access.log and errors to the errors.log. Both logs are located in the directory that you have set up earlier as log_dir, namely /var/log/sites/mail.example.com. You have to configure logrotate to rotate these logs.

Open the /etc/logrotate.d/nginx file:

nano /etc/logrotate.d/nginx

Add the following block at the end of this file:

/var/log/sites/mail.example.com/access.log  /var/log/sites/mail.example.com/errors.log {
        missingok
        rotate 10
        compress
        delaycompress
        notifempty
        create 0640 www-data adm
        size 2M
        sharedscripts
        prerotate
                if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
                        run-parts /etc/logrotate.d/httpd-prerotate; \
                fi; \
        endscript
        postrotate
                [ ! -f /var/run/nginx.pid ] || kill -USR1 `cat /var/run/nginx.pid`
        endscript
}

19.16.5. Configure Fail2ban to protect Roundcube against brute-force attacks

Navigate to the /etc/fail2ban/filter.d directory:

cd /etc/fail2ban/filter.d

Ignore the roundcube-auth.conf file which is already there, and create a new file called roundcube.conf:

nano roundcube.conf

Add the following content in this file:

[Definition]

failregex = IMAP Error: Login failed for .* from <HOST>
            ^<HOST> .* \"GET / HTTP/2.0\" 401 195 .*$
ignoreregex =

The second line identifies the failed log in attempts against the HTTP authentication window, which is displayed right before loading the Roundcube login page and can be also subject to brute-force attacks. Now open the /etc/fail2ban/jail.local file:

nano /etc/fail2ban/jail.local

Add the following lines in the 'Webmail and groupware servers' section:

[roundcube]
enabled  = true
filter   = roundcube
logpath  = /var/log/sites/mail.example.com/errors.log
           /var/log/sites/mail.example.com/access.log
port     = 80,443
maxretry = 4
bantime = 259200

Replace example.com with the main domain hosted on your server. Reload Fail2ban:

systemctl reload fail2ban

19.16.6. Enable the ‘managesieve’ plugin in Roundcube

When configuring the Roundcube installation, you have checked managesieve in the list of plugins that will be installed. So, the managesieve plugin is already installed. Yet, to be able to use it and create custom filters from Roundcube ‘Settings’ > ‘Filters’, you will have to enable it. Navigate to the managesieve directory of Roundcube:

cd /var/www/mail.example.com/plugins/managesieve

Copy config.inc.php.dist to config.inc.php:

cp config.inc.php.dist config.inc.php

Open the config.inc.php file:

nano config.inc.php

Change $config['managesieve_port'] = null; to:

$config['managesieve_port'] = 4190;

Change $config['managesieve_default'] = '/etc/dovecot/sieve/global'; to:

$config['managesieve_default'] = '/etc/dovecot/conf.d/custom-sieves/global_after.sieve';

Also change $config['managesieve_vacation'] = 0; to:

$config['managesieve_vacation'] = 1;

Restart Dovecot:

systemctl restart dovecot

19.16.7. Enable the ‘password’ plugin in Roundcube

When installing Roundcube, you have checked ‘password’ in the list of plugins that will be installed. Therefore, the ‘password’ plugin is already installed. Yet, to be able to use it, you have to enable it.

Execute the following SQL commands in order to give user roundcubeuser SELECT privileges on the username and password columns and UPDATE privileges on the password column of the mailbox table from the mail database, on localhost:

mysql -u root -p
MariaDB [(none)]> GRANT SELECT (username, password), UPDATE (password) ON mail.mailbox TO 'roundcubeuser'@'localhost';
MariaDB [(none)]> FLUSH PRIVILEGES;
MariaDB [(none)]> exit
Bye

Whithout these privileges, the users won’t be able to change their passwords from inside Roundcube.

Navigate to the plugin’s directory:

cd /var/www/mail.example.com/plugins/password

Copy the config.inc.php.dist file to config.inc.php:

cp config.inc.php.dist config.inc.php

Edit config.inc.php:

nano config.inc.php

Edit only the lines listed below. They should look like this:

$config['password_driver'] = 'sql';
$config['password_confirm_current'] = true;
$config['password_minimum_length'] = 8;
$config['password_log'] = false;
$config['password_login_exceptions'] = null;
$config['password_hosts'] = array('mail.example.com');
$config['password_force_save'] = true;
$config['password_algorithm'] = 'dovecot';
//$config['password_dovecotpw'] = '/usr/local/sbin/doveadm pw'; // for dovecot-2.x
//$config['password_dovecotpw'] = '/usr/local/sbin/dovecotpw'; // for dovecot-1.x
$config['password_dovecotpw'] = '/usr/bin/doveadm pw';
$config['password_dovecotpw_method'] = 'CRAM-MD5';
$config['password_dovecotpw_with_method'] = true;

// SQL Driver options.

$config['password_db_dsn'] = 'mysql://mail:gdf6g19VBg45sDfg14@localhost/mail';

$config['password_query'] = 'UPDATE mail.mailbox SET password=%P WHERE username=%u AND password=%O LIMIT 1';

$config['password_crypt_hash'] = 'md5';
$config['password_hash_algorithm'] = 'sha1';

where gdf6g19VBg45sDfg14 is the password of the user mail, that you set up when you created the mail database.

Now, every user can change her/his password when logged in to Roundcube from ‘Settings’ > ‘Password’.

19.16.8. Enable the ‘autologon’ and ‘autologout’ plugins

If you want to integrate Roundcube with Roundpin, to be able to open a new email window with one click, to easily send emails to your Roundpin contacts, and to be able to check incoming emails from inside Roundpin, you will need to enable the ‘autologon’ and ‘autologout’ Roundcube plugins. To do so, edit the main configuration file (remember that you have copied the main configuration file, /var/www/mail.example.com/config/config.inc.php, as /srv/scripts/roundcube.php, for security reasons):

nano /srv/scripts/roundcube.php

Add ‘autologon’ and ‘autologout’ to the list of enabled plugins, by modifying the $config['plugins'] array, as follows:

// List of active plugins (in plugins/ directory)
$config['plugins'] = array('additional_message_headers', 'jqueryui', 'managesieve', 'markasjunk', 'password', 'show_additional_headers', 'autologon', 'autologout');

The ‘autologon’ plugin is already present in the /var/www/mail.example.com/plugins directory because it’s part of the default Roundcube installation, therefore, you don’t have to download it and place it in that directory. However, you have to change it slightly in order to use it to integrate Roundcube with Roundpin. Open the /var/www/mail.example.com/plugins/autologon/autologon.php file for editing:

nano /var/www/mail.example.com/plugins/autologon/autologon.php

Search for the following lines, and comment them out, to make them look like this:

//        if (!empty($_GET['_autologin']) && $this->is_localhost()) {
//            $args['user']        = 'me';
//            $args['pass']        = '******';
//            $args['host']        = 'localhost';
//            $args['cookiecheck'] = false;
//            $args['valid']       = true;
//        }

Then add the following lines right below them:

        if (!empty($_POST['_autologin']) && $this->is_localhost())
        {
            $args['user'] = $_POST['_user'];
            $args['pass'] = $_POST['_pass'];
            $args['host'] = '127.0.0.1';
            $args['cookiecheck'] = false;
            $args['valid'] = true;
        }

Then, search for the following line and comment it out, to make it look like this:

//        return $_SERVER['REMOTE_ADDR'] == '::1' || $_SERVER['REMOTE_ADDR'] == '127.0.0.1';

Next, add the following line right below it:

        return true;

The ‘autologout’ plugin has to be first downloaded from here, then uncompressed and saved in the /var/www/mail.example.com/plugins directory under the name autologout. You should also set the right ownership and permissions for the new plugin:

cd /var/www/mail.example.com/plugins
chown -R www-data:www-data autologout
chmod 750 autologout
chmod 640 autologout/*

This concludes enabling the two plugins.

19.16.9. Creating canned emails

If you frequently send emails with similar content, you can create a template, or a ‘canned’ email, that you can then select from a drop-down list, modify a bit if necessary and send. This will spare you the time to write the same content over and over again.

To create a ‘canned’ email go to ‘Settings’ > ‘Responses’ > click on the ‘Create’ button on the upper bar. You will see the following screen:

In the ‘Name’ field enter a name for that ‘canned’ email, in the ‘Response Text’ text area enter the content of the email, then click ‘Save’. You can create as many ‘canned’ emails as you need. Then, when you want to send a new email or to reply to a received email, you will see the ‘Responses’ button on the upper bar of the email editing window. When you click on ‘Responses’, you will see a drop-down list with all the ‘canned’ emails that you have saved. When you choose an email from the drop-down list, its content will be automatically inserted in the text area of the new message or reply. You can then modify the text where you find necessary and send the email to the recipient.

19.16.10. Creating filters

If you want to filter incoming messages according to certain rules, to separate them from regular emails and send them to a specific folder, flag them, redirect them, discard them, etc. you can create an email filter. To create a new filter go to ‘Settings’ > ‘Filters’ > click on the ‘Create’ button on the upper bar. You will see the following screen:

In the ‘Filter name’ field enter a name for the filter, in the ‘Scope’ field choose from the drop-down list one of the three options: ‘matching all of the following rules’, ‘matching any of the following rules’ or ‘all messages’. In the ‘Rules’ section add any rules you need. For example:

Subject

From

contains

is equal to

view our catalog

contact@domain.com

You can add new rules by clicking on the ‘+’ sign.

In the ‘Actions’ section add any action you need. For example:

Move message to

Junk

You can add multiple actions, such as:

Redirect message to

Send message copy to

info@somedomain.com

office@otherdomain.com

Click ‘Save’.

If you want to separate a specific type of incoming emails from the regular emails and send them to a separate folder, you can create a new folder in ‘Settings’ > ‘Folders’ > ‘Create’, then add a filter to send all incoming emails falling under a specific rule, to the newly created folder.

19.16.11. Creating ‘out of office’ replies

When you know you will be out of office for a period of time because of a vacation, a voyage, etc., and you want to inform your contacts that you are out of office if they send you emails during that period, you can create an ‘out of office’ reply. Go to ‘Settings’ > ‘Out of Office’. You will see the following screen:

In the ‘Subject’ field enter a subject for the reply, such as ‘I’m on vacation’. In the ‘Body’ field enter the content of the message, for example:

Hello,

I’m on vacation until July 31. For urgent matters you can contact my assistant at office@domain.com.

As soon as I come back I’ll send you my response.

Thank you for your understanding!

Sincerely,

John Doe

In the ‘Start time’ and ‘End time’ fields enter the days from the beginning and the end of the time period in which you want the automatic ‘out of office’ reply to be sent. In the ‘Status’ field choose ‘On’. Under ‘Advanced settings’, in the ‘My e-mail address’ enter the email address of the Roundcube account. In the ‘Reply interval’ enter 1. This means that the mail server will send the ‘out of office’ reply only once per day if a contact sends multiple emails to your email address in a day. In the ‘Incoming message action’ field choose ‘Keep’ if you want the incoming messages to be kept in the Inbox after they arrive. You can also choose to discard the messages that arrive during the vacation, to redirect them, or to send copies of the incomng messages to a different email address. Click ‘Save’.

Please note that after you create the ‘out of office’ reply, it will be listed in the ‘Filters’ section. This is because the ‘out of office’ reply is nothing else than a type of filter: it filters all the incoming emails and if it finds that some emails have arrived in the specified period of time, it replies with the predefined text of the ‘out of office’ response. Thus, you can create multiple ‘out of office’ replies as regular filters, one for each period of vacation during the year, and you can enable and disable them as needed. If in addition to an ‘out of office’ reply you add other type of filters, in the ‘Out of Office’ section you will find a new field called ‘Put the out-of-office rule after’, which will allow you to choose the filter after which the ‘out of office’ filter will be applied to incoming emails.

19.16.12. Changing themes

You can change Roundcube’s themes, called ‘skins’ by clicking on ‘Settings’ on the left bar > ‘Preferences’ > ‘User Interface’ > ‘Interface Skin’. You can choose between ‘Classic’, ‘Elastic’ and ‘Larry’. However, only ‘Elastic’ is responsive.

In the ‘Preferences’ section you can also change other settings, such as those related to ‘Composing Messages’.

19.16.13. Upgrading Roundcube

Before upgrading Roundcube to a new version, it’s recommended to verify if the new version has been tested and confirmed to function well within the suite of applications described in this guide. Once we test an application and confirm that it works well, we include it on this page.

You can check whether a new version of Roundcube has been released on the official github.com ‘Releases’ page: https://github.com/roundcube/roundcubemail/releases . When a new version is avaialble, you can upgrade your existing installation by following the steps detailed below.

First make a backup of your Roundcube database by logging in to phpMyAdmin, clicking on the Roundcube database (roundcubemail) on the left panel, then going to ‘Export’, then ‘Go’, then ‘Save File’, then ‘OK’. Don’t forget to include the date of the backup in the name of the exported file (Eg.: roundcubemail-2021-9-25.sql).

Then make a backup copy of the whole Roundcube directory:

cd /var/bm_archives
tar czf var-www-mail.example.com-2021-09-25.tar.gz /var/www/mail.example.com

Replace example.com-2021-09-25 with your domain and date of backup. You can use a FTP client like FileZilla to upload the database backup from your local computer (roundcubemail-2021-9-25.sql) to the backups directory on the remote server (/var/bm_archives). Also, you can use FTP to download the backup of the Roundcube files (var-www-mail.example.com-2021-09-25.tar.gz) to your local computer. In this way you will have the backups both on the remote server and your local computer.

Please note that you shouldn’t overwrite the Roundcube installation with the new version. First download the latest version of Roundcube from their official website to the /tmp directory. Navigate to https://roundcube.net/download/, look for the latest ‘Complete’ stable version, right-click on the ‘Download’ button and choose ‘Copy Link’ to copy the URL to the clipboard. Then run:

cd /tmp
wget https://github.com/roundcube/roundcubemail/releases/download/1.4.8/roundcubemail-1.4.8-complete.tar.gz

Replace 1.4.8 with your version number. Extract the archive, then go to the /bin directory:

tar xf roundcubemail-1.4.8-complete.tar.gz 
cd roundcubemail-1.4.8/bin

Next use the installto.sh script to upgrade Roundcube. Run:

php installto.sh /var/www/mail.example.com

Replace example.com with your domain. You’ll be asked if you want to update. Type y and press Enter. The update script will then copy all the new files to the target location and check and update the database schema.

Next, set the right ownership and permissions for the /var/www/mail.example.com directory:

chown -R www-data:www-data /var/www/mail.example.com
find /var/www/mail.example.com -type d -exec chmod 750 {} +
find /var/www/mail.example.com -type f -exec chmod 640 {} +

The next step is to update Roundcube dependencies with Composer. First check if you have it installed by running:

composer.phar

If the output shows Composer’s version and a list of options and commands, it means it’s installed. If the output is:

-bash: composer.phar: command not found

it means that Composer is not installed. To install Composer system-wide to manage dependencies when updating php applications, run:

cd /usr/local/bin
curl -sS https://getcomposer.org/installer | php

This will create a composer.phar file in the /usr/local/bin directory and you will be able to run it without specifying its full path, like this:

composer.phar

When you want to update Composer, run:

composer.phar self-update

Now update the Roundcube dependencies with Composer by running:

cd /var/www/mail.example.com
composer.phar update --no-dev 

Then, because you are using the build-in addressbook, you have to also run the indexing script (/var/www/mail.example.com/bin/indexcontacts.sh). First change its permissions:

chmod 740 /var/www/mail.example.com/bin/indexcontacts.sh

Then run it specifying its full path, like this:

/var/www/mail.example.com/bin/indexcontacts.sh

Then the archive and the directory of the new Roundcube version from the /tmp directory can be removed:

cd /tmp
rm -r roundcubemail-1.4.8-complete.tar.gz roundcubemail-1.4.8

If during the upgrade process you are asked if you want to let the upgrade script to edit the configuration file to remove obsolete settings, etc., you should answer yes. In this case, the /var/www/mail.example.com/config/config.inc.php file will be edited by the script and for security reasons you’ll have to take its content outside the web root, as you did when installing Roundcube. So, copy the content of the config.inc.php file to /srv/scripts like this:

cp /var/www/mail.example.com/config/config.inc.php /srv/scripts/roundcube.php

Next, empty the /var/www/mail.example.com/config/config.inc.php file:

cat /dev/null > /var/www/mail.example.com/config/config.inc.php

Edit the file:

nano /var/www/mail.example.com/config/config.inc.php

Place the following line inside this file:

<?php include('/srv/scripts/roundcube.php'); ?>

Set the proper permissions for the /srv/scripts/roundcube.php file:

cd /srv/scripts
chown www-data:root roundcube.php
chmod 400 roundcube.php

Since you have modified the ‘autologon’ plugin in order to integrate Roundcube with Roundpin as explained earlier in the Enable the ‘autologon’ and ‘autologout’ plugins chapter, you will need to make the changes again after upgrading Roundcube. Therefore, open the /var/www/mail.example.com/plugins/autologon/autologon.php file for editing:

nano /var/www/mail.example.com/plugins/autologon/autologon.php

Search for the following lines, and comment them out, to make them look like this:

//        if (!empty($_GET['_autologin']) && $this->is_localhost()) {
//            $args['user']        = 'me';
//            $args['pass']        = '******';
//            $args['host']        = 'localhost';
//            $args['cookiecheck'] = false;
//            $args['valid']       = true;
//        }

Then add the following lines right below them:

        if (!empty($_POST['_autologin']) && $this->is_localhost())
        {
            $args['user'] = $_POST['_user'];
            $args['pass'] = $_POST['_pass'];
            $args['host'] = '127.0.0.1';
            $args['cookiecheck'] = false;
            $args['valid'] = true;
        }

Then, search for the following line and comment it out, to make it look like this:

//        return $_SERVER['REMOTE_ADDR'] == '::1' || $_SERVER['REMOTE_ADDR'] == '127.0.0.1';

Next, add the following line right below it:

        return true;

19.17. Install and integrate SpamAssassin

The best free and open source spam filter is SpamAssassin, officially called “Apache SpamAssassin”. Indeed, Rspamd seems to offer a bit faster spam filtering, but at the cost of using large amounts of RAM memory, which makes it inferior to SpamAssassin. To install SpamAssassin run:

apt-get install spamassassin spamc dovecot-antispam spamass-milter

Make a copy of the /etc/default/spamassassin file:

cp /etc/default/spamassassin /etc/default/spamassassin_orig

Open the /etc/default/spamassassin file:

nano /etc/default/spamassassin

Change the OPTIONS parameter, to make it look like this:

OPTIONS="-x --max-children 5 --nouser-config --helper-home-dir /var/lib/spamassassin -u debian-spamd -g debian-spamd --siteconfigpath /etc/spamassassin --socketpath=/var/spool/postfix/spamassassin/spamd.sock --socketowner=debian-spamd --socketgroup=debian-spamd --socketmode=0660"

Change the PIDFILE parameter, to make it look like this:

PIDFILE="/var/run/spamassassin.pid"

Change the CRON parameter, to make it look like this:

CRON=1

Add the spamass-milter user to the debian-spamd group:

adduser spamass-milter debian-spamd

Create the /var/spool/postfix/spamassassin directory and set the right ownership for it:

mkdir /var/spool/postfix/spamassassin
chown -R debian-spamd:debian-spamd /var/spool/postfix/spamassassin

Make a copy of the /etc/spamassassin/local.cf file:

cp /etc/spamassassin/local.cf /etc/spamassassin/local.cf_orig

Open the /etc/spamassassin/local.cf file:

nano /etc/spamassassin/local.cf

Change/add the following parameters to make them look like this:

report_safe 0
required_score 2.0

use_bayes               1
use_bayes_rules         1
bayes_auto_learn        1

skip_rbl_checks         1
skip_uribl_checks       1
use_razor2              0
use_pyzor               0

#   Set headers which may provide inappropriate cues to the Bayesian
#   classifier
bayes_ignore_header X-Bogosity
bayes_ignore_header X-Spam-Flag
bayes_ignore_header X-Spam-Status

ifplugin Mail::SpamAssassin::Plugin::Shortcircuit
endif # Mail::SpamAssassin::Plugin::Shortcircuit

Make a copy of the /etc/default/spamass-milter file:

cp /etc/default/spamass-milter /etc/default/spamass-milter_orig

Open the /etc/default/spamass-milter file:

nano /etc/default/spamass-milter

Change the OPTIONS parameter, to make it look like this:

OPTIONS="-u spamass-milter -i 127.0.0.1 -m -I -- --socket=/var/spool/postfix/spamassassin/spamd.sock"

Enable spamassassin and spamass-milter by running:

systemctl enable spamassassin.service
systemctl enable spamass-milter.service

Now open the /etc/postfix/master.cf file:

nano /etc/postfix/master.cf

Uncomment the following two lines located near the end of the file, above the policyd-spf line. The two lines should look like this:

dovecot   unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail:vmail argv=/usr/bin/spamc -u debian-spamd -e /usr/lib/dovecot/deliver -d ${recipient}

Open the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Edit the smtpd_milters and non_smtpd_milters lines, to make them look like this :

smtpd_milters = unix:/var/spool/postfix/spamass/spamass.sock local:/var/run/opendkim/opendkim.sock
non_smtpd_milters = unix:/var/spool/postfix/spamass/spamass.sock local:/var/run/opendkim/opendkim.sock

Restart the following services:

systemctl restart dovecot
systemctl restart postfix
systemctl restart spamassassin
systemctl restart spamass-milter

To check if SpamAssassin works, send an email from one of your other email addresses hosted on other servers to an email address on the mail server that you are configuring now (for example to admin@example.com). On the ‘Subject’ line enter the following text:

*****SPAM***** XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X

You can leave the body of the email empty. If SpamAssassin is functioning correctly, the message from above will be sent to the ‘Junk’ folder of your email account. Please note that the message will not be sent to ‘Junk’ if the ‘Junk’ folder doesn’t exist yet in that email account. After you create a mailbox, you should log in to Roundcube at least once because the ‘Junk’ folder is created automatically when you log in for the first time to Roundcube. If the ‘Junk’ folder doesn’t exist, a message identified as spam is sent to ‘Inbox’.

To see the way SpamAssassin marks an email as spam, you can look at the source of an email identified as such. You will see the headers added by SpamAssassin, which will look similar to this:

X-Spam-Flag: YES
X-Spam-Status: Yes, score=1001.6 required=2.0 tests=DKIM_SIGNED,DKIM_VALID,
        FREEMAIL_FROM,GTUBE,SUBJ_ALL_CAPS,UNPARSEABLE_RELAY autolearn=no
        autolearn_force=no version=3.4.2
X-Spam-Report: 
        * 1000 GTUBE BODY: Generic Test for Unsolicited Bulk Email
        *  0.0 FREEMAIL_FROM Sender email is commonly abused enduser mail
        *      provider (...)
        *  1.6 SUBJ_ALL_CAPS Subject is all capitals
        * -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature
        *  0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily
        *       valid
        *  0.0 UNPARSEABLE_RELAY Informational: message has unparseable relay
        *      lines
X-Spam-Level: **************************************************
X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on
        mail.example.com

After you install SpamAssassin you may notice in your /var/log/mail.log file an error like this:

spamass-milter[12892]: Could not retrieve sendmail macro "i"!.  Please add it to confMILTER_MACROS_ENVFROM for better spamassassin results

This is a harmless warning that you can ignore. It appears because unlike the Sendmail server, for which spamass-milter was written originally, Postfix assigns a queue ID to an incoming message later in the SMTP transaction, and spamass-milter asks for that information before Postfix can provide it. This doesn’t affect spam filtering.

19.17.1. Upgrading SpamAssassin

Since SpamAssassin has been installed from the official Debian repository, to upgrade it, all you need to do is to run apt-get update && apt-get dist-upgrade with a specific frequency, as described in the Maintenance steps chapter. This command will upgrade SpamAssassin if there is a new version available. Also, during these upgrades, the configuration changes implemented as described above, will be preserved.

19.18. Integrate ClamAV using clamav-milter

A mail server should scan all incoming and outgoing emails, including their compressed attachments, with an antivirus. ClamAV is well-suited for this purpose. If you already installed it as described in the Install ClamAV chapter, you will only need to integrate it with your mail server, as described below.

First install the clamsmtp and clamav-milter packages:

apt-get install clamsmtp clamav-milter

Make a copy of the /etc/clamav/clamav-milter.conf file:

cp /etc/clamav/clamav-milter.conf /etc/clamav/clamav-milter.conf_orig

Open the /etc/clamav/clamav-milter.conf file:

nano /etc/clamav/clamav-milter.conf

Change the following directives, to make them look like this:

MilterSocket /var/run/clamav/clamav-milter.ctl
ClamdSocket unix:/var/run/clamav/clamd.ctl
MaxFileSize 600M
MilterSocketGroup postfix
MilterSocketMode 664

Make a copy of the /etc/clamsmtpd.conf file:

cp /etc/clamsmtpd.conf /etc/clamsmtpd.conf_orig

Open the /etc/clamsmtpd.conf file:

nano /etc/clamsmtpd.conf

Change the following directives, to make them look like this:

OutAddress: 10026
Listen: 127.0.0.1:10025
ClamAddress: /var/run/clamav/clamd.ctl

Create the socket directory and set the proper ownership for it:

mkdir /var/spool/postfix/clamav
chown -R clamav:postfix /var/spool/postfix/clamav

Next, open the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Edit the smtpd_milters and non_smtpd_milters lines, to make them look like this:

smtpd_milters = unix:/var/spool/postfix/spamass/spamass.sock unix:/var/run/clamav/clamav-milter.ctl local:/var/run/opendkim/opendkim.sock
non_smtpd_milters = unix:/var/spool/postfix/spamass/spamass.sock unix:/var/run/clamav/clamav-milter.ctl local:/var/run/opendkim/opendkim.sock

Restart services and update the virus signatures:

systemctl restart clamav-daemon
systemctl restart clamav-milter
freshclam
systemctl restart postfix

19.18.1. Test email virus detection

You can check if ClamAV is scanning incoming and outgoing emails by sending an email from an email address on a different host to an email address on the server that you are configuring, and vice versa. If you check the source of such an email (in Thunderbird, in the opened message window, go to ‘More’ > ‘View Source’), you will see two lines like this:

X-Virus-Scanned: clamav-milter 0.102.4 at mail.example.com
X-Virus-Status: Clean

To check if the antivirus detects viruses included in the email attachment or in the email body, do the following experiment:

Create the EICAR Anti-Virus Test File by saving the following string in a text file called testing:

X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

The EICAR test file simulates a real virus, without causing any harm. Attach the testing file to an email and send that email to an email address hosted on the server that you are configuring. Be aware that some email providers, such as mail.com will refuse to send an email with an attachedment that contains a test virus. When trying to send such a message, you will receive a ‘Mail delivery failed: returning message to sender’ message. On the other hand, other email providers, such as proton.com will let you send the email.

If the real-time email virus detection works as expected, you shouldn’t receive the email with the ‘infected’ attachment in the mailbox located on the server that you are configuring. Instead, in the /var/log/clamav/clamav.log file, you will see a line this this:

Sat Mar 05 11:10:38 2021 -> fd[10]: Eicar-Signature(69630e4574ec6798239b091cda43dca0:69) FOUND

You will see a similar line if you try to send an ‘infected’ email from the server you are configuring, to an email account on an external host. In this situation, if you check the /var/log/mail.log file, you will also see a line like the one shown below, which will indicate that the antivirus has prevented the ‘infected’ email from being sent, although the message will appear as successfully sent in Roundcube:

milter-hold: END-OF-MESSAGE from mail.example.com[123.123.123.123]: milter triggers HOLD action; from=<admin@example.com> to=<username@recipientdomain.com> proto=ESMTP helo=<mail.example.com>

Therefore, ClamAV deletes immediately any infected incoming or outgoing email. As mentioned, an infected outgoing email will have a copy saved in the Sent folder, although it won’t be actually sent.

You can repeat the experiment from above, by archiving the testing file in different formats (zip, tar.gz, tar.xz, tar.bz2, etc.) and attaching the archive to the test email, to test if ClamAV can detect viruses hidden inside archives, or by including the EICAR string in the body of the email. The EICAR string wasn’t created to be detected when surrounded by other texts, so, if you include it in the body of an email, make sure to include it alone, without any other text lines above or below it.

19.18.2. Remove the X-Virus-Scanned and the X-Virus-Status headers from all emails

After you run the tests described above and verify that the antivirus works as expected, you should remove the X-Virus-Scanned and the X-Virus-Status headers from outgoing and incoming emails, because they can give potential attackers information about clamav-milter‘s existence and version. To remove this line from the headers of all the emails, edit the /etc/clamav/clamav-milter.conf file:

nano /etc/clamav/clamav-milter.conf

Comment out the AddHeader Replace parameter like this:

#AddHeader Replace

Then restart clamav-milter:

systemctl restart clamav-milter

19.19. Install and integrate Postgrey

Postgrey is an application that implements ‘greylisting’ to fight spam. ‘Greylisting’ means that when the mail server receives a request for email delivery from an external SMTP server whose IP address is seen for the first time, it neither blacklists it, nor whitelists it. It just responds with a temporary SMTP error code, to test if the external server will try again after a few minutes, as it is required by the official RFC rules and as all the legitimate SMTP servers will do. If the external server tries again after a specified time, the email is accepted. This approach drastically reduces spam, because in general spammers use compromised mail servers to send as many spam emails as possible in a short period of time, since their behavior can be noticed, the compromised system can be patched, or their IP can be included on public blacklists. Thus, the typical spam sending SMTP server will try to deliver an email only once.

To install Postgrey run:

apt-get install postgrey

Edit the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Uncomment the following line located at the end of the smtpd_recipient_restrictions block, before permit, to make it look like this:

smtpd_recipient_restrictions =
            ...
            check_policy_service inet:localhost:60000,
            permit

Restart Postfix:

systemctl restart postfix

Make a copy of the /etc/default/postgrey file:

cp /etc/default/postgrey /etc/default/postgrey_orig

Edit the /etc/default/postgrey file:

nano /etc/default/postgrey

Edit the POSTGREY_OPTS and the POSTGREY_TEXT directives. Make them look like this:

POSTGREY_OPTS="--inet=60000 --delay=199 --max-age=35 --auto-whitelist-clients=5"

POSTGREY_TEXT="Please come back later."

The options used in the POSTGREY_OPTS parameter are explained below:

--inet=60000 make Postgrey listen on port 60000 on localhost, both on IPv4 and IPv6;

--delay=199 reject the emails from new IPs for a minimum time span of 199 seconds;

--max-age=35 delete recorded IPs older than 35 days since the last time that they have been seen

--auto-whitelist-clients=5 whitelist a client only after the 5th successful email delivery

The text included in the POSTGREY_TEXT parameter is the customized rejection text.

Postgrey comes with two important configuration files: /etc/postgrey/whitelist_recipients and /etc/postgrey/whitelist_clients.

The /etc/postgrey/whitelist_recipients file offers a quick way to bypass all greylisting mechanisms. In this file you can include all the email addresses hosted on the server for which you want to receive emails from any external server, including potential spammers. This can be useful for public addresses that should receive emails instantly, such as postmaster@ or abuse@ (these two addresses have been already included in this file by default).

The /etc/postgrey/whitelist_clients file lets you control the whitelisting of various mail hosts. In this file you can include domains, IPs or regular expressions, to identify the mail servers that will be allowed to send emails without passing through the greylisting mechanism implemented by Postgrey, so that you can be sure that all emails coming from them will reach your mailboxes. By default, this file already contains a long list of mail servers that are known to have problems with resending emails, although they are legitimate mail servers.

You can add other domains to /etc/postgrey/whitelist_clients, such as domains that you know to be legitimate email senders, like the following:

mout.gmx.com
mail.com
aol.com
hotmail.com

Restart Postgrey:

systemctl restart postgrey

To see if Postgrey is listening to port 60000, run:

lsof -i :60000

The result should look like this:

COMMAND     PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
postgrey  24520 postgrey    5u  IPv6 473078      0t0  TCP localhost:60000 (LISTEN)
postgrey  24520 postgrey    6u  IPv4 473079      0t0  TCP localhost:60000 (LISTEN)

To check if Postgrey is working as expected, you can send an email from an email account hosted on an external server, to an email account hosted on the server you are configuring. If the IP of the sending server is new, you will see a line similar to the one from below, in the /var/log/mail.log file:

mail postgrey[25613]: action=greylist, reason=new, client_name=sender.com, client_address=124.124.124.124/32, sender=usernameg@senderdomain.com, recipient=admin@example.com

Also, you will receive the email after a delay of 199 seconds (3.3 minutes) or more. In the header of the received email you can notice the X-Greylist header, that will look like this:

X-Greylist: delayed 303 seconds by postgrey-1.36 at mail.example.com;

19.19.1. Upgrading Postgrey

Since Postgrey has been installed from the official Debian repository, to upgrade it, all you need to do is to run apt-get update && apt-get dist-upgrade with a specific frequency, as described in the Maintenance steps chapter. This command will upgrade Postgrey if there is a new version available. Also, during these upgrades, the configuration changes implemented as described above, will be preserved.

19.20. Special Postfix settings

19.20.1. Blacklisting and whitelisting IPs and domains with Postfix

If you want to blacklist IPs, IP ranges, domains and subdomains, so that all the emails coming from them will be rejected, you need to edit the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Add check_client_access hash:/etc/postfix/client_checks, right below smtpd_recipient_restrictions = like this:

smtpd_recipient_restrictions =
            check_client_access hash:/etc/postfix/client_checks,
            reject_sender_login_mismatch,
            permit_mynetworks,
            permit_sasl_authenticated,
            reject_invalid_hostname,
            ...

Next create a file called client_checks in the /etc/postfix directory:

nano  client_checks

Here, first list all the IPs, IP ranges or domains that you want rejected, and then all the IPs, IP ranges or domains that you want to whitelist, so that all the emails coming from them will be accepted, like the following:

example.com              REJECT 
123.123.123.123          REJECT
222.222.222.0/24         REJECT
2002:acf4::1             REJECT
2001:db8:1234::/48       REJECT
example2.org             OK
example3.net             OK
111.111.111.111          OK
232.232.232.0/24         OK
2002:be21::2             OK
2001:cf9:45::/48         OK

You can add an optional message to be sent by Postfix when rejecting an email like so:

example.com               REJECT     Your domain is blacklisted

Please note that when listing a domain, like example.com, with the REJECT directive, the emails from all its subdomains will also be rejected (the emails from sub1.example.com or sub2.example.com will also be rejected). In other words, listing a domain with REJECT will also blacklist its subdomains.

Next, create the lookup table file by running:

postmap client_checks

Run the postmap command again each time you modify the /etc/postfix/client_checks file.

In the situation in which you want to reject or accept the emails coming from specific email addresses, in spite of the Postfix policy for their domains or IPs, you can do so by using the check_sender_access directive. Open the /etc/postfix/main.cf file and add check_sender_access hash:/etc/postfix/sender_checks, right above check_client_access hash:/etc/postfix/client_checks, like this:

smtpd_recipient_restrictions =
       check_sender_access hash:/etc/postfix/sender_checks,
       check_client_access hash:/etc/postfix/client_checks,
       reject_sender_login_mismatch,
       permit_mynetworks,
       permit_sasl_authenticated,
       reject_invalid_hostname,
       ...

Then create the sender_checks file in the /etc/postfix directory:

nano sender_checks

Add the email addresses that you want to blacklist/whitelist like this:

contact@example3.com    REJECT
office@example4.net     OK

This will ensure that all the emails coming from contact@example3.com will be rejected, even if the example3.com domain is whitelisted in the /etc/postfix/client_checks file. It will also ensure that all the emails coming from office@example4.net will be accepted, even if the example4.net domain is blacklisted in the /etc/postfix/client_checks file or if the IP of the example4.net domain is included in a public blacklist like zen.spamhaus.org . Then run the postmap command:

postmap  sender_checks

Run the postmap command again each time you modify the /etc/postfix/sender_checks file.

For all the changes to take effect restart Postfix:

systemctl restart postfix

Please note that for the above settings to work correctly, the check_sender_access directive has to be placed before the check_client_access directive, and both of them must be placed before all the other directives in the smtpd_recipient_restrictions block.

19.20.2. Configuring alias email addresses

If you want all the emails coming to one email address to be automatically transferred to a different email address, you can do this by configuring an alias email address. For example you want all the emails received by support@somedomain.com to be sent automatically to office@anotherdomain.net. All you have to do is to use Postfix Admin to add support@somedomain.com as the alias of office@anotherdomain.net . So, log in to Postfix Admin at:

https://mail.example.com/net-pstfxdmn/public

Replace example.com with the main domain hosted on your server and net-pstfxdmn with the custom name of the Postfix Admin login page. Once logged in, go to ‘Domain List’, click on the domain of the email address that you want to set as alias, here it’s somedomain.com, next click on ‘Add Alias’, then, in the ‘Alias’ text box enter the first part of the email address that you want to set as alias, here support, then, in the ‘To’ text area enter one per line all the email addresses to which you want all the emails to be transferred; here we want all the emails to go to office@anotherdomain.net, so we’ll enter only office@anotherdomain.net . Then click on ‘Add Alias’. From now on, all the emails received by support@somedomain.com will be automatically transferred to office@anotherdomain.net.

19.20.3. Changing the “From”, “Return-Path” and “X-Sender” address of outgoing emails

A very important directive in Postfix settings is the reject_sender_login_mismatch directive located in the smtpd_recipient_restrictions block, in the /etc/postfix/main.cf file. This directive prevents logged in email clients (like Roundcube), to send emails with a ‘From’ address different from the email address that they are logged in with. Without this directive in place, any user that gets logged in to Postfix with a web application like Roundcube, can change the “From” address when sending an email, to literally anything that they want, even to nonexistent addresses with nonexistent domains. In this way they can send emails impersonating existing users on the same mail server or other users on other mail servers or nonexistent users. Thus, the reject_sender_login_mismatch has a very important role, by forcing users that log in with email clients like Roundcube, to use their real email address in the ‘From’ field. They can still change the ‘From’ address in their email client, but when they try to send the email, they will receive an error telling them that the server rejected the message.

However, there can be rare cases in which you want to replace the real ‘From’, as well as the ‘Return-Path’ and ‘X-Sender’ address, with a different email address that exists on the same server. This is called ‘address rewriting’. For example, you want all the emails sent from your server from the addresses info@domain.com to appear as being sent from office@otherdomain.net. In other words, you want Postfix to rewrite the ‘From’, ‘Return-Path’ and ‘X-sender’ address for all the emails sent from info@domain.com, so that they appear as being sent from office@otherdomain.net. You can also add office@otherdomain.net as the ‘Reply-To’ address to all the emails sent from info@domain.com. To be noted that all the three email addresses are hosted on your server and the emails are sent to external email addresses, not to local ones.

To rewrite the ‘From’ and ‘X-sender’ address and to add a ‘Reply-To’ address, first make sure that the following line is present at the bottom of the /etc/postfix/main.cf file:

header_checks = regexp:/etc/postfix/header_checks

Then edit the header_checks file:

nano /etc/postfix/header_checks

Enter the following lines at the bottom of this file:

/^From:.*info@domain.com/ REPLACE From: office@otherdomain.net
/^X-Sender:.*info@domain.com/ REPLACE X-Sender: office@otherdomain.net
/^From:.*info@domain.com/ PREPEND Reply-To: office@otherdomain.net

You can add multiple similar lines for different email addresses.

Then run the postmap command:

postmap /etc/postfix/header_checks

With these configurations you replaced the ‘From’ and ‘X-Sender’ address and added a ‘Reply-To’ address to all the emails sent from info@domain.com. However, the ‘Return-Path’ address, that can be seen by any recipient in the message’s source, remained info@domain.com . To change the ‘Return-Path’ address to office@otherdomain.net , first edit the /etc/postfix/main.cf file:

nano /etc/postfix/main.cf

Add smtp_generic_maps = hash:/etc/postfix/generic-maps right below smtpd_sender_login_maps = proxy:mysql:/etc/postfix/mysql-sender-login-maps.cf .

Then create the generic-maps file like this:

nano /etc/postfix/generic-maps

Enter the following content in this file:

info@domain.com  office@otherdomain.net

You can enter multiple similar lines for other email addresses. If you had wanted all the emails coming from the domain.com domain to have office@otherdomain.net as ‘Return-Path’ address, you would have entered:

@domain.com  office@otherdomain.net

Run the postmap command:

postmap /etc/postfix/generic-maps

Change ownership for the new files:

cd /etc/postfix
chown root:postfix generic-maps generic-maps.db

Restart Postfix:

systemctl restart postfix

Please keep in mind that the address rewriting configured as described above works only for emails sent to external email addresses. It won’t work for emails sent to email addresses hosted on the same server. Those emails will still appear as being sent from their real sending address.

19.21. Undeserved email deliverability issues

After you install the mail server as described above, you will want to send test emails to email addresses offered by different email providers, to see if they reach their target and are not considered spam. This is a way to test if the SPF, DKIM and DMARC records are effective in proving to external mail servers that the emails coming from your mail server are legitimate.

You can expect that all the big email providers will accept your emails in the ‘Inbox’ (where they belong), with the exception of Microsoft and Yahoo. Even if your mail server is perfectly configured and your server’s IP is not included on any public blacklist (or blocklist), it can still be included on private blacklists, without your fault. Big companies like Microsoft or Yahoo have their own private blacklists in which they include IPs and even entire IP ranges, based on criteria that they keep for themselves. Your IP can be included on such a private blacklist because in the past other clients used that IP to send spam, or just because it is part of a range of IPs that were identified as spam-sending IPs, or even because your IP is part of a range of IPs that have been known to be dynamically allocated and not static IPs. There may be also other reasons. So, even if you made absolutely no mistake and your mail server is perfectly configured, your emails can be automatically filtered as spam by Yahoo, or they can be even rejected by Microsoft, with an ‘Undelivered Mail Returned to Sender’ message similar to this:

hotmail-com.olc.protection.outlook.com[104.47.58.33] said: 550 5.7.1
    Unfortunately, messages from [123.123.123.123] weren't sent. Please contact
    your Internet service provider since part of their network is on our block
    list (S3150). You can also refer your provider to
    http://mail.live.com/mail/troubleshooting.aspx#errors.

where 123.123.123.123 is the IP of your server.

The first thing to do in this situation, is to try to delist your IP from the private blacklists of the two providers.

If your emails are rejected by ‘office.com’ email accounts, you can try to delist your IP on the delisting portal available at: https://sender.office.com . However, if your emails are rejected by ‘hotmail.com’/’live.com’/’outlook.com’ email accounts, or by ‘yahoo.com’ emails accounts, you should send a support request by filling a form, as shown below:

For Microsoft, the support form is:

https://support.microsoft.com/en-us/getsupport?oaspworkflow=start_1.0.0.0&wfname=capsub&productkey=edfsmsbl3&locale=en-us&ccsid=635900597399910848

For Yahoo, the support form is:

https://io.help.yahoo.com/contact/index?page=contactform&locale=en_US&token=Zh/BBVqXzLHlIbokbUqVWTUbuuQeXGkGnZzhKR2JQ4O6mMQdy9JSWdtWFXvjthcYCRj9bUIFfycOfG+4GOHPHoOGa8HwDO2+0kYRtTcdR8O13Mvs9cOruJ0TlC3hh4bCEtPlZ0yk7fvp1MFjGnAOWw==&selectedChannel=email-icon&guccounter=1

In these forms you should explain shortly that you rented the server recently, that you didn’t send any bulk emails, that you just sent test emails and that they should remove your IP from their blacklist, because all your emails are rejected / filtered as spam by their mail servers.

You will receive an automated answer after about one day. If they refuse to delist your IP you should reply and insist. If they suggest that you should join their Junk Email Reporting program (JMRP) and Smart Network Data Services program (SNDS), it’s recommended not to join. It won’t make any difference if you do. They can ask you to prove that you acquired the IP recently by attaching the first invoice from your hosting provider and the email that the hosting provider sent you when you signed up with them, confirming the opening of your account (you can copy this email in a PDF file). You can send them these documents after you remove your personal data from them. If they refuse to delist your IP, you can insist, stressing that you rented the server with that IP only a short time before and that you didn’t send any bulk emails.

If even after your explanations, they refuse to delist your IP, the next step is to open a support ticket with your hosting provider. You should explain the same things to them, insisting that you only sent test emails, that the server is perfectly configured and that other major email providers accept your emails in the Inbox. You should urge them to contact Microsoft/Yahoo support and ask that they delist your IP or the IP range to which your IP belongs, because this situation was not caused by any mistakes on your part and it impairs your email communication.

Depending on your experience with Microsoft and Yahoo support, you can even display a message similar to the following, on your newsletter subscription page (if you have one):

“We recommend that you sign up with an email address with a different domain than office.com, outlook.com, live.com, hotmail.com or yahoo.com. Apart from the privacy concerns that they arise, these types of email accounts are known for rejecting perfectly legitimate emails or tagging them as spam.”

It’s possible that even aol.com accounts can filter the emails coming from your server as spam, but after the user marks an email as ‘Not spam’ (by clicking on the OK sign on the upper bar), all the emails that will follow will land in the ‘Inbox’.

19.21.1. UCEPROTECTL3 blacklist

If you find that your server’s IP has been included on the UCEPROTECTL3 blacklist (http://www.uceprotect.net/en/rblcheck.php), you should know that this is not a serious blacklist and that it is not used for mail filtering by many system administrators. Wikipedia lists ‘UCEPROTECT-Network’ under ‘Suspect RBL providers’ here. However, it seems that it is used by Microsoft’s mail servers, which makes it problematic. If your IP is only included on the UCEPROTECT Level 3 blacklist and not on the Level 2 or Level 1 blacklists, it means that you didn’t send any spam but other IPs in the same range as your IP sent spam. By including your IP on the Level 3 blacklist, they try to make you complain to your hosting provider, and in this way, your hosting provider might feel pressured to take action against the spam-sending IPs. They also ask for money for every delisting from the UCEPROTECTL3 blacklist, in spite of the fact that the IP user didn’t do anything wrong. Also, users who payed for delisting confessed that their IPs were delisted, but that didn’t help, since their emails were still rejected by Microsoft. This way of asking for money in exchange for delisting IPs, which none of the reputable public blacklists practice, is suspect and unethical. Under no circumstance should you pay to have your IP delisted from a blacklist like UCEPROTECTL3.

The solution to this problem is to contact your hosting provider and describe the situation, pointing out that according to UCEPROTECT’s own words, you didn’t send any spam, but other IPs from the same range, did. Your hosting provider may stop the real spammers and this will cause the delisting of your IP, or the delisting may happen automatically, if no IPs in the same range as yours will send spam within a certain period of time.

You can send your questions and comments to: