This is an old revision of the document!
mailserver
This tutorial is for users of Debian GNU/Linux who want to set up a proper email server.. This tutorial assumes you know how to set up A, AAAA, SPF, DKIM, DMARC, MX, and PTR records. Set an A record for example.org and mail.example.org. If you don't know how, then learn up, and do not proceed. Thanks to LinuxBabe for a great jumping off point. Let's begin by editing our hosts file sudo nano /etc/hosts
as follows:
127.0.1.1 example.org example 127.0.0.1 mail.example.org localhost
Install postfix and mailutils sudo apt-get install mailutils postfix -y
picking Internet Site
and set your domain to example.org
.
Install firewall, open common ports for front facing website, and for imap/smtp:
sudo apt install ufw sudo ufw allow 22/tcp sudo ufw allow 53/tcp sudo ufw allow 25/tcp sudo ufw allow 587/tcp sudo ufw allow 143/tcp sudo ufw allow 993/tcp sudo ufw allow 80 sudo ufw allow 443
Increase quota / message size:
sudo postconf -e message_size_limit=52428800
Set hostname and aliases in sudo nano /etc/postfix/main.cf
and make sure that the hostname, origin, destination, mailbox size, and quota are set. Also, in my case, I only have ipv4 support, so I explicitly set that as well.
myhostname = mail.example.com myorigin = /etc/mailname mydestination = example.com, $myhostname, localhost.$mydomain, localhost mailbox_size_limit = 0 inet_protocols = ipv4 message_size_limit = 52428800
Let's also make sure that system emails are sent to the user we created above instead of root by sudo nano /etc/aliases
and then:
postmaster: root root: user
Now, set up the server block for your mail server's website:
sudo nano /etc/nginx/conf.d/mail.example.com.conf sudo mkdir -p /usr/share/nginx/html/
The contents looking something like:
server { listen 80; #listen [::]:80; server_name mail.example.com; root /usr/share/nginx/html/; location ~ /.well-known/acme-challenge { allow all; } }
Once that is done, restart the service sudo systemctl reload nginx
and then let's generate a cert:
sudo apt install certbot sudo apt install python3-certbot-nginx sudo certbot certonly -a nginx --agree-tos --no-eff-email --staple-ocsp --email email@email.com -d mail.example.com
Now, let's configure postfix to work together with Dovecot/submission on 587 and 465 and to use TLS by editing sudo nano /etc/postfix/master.cf
as follows:
submission inet n - y - - smtpd -o syslog_name=postfix/submission -o smtpd_tls_security_level=encrypt -o smtpd_tls_wrappermode=no -o smtpd_sasl_auth_enable=yes -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_sasl_type=dovecot -o smtpd_sasl_path=private/auth smtps inet n - y - - smtpd -o syslog_name=postfix/smtps -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_sasl_type=dovecot -o smtpd_sasl_path=private/auth
It's now time to configure postfix sudo nano /etc/postfix/main.cf
to use TLS:
#Enable TLS Encryption when Postfix receives incoming emails smtpd_tls_cert_file=/etc/letsencrypt/live/mail.example.com/fullchain.pem smtpd_tls_key_file=/etc/letsencrypt/live/mail.example.com/privkey.pem smtpd_tls_security_level=may smtpd_tls_loglevel = 1 smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache #Enable TLS Encryption when Postfix sends outgoing emails smtp_tls_security_level = may smtp_tls_loglevel = 1 smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache #Enforce TLSv1.3 or TLSv1.2 smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
Now, let's configure and enable SASL support. Open /etc/postfix/main.cf
and enter:
# SASL Authentication with Dovecot smtpd_sasl_auth_enable = yes smtpd_sasl_type = dovecot smtpd_sasl_path = private/auth smtpd_sasl_security_options = noanonymous
Now, we can install dovecot and configure it to use IMAP and lmtp. Install the packages with sudo apt install dovecot-core dovecot-imapd dovecot-lmtpd
and then edit sudo nano /etc/dovecot/dovecot.conf
:
<protocols = imap lmtp>
After that, open sudo nano /etc/dovecot/conf.d/10-mail.conf
and change the default mail director location as follows:
<mail_location = maildir:~/Maildir>
Let's make sure dovecot is part of the mail group with sudo adduser dovecot mail
and now we can configure dovecot with sudo nano /etc/dovecot/conf.d/10-master.conf
in order to be able to leverage lmtp:
service lmtp { unix_listener /var/spool/postfix/private/dovecot-lmtp { mode = 0600 user = postfix group = postfix } }
Similarly, we need to edit postfix for lmtp as well with sudo nano /etc/postfix/main.cf
and then specifying:
mailbox_transport = lmtp:unix:private/dovecot-lmtp smtputf8_enable = no
Next, let's configure dovecot authorization with sudo nano /etc/dovecot/conf.d/10-auth.conf
plain login as follows:
disable_plaintext_auth = yes auth_username_format = %n auth_mechanisms = plain login
Now, configure SSL/TLS encryption in dovecot using your website/domain certs from earlier with sudo nano /etc/dovecot/conf.d/10-ssl.conf
:
ssl = required ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem ssl_key = </etc/letsencrypt/live/mail.example.com/privkey.pem ssl_prefer_server_ciphers = yes ssl_min_protocol = TLSv1.2
Set up the SASL listener by editing sudo nano /etc/dovecot/conf.d/10-master.conf
and adding this block:
service auth { unix_listener /var/spool/postfix/private/auth { mode = 0660 user = postfix group = postfix } }
If you have errors or can't connect your email client at this point, you can test your handshakes as follows:
openssl s_client -connect mail.example.com:465 openssl s_client -starttls smtp -connect mail.example.com:25
Now it is time to setup an spf policy agent so that the incoming email that is received checks for validity of spf records. Do not confuse this with creating an spf TXT record for your outgoing email. Let's install spf policy with sudo apt install postfix-policyd-spf-python
and then edit sudo nano /etc/postfix/master.cf
as follows:
policyd-spf unix - n n - 0 spawn user=policyd-spf argv=/usr/bin/policyd-spf
After that, let's set up sudo nano /etc/postfix/main.cf
as follows:
policyd-spf_time_limit = 3600 smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service unix:private/policyd-spf
Now, it is time to set up DKIM on your server. After creating the DKIM record/key on your server, you will need to create a corresponding TXT record for it to establish that anything over smtp with that signature is, in fact, you/your server. Let's install opendkim with sudo apt install opendkim opendkim-tools
and add postfix to its group with sudo adduser postfix opendkim
and then adjust the configuration in sudo nano /etc/opendkim.conf
as follows:
Canonicalization relaxed/simple Mode sv SubDomains no Nameservers 8.8.8.8,1.1.1.1 KeyTable refile:/etc/opendkim/key.table SigningTable refile:/etc/opendkim/signing.table ExternalIgnoreList /etc/opendkim/trusted.hosts InternalHosts /etc/opendkim/trusted.hosts
Now that the configuration for DKIM is ready, let's create the keys and content for the locations specified above:
sudo mkdir -p /etc/opendkim/keys sudo chown -R opendkim:opendkim /etc/opendkim sudo chmod 711 /etc/opendkim/keys
Once all the directories and key locations are created, let's open the signing table with sudo nano /etc/opendkim/signing.table
and enter the following:
Now that the signing table is setup, we need to edit the key table with sudo nano /etc/opendkim/key.table
and enter the following:
default._domainkey.example.com example.com:default:/etc/opendkim/keys/example.com/default.private
The trusted hosts is next, over in sudo nano /etc/opendkim/trusted.hosts
which we simply enter:
.domain.com
We now need to cut the DKIM keys (and make sure to add TXT records on your DNS host later) as follows:
sudo mkdir /etc/opendkim/keys/example.com sudo opendkim-genkey -b 2048 -d example.com -D /etc/opendkim/keys/example.com -s default -v sudo chown opendkim:opendkim /etc/opendkim/keys/example.com/default.private sudo chmod 600 /etc/opendkim/keys/example.com/default.private
To get the information you need for the DNS record, you can run sudo cat /etc/opendkim/keys/example.com/default.txt
and then copy everything between the parentheses into your TXT record with default._domainkey
as the host. After the DKIM TXT record caches, test it as follows:
sudo opendkim-testkey -d example.com -s default -vvv
Note that that output will display “key not secure” unless you configure DNSSEC, which this tutorial has not done. It's now time to configure postfix to leverage this DKIM key.
sudo mkdir /var/spool/postfix/opendkim sudo chown opendkim:postfix /var/spool/postfix/opendkim sudo nano /etc/opendkim.conf <Socket local:/var/spool/postfix/opendkim/opendkim.sock> sudo nano /etc/default/opendkim <SOCKET="local:/var/spool/postfix/opendkim/opendkim.sock"> sudo nano /etc/postfix/main.cf <milter_default_action = accept> <milter_protocol = 6> <smtpd_milters = local:opendkim/opendkim.sock> <non_smtpd_milters = $smtpd_milters>
It's now a good time to test your email quality with Mail Tester to see if you got a 10/10 score. When upgrading postfix on the server, select “No configuration” as otherwise it will overwrite the configurations above. If you need help with creating spf, dmarc, or dkim TXT records, see spfdkim. Another optional setting is to reject incoming email that lacks a PTR (reverse DNS) record.
sudo nano /etc/postfix/main.cf <smtpd_sender_restrictions => <permit_mynetworks,> <permit_sasl_authenticated,> <reject_unknown_reverse_client_hostname,> <reject_unknown_client_hostname,> <reject_unknown_sender_domain,> <reject_unauthenticated_sender_login_mismatch,> <reject_sender_login_mismatch> <permit>
To set up email header and/or body checks to prevent spam:
sudo apt install postfix-pcre sudo nano /etc/postfix/main.cf <header_checks = pcre:/etc/postfix/header_checks> <body_checks = pcre:/etc/postfix/body_checks>
You will then need to configure the files with whatever strings you expect spam headers or bodies to have, and either reject them and/or discard them. You will also need to rebuild the indexes. You may optionally set up dmarc verification and reporting with openDMARC.
sudo apt install opendmarc <no to db configure> sudo nano /etc/opendmarc.conf <AuthservID OpenDMARC> <TrustedAuthservIDs mail.yourdomain.com> <RejectFailures true> <IgnoreAuthenticatedClients true> <SPFSelfValidate true> <Socket local:/var/spool/postfix/opendmarc/opendmarc.sock> sudo mkdir -p /var/spool/postfix/opendmarc sudo chown opendmarc:opendmarc /var/spool/postfix/opendmarc -R sudo chmod 750 /var/spool/postfix/opendmarc/ -R sudo adduser postfix opendmarc sudo systemctl restart opendmarc
Now, configure postfix to work with openDMARC. Add the openDMARC socket to the milter block you created earlier.
sudo nano /etc/postfix/main.cf <milter_default_action = accept> <milter_protocol = 6> <smtpd_milters = local:opendkim/opendkim.sock,local:opendmarc/opendmarc.sock> <non_smtpd_milters = $smtpd_milters> sudo systemctl restart postfix
This about covers everything. The only missing part is how to get past picky microsoft users and/or automate or simplify account creation. Okay, to view and/or delete messages from postfix mailq:
mailq postcat -q E900C4780073 postsuper -d E900C4780073 postsuper -d ALL
If you have issues, it's good to be familiar with some different uses of the dig
command to test your records. Here's how to check dmarc, dkim, spf, and ptr. The +short
is optional, of course. I also included how you can verify your dkim key as well.
dig txt +short _dmarc.jonathanhaack.com dig txt +short _dmarc.haacksnetworking.org dig default._domainkey.jonathanhaack.com txt dig default._domainkey.haacksnetworking.org txt dig txt +short jonathanhaack.com dig txt +short haacksnetworking.org dig -x 8.28.86.130 +short dig -x 8.28.86.125 +short sudo opendkim-testkey -d jonathanhaack.com -s default -vvv sudo opendkim-testkey -d haacksnetworking.org -s default -vvv
Also, please note that the above applies to clients connecting to the domain. If you intend to also host websites/content on the same host as the mail server, then you will also need to set up dmarc, spf, and mx records for the subdomain, mail.example.com. You will not need to setup dkim nor change the PTR. To test the validity of the command line email set up, ssh into your server and send an email as follows:
echo "Hi, I am testing the subdomain email health." | mail -s "CLI Email Test" oemb1905@jonathanhaack.com
It's also common to auto = create
mission-critical folders in IMAP.
sudo nano /etc/dovecot/conf.d/15-mailboxes.conf
An example block:
mailbox Drafts { auto = create special_use = \Drafts }
Simply add the auto = create
to whichever directories you need. Some smtp servers and/or MTAs capitalize the user name of your email without permission. To avoid having your server reject these, please add the following to `main.cf.`
virtual_alias_maps = regexp:/etc/postfix/virtual_alias
From there, you can configure aliases one by one or with regex. For example, I could either do all combinations of oemb (line 1-2, etc.), or the regex for all of them (line 3):
/^OEMB1905/ oemb1905 /^Oemb1905/ oemb1905 [and so on ...] /^[Oo][Ee][Mm][Bb]1905/ oemb1905
When finished configuring, run postmap /etc/postfix/virtual_alias
to honor the changes. Alright, to stop spam we are going to use a mix of spamassassin and dovecot-sieve. Let's set up dovecot-sieve first and alert dovecot's delivery agents to its existence.
sudo apt install dovecot-sieve dovecot-managesieved sudo nano /etc/dovecot/dovecot.conf
Set to:
protocols = imap lmtp sieve
Then, open
sudo nano /etc/dovecot/conf.d/15-lda.conf
Set to:
protocol lda { mail_plugins = $mail_plugins sieve }
Finally,
sudo nano /etc/dovecot/conf.d/20-lmtp.conf
Which should be:
protocol lmtp { mail_plugins = quota sieve }
Once that's done, let's enable regular expression checking of headers and the email body within postfix. I personally don't use these, but I keep them active just in case.
sudo apt install postfix-pcre sudo nano /etc/postfix/main.cf <header_checks = pcre:/etc/postfix/header_checks> <body_checks = pcre:/etc/postfix/body_checks>
This enables regular expression checking on headers and in the email body. You can enter REJECT or DISCARD as follows:
sudo nano /etc/postfix/header_checks </free mortgage quote/ REJECT> sudo nano /etc/postfix/body_checks </free mortgage quote/ DISCARD> sudo postmap /etc/postfix/body_checks sudo postmap /etc/postfix/header_checks
Now it's time to setup spamassassin.
sudo apt install spamassassin spamc spamass-milter sudo systemctl enable spamassassin sudo systemctl start spamassassin sudo nano /etc/postfix/main.cf <smtpd_milters = local:opendkim/opendkim.sock,local:opendmarc/opendmarc.sock,local:spamass/spamass.sock>
The milter line above depends on one's setup. This works for me because I also use opendkim and opendmarc. Adjust as needed.
sudo nano /etc/default/spamass-milter <Make sure the REJECT line is commented out, otherwise false positives won't get delivered.>
If you prefer to reject then uncomment that line. You may also optionally add a custom message:
<sudo nano /etc/default/spamass-milter> OPTIONS="-u spamass-milter -i 127.0.0.1 -R SPAM_ARE_NOT_ALLOWED_HERE"
Now that dovecot's sieve tool is set up, we can instruct dovecot to use a sieve rule that assesses spam assassin's headers and moves the message to Junk if it meets the specified criteria.
sudo nano /etc/dovecot/conf.d/90-sieve.conf <sieve_before = /var/mail/SpamToJunk.sieve>
Open the sieve rule with sudo nano /var/mail/SpamToJunk.sieve
and enter the following:
require "fileinto"; if header :contains "X-Spam-Flag" "YES" { fileinto "Junk"; stop; }
After creating the sieve rule, compile it.
sudo sievec /var/mail/SpamToJunk.sieve
[DO NOT DO THIS] Now we can alert postfix to check our spamassassin policy on our behalf. In the /etc/postfix/main.cf
section titled smtpd_recipient_restrictions
add the following line: [DO NOT DO THIS]
<check_policy_service unix:private/spamassassin>
[DO NOT DO THIS] And over in /etc/postfix/master.cf
enter the following at the bottom: [DO NOT DO THIS]
spamassassin unix - n n - - pipe user=debian-spamd argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}
We can now configure some custom scores and white/black lists as needed so sudo nano /etc/spamassassin/local.cf
. I've included the default and explicit header lines as well, which can help with debugging. Specifically, SA will not give a score breakdown on ham unless those are specified.
report_contact webmaster@domain.com required_score 5.0 #rewrite_header Subject **Possible Spam** report_safe 0 always_add_headers = 1 #add_header all Flag _YESNO_ #add_header all Status "_YESNO_, score=_SCORE_ required=_REQD_ tests=_TESTS_ autolearn=_AUTOLEARN_ version=_VERSION_" #add_header all Report _REPORT_ #add_header all Level _STARS_ #add_header all Checker-Version "SpamAssassin _VERSION_ (_DATE_) on _HOSTNAME_"
Before we enter the custom scores and white and black lists, let's install and enable unbound, so that spam-assassin can query the RBLs. RBLs do not allow upstream DNS queries, thus we are required to run our own recursive resolver here. Take care not to expose this publicly. I prefer unbound, but one can also use bind9 or others. Specify the DNS in the local.cf
and create your rules/lists:
sudo apt install unbound sudo nano /etc/spamassassin/local.cf <dns_server 127.0.0.1> <score MISSING_FROM 5.0> <score MISSING_DATE 5.0> <score MISSING_HEADERS 3.0> <score PDS_FROM_2_EMAILS 3.0> <score FREEMAIL_FORGED_REPLYTO 3.5> <score DKIM_ADSP_NXDOMAIN 5.0> <score FORGED_GMAIL_RCVD 2.5> <score FREEMAIL_FORGED_FROMDOMAIN 3.0> <score HEADER_FROM_DIFFERENT_DOMAINS 3.0> <score FREEMAIL_FROM 3.0> <score ACCT_PHISHING 3.0> <score AD_PREFS 3.0> <score ADMAIL 3.0> <score ADMITS_SPAM 3.0> <score CONFIRMED_FORGED 3.0> <score FROM_PAYPAL_SPOOF 3.0> <score SPF_SOFTFAIL 2.0> <score SPF_FAIL 5.0> <whitelist_from *@statefarm.com> <blacklist_from *@email.freethinkerdaily.com>
Additionally, if you check the full headers, you will see that the RBLs can now be queried without issue. Note that whitelisting adds a -100 score and that blacklisting adds a +100 score. To understand how to tweak the symbolic headers better, one should review their spam and headers periodically and update rules. It is also possible to manage behaviors for whitelist, blacklist, and spam scores after dovecot receives them and from Roundcube. This would be instead of managing them with spam assassin at the CLI before dovecot receives them. To do that, comment out white and black list rules, and remove the sieve_before
rule we created above. Navigate to roundcube > settings > filters > edit filter set. To replicate the same functionality as above, I created the following:
#open roundcube, click the Edit filter set #copy paste this into the block, replace everything require ["fileinto"]; # rule:[whitelist] if anyof ( header :contains "from" [ "noreply@dmarc.yahoo.com", "noreply@dmarc.google.com", "Friend@protonmail.com" ] ) { keep; stop; } # rule:[blacklist] if anyof ( header :contains "from" [ "awakening-minds.com", "porn@yahoo.com", "bounce-1.public.govdelivery.com" ] ) { fileinto "Junk"; stop; } # rule:[spamcheck] if anyof ( header :contains "x-spam-status" "Yes", header :contains "x-spam-flag" "YES", header :contains "x-spam-level" "*****" ) { fileinto "Junk"; stop; }
Additionally, the best way to monitor your server is with pflogsum.
sudo apt install pflogsumm sudo apt install rsyslog
Disable the /var/log/mail.log
entries that are in rsyslog
and any others that are located in /etc/logrotate.d/
. Once that's done, create a new rule as follows:
sudo nano postfix-log
In that file, enter the following:
/var/log/mail.log { missingok daily rotate 7 create compress start 0 }
Once that's done, let's create script and cronjob to send us daily reports:
sudo nano /usr/local/bin/pflog-run.sh
In that script, enter something like this:
#!/bin/sh #/usr/sbin/logrotate -f /etc/logrotate.d/postfix-log gunzip /var/log/mail.log.0.gz /usr/sbin/pflogsumm /var/log/mail.log.0 --problems-first --rej-add-from --verbose-msg-detail -q | mail -s "[pflog-lastlog]-$(hostname -f)-$(date)" email@email.com gzip /var/log/mail.log.0 sleep 2s systemctl restart rsyslog systemctl restart postfix systemctl restart dovecot exit 0
The key here is that your script and zip and unzipping rules match the retention and naming conventions specified in logrotate. Since I floored the rotation at 0, the script always unzips the 0.gz
log. This is why it is preferable and easier to remove the mail.log
stanza from the other rotations. This allows one to easily customize it for email logs without messing with other rotations and settings. Once that's done, set up a cronjob and you are all set.
30 12 * * * /bin/bash /usr/local/bin/pflog-run.sh >> /home/logs/pflog-run.log
In extreme cases, directly block problem domains in postfix in /etc/postfix/main.cf
by adding reject_sender example.com
within the smtpd_sender_restrictions =
block. Make sure to add all users to the mail group.
sudo adduser username mail
The SASL module packages should be brought in as dependencies of postfix and/or dovecot. However, on upgrades, etc., they might be removed during dependency resolution. If you get “no sasl” report on your logs suddenly, despite everything working prior, use:
sudo apt-get install libsasl2-modules
If/when things are going wrong, turn on your detailed debugging logs and study them:
nano /etc/dovecot/conf.d/10-logging.conf <mail_debug yes>
— oemb1905 2025/04/08 13:16