------------------------------------------- * **mailserver** * **Jonathan Haack** * **Haack's Networking** * **webmaster@haacksnetworking.org** ------------------------------------------- //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 and make sure you or your ISP has set a PTR record to mail.example.org for the IPv4 and IPv6 addresses. 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 25/tcp sudo ufw allow 587/tcp sudo ufw allow 143/tcp sudo ufw allow 465/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'': After that, open ''sudo nano /etc/dovecot/conf.d/10-mail.conf'' and change the default mail director location as follows: Let's make sure dovecot is part of the mail group, including any users you intend to use email: sudo adduser dovecot mail sudo adduser username mail Now we can configure dovecot over at ''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 = 15 [or desired score] OPTIONS="${OPTIONS} -r 15" Again, I do not use the reject or discard options but rather leverage spam assassin's scoring and header assessing together with dovecot, which can move emails to locations fitting their scores. There are two fundamental ways to configure the scoring and sieve rules, either with the CLI or with Roundcube. When I first began, I used the CLI and created a simple rule in ''sudo nano /etc/dovecot/conf.d/90-sieve.conf'' and entered this block: sieve_before = /var/mail/SpamToJunk.sieve The sieve_before rule ensures spam assassin assesses the email right after they arrive and before sending them to dovecot and its configured delivery agents. Let's open ''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 This rule does one thing. It checks whether spam assassin identified the message as spam, and if so, it uses dovecot to file it in Junk. This means your spam assassin scores and config are what drive the success rate of this sieve rule. It's also important to note that this rule is global, and impacts all user names on the mail server. It's a good approach for the most heinous spam, leaving more customized rules to roundcube's sieve implementation, later in this tutorial. Let's open ''sudo nano /etc/spamassassin/local.cf'' and adjust it as follows. report_contact webmaster@domain.com #adjust score below to your use-case required_score 5.0 #rewrite_header Subject **Possible Spam** report_safe 0 add_header all Spam-Flag _YESNO_ add_header all Score _SCORE_ add_header all Report _REPORT_ add_header all Level _STARS_ add_header all Status "_YESNO_, score=_SCORE_ required=_REQD_ tests=_TESTS_ autolearn=_AUTOLEARN_ version=_VERSION_" add_header all Checker-Version "SpamAssassin _VERSION_ (_DATE_) on _HOSTNAME_" #legacy/deprecated header config - do not use, retained for historical record #always_add_headers = 1 I included some header options, which can help with debugging. Also, I disable safe reporting and Subject rewriting because they alter the original email, which I think is overkill. In order to activate all that spam assassin can do, we need to have our own recursive DNS resolver, required by RBL services. Let's use the DNS server unbound and install it as follows ''sudo apt install unbound''. It works out of the box, but you can also tweak it by looking at my tutorial here: [[https://wiki.haacksnetworking.org/doku.php?id=computing:unbounddns|Unbound DNS]]. Okay, let's now insruct spamassassin to use our dns server by opening ''sudo nano /etc/spamassassin/local.cf'' and entering the DNS server. We will also add some common scores and white and black lists while at it. 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 based on the headers you see in the full message source. Make sure to compile the sieve file with ''sievec'' each time you adjust the config, and restart postfix and dovecot. This approach above is good to do for egregiously bad email, but individual users will likely need their own controls. So, for larger servers, you can alternately use Roundcube instead. Remember, you must pick one or the other because the ''sieve_before'' rules above will bypass Roundcube's sieve logic. Bearing this in mind, if you want to use Roundcube for sieve rules, let's navigate to roundcube > settings > filters > edit filter set. To replicate similar functionality as above, I created the following: 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; } These rules are processed sequentially. Monitor the Junk folder periodically and refine whitelists as needed. If something escapes, like a full health dirty marketing scam, adjust your blacklist. That's all there is to it. Now that spam controls are setup, we need to setup some auditing tools to monitor how well our server is doing these tasks. For postfix, that tool is pflogsumm. Let's install it with ''sudo apt install pflogsumm'' and let's use rsyslog and log rotate to manage the logs, requiring us to also install rsyslog with ''sudo apt install rsyslog'' Disable the ''/var/log/mail.log'' entries that are in the ''rsyslog'' logrotate rule in ''/etc/logrotate.d/''. This is because we are going to make our own logrotate rule that works nicely with pflogsumm. Create the file ''sudo nano postfix-log'' inside ''/etc/logrotate.d/'' and 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 by creating a file ''sudo nano /usr/local/bin/pflog-run.sh'' and entering something like: #!/bin/sh #/usr/sbin/logrotate -f /etc/logrotate.d/postfix-log [helpful for manual testing] 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 You can also use the ''-d yesterday'' flag in pflogsumm and wildcard your domains, which I later found out. But, this works too and I retain its use in my production servers. This ends our primary configuration. If you don't have Roundcube setup, look here: [[https://wiki.haacksnetworking.org/doku.php?id=computing:roundcube|Roundcube Tutorial]]. The rest of this tutorial is miscellaneous information that has come up along the way. ------------------------------------------- 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 To setup autodiscovery, setup a separate vhost in apache with autodiscover.domain.com, and then create your A, AAAA, and discovery records: _imap._tcp 10 1 143 mail.haacksnetworking.org _submission._tcp 10 1 587 mail.haacksnetworking.org _imaps._tcp 0 1 993 mail.haacksnetworking.org _submissions._tcp 0 1 465 mail.haacksnetworking.org _autodiscover._tcp 10 1 443 mail.haacksnetworking.org autodiscover A 8.28.86.125 autodiscover AAAA 2604:fa40:0:10::18 After that, setup your ''autodiscover.xml'' file: sudo nano /var/www/autodiscover.haacksnetworking.org/public_html/autodiscover/autodiscover.xml Inside that file, enter something similar to this entry below, obviously adjusting for your priority, weight, and desired client configuration behavior: email settings IMAP mail.haacksnetworking.org 993 %EMAILADDRESS% haacksnetworking.org SSL IMAP mail.haacksnetworking.org 143 %EMAILADDRESS% haacksnetworking.org STARTTLS SMTP mail.haacksnetworking.org 465 %EMAILADDRESS% haacksnetworking.org SSL SMTP mail.haacksnetworking.org 587 %EMAILADDRESS% haacksnetworking.org STARTTLS Pretty much everything one needs is now setup. To check record health after you set your DNS records, you can do the following: 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 You should test email health with the CLI and/or use a service like [[https://www.mail-tester.com/|Mail Tester]]. I recommend using both CLI to send email and a common client. Both domain.com and mail.domain.com should work if you set everything right. Here's how to send a simple email at the CLI: echo "Hi, I am testing the subdomain email health." | mail -s "CLI Email Test" oemb1905@jonathanhaack.com Postfix has its own CLI control tools, such as but not exclusive to viewing email, deleting email, etc.: mailq postcat -q E900C4780073 postsuper -d E900C4780073 postsuper -d ALL These tools prove helpful if/when emails get stuck, etc. --- //[[alerts@haacksnetworking.org|oemb1905]] 2025/05/15 19:19//