ASCII Smiley Face Daniel Dickinson Mini Headshot
The C Shore

Mail Gateway HOWTO

Yet Another Mail Gateway HOWTO

Version 0.1.1 (pre-alpha)

Copyright © 2018 Daniel F. Dickinson

Released under Creative Commons Attribution Share-Alike 4.0 License


This is currently in ‘recipe’ format and doesn’t explain why or go into depth. Future plan for this doc is to be more detailed in those areas.

What you get

  • Incoming mail gateway (also can be a backup MX)
    • SMTP for incoming mail with queuing and forwarding to delivering MX when it is available.
    • We handle aliases to actual mailboxes
    • ‘actual mailboxes’ is handled by whatever handles the LMTP for recipients we’ve identified as having a mailbox and just being an alias.
  • Security
    • STARTTLS SSL when possible.
    • Only relay port 25 mail whose destination is the delivering MX.
    • Avoid being a backscatter source
      • Accept only mail for mailboxes known to the delivering mailhost or aliases shared among the gateway hosts (if more than one).
      • Sync known mailboxes and aliases with delivering mailhost (pushed to us when delivering mailhost is updated).
      • No bounces after mail accepted.
    • RBL antispam measures
    • Incoming DKIM, DMARC, and SPF (check if others’ mail is spoofed), bypass from VPN(?))
    • Outgoing DKIM, DMARC, and SPF (reducing opportunities for others to spoof us, if other servers check for these).
    • Use Let’s Encrypt HTTPS certificates from web server
    • Delivering mailhost only accessible via ‘Server VPN’ (doesn’t accept mail from random strangers nor serves delivered mail to strangers)
    • Transfers to final destination performed over VPN and LMTP TLS to delivering mailhost
    • When used as a Backup MX don’t relay through another MX, but deliver to delivering mailhost as above
      • Because we sync allowed aliases and mailboxes there is no reason to add the complication of going through a single point of failure (except of course the delivering mailhost, and in a large installation this too can be distributed among servers).
    • Outgoing mail gateway (point of origin load balances which gateway to use)
      • STARTTLS SSL when possible for outgoing mail
      • STARTTLS SSL required on the Submission port (i.e. the only port on which we allow mail to destinations other than our own domain(s))
      • SMTP AUTH required on the Submission port
      • Submission port only allowed for ‘Client VPN’ users
      • Outgoing DKIM, DMARC, and SPF (reducing opportunities for others to spoof us, if other servers check for these).
  • Admin
    • Some attack reduction and blocking
    • Some stats

Not in this document

Future work

  • DNSSEC in order to enable DANE

Out of Scope (i.e. Lots of Other Documentation Sources for These)

  • Initial host/instance setup
  • General admin utilities and convenience setup
  • Creating local certificates for private communication
  • Details on setting up OpenVPN for private channel

Deliberately Excluded

  • Bayesian spam filtering
    • For a personal server it makes more sense to take advantage of your client side spam filter because you won’t get all that much spam with this setup (the lightweight measures incorporated here take care of the majority of spam).
  • Virtual domains; for a personal server it’s overkill


  • An internet accessible host with static IP (and no history of abuse)
    • NB This is a pretty much a hard requirement for a gateway mx because we want to be sure to receive and send mail without being restricted.
  • DNS Service (such as, self-hosted, etc)
  • For this HOWTO: CentOS 7, 2 GiB RAM, 10 GiB HD (e.g. virtual HD)
  • Repos
    • Defaults + EPEL (to install epel do yum install epel-release)
  • Let’s Encrypt certificate and renewal mechanism (e.g. such as described in Yet Another Web Server HOWTO).


The following packages need to be installed for this setup (e.g. yum install package1 package2 ...)

Admin Tools

  • policycoreutils
  • policycoreutils-python

Mail Server

  • postfix


  • cyrus-sasl
  • cyrus-sasl-plain
  • cyrus-sasl-md5

Antispam and Antispoofing

  • opendkim
  • opendmarc

Virus Detection / Filtering

  • clamav
  • clamav-scanner
  • clamav-scanner-systemd
  • clamav-milter
  • clamav-update


  • awstats

Attack Detection / Blocking

  • fail2ban
  • fail2ban-firewalld
  • fail2ban-server


  • openvpn

First Steps

  1. Configure networking,admin users etc for your host/instance
  2. (Optional) Install your preferred admin/monitoring utilities etc.
  3. Install “Admin Tools” listed above
  4. Add ‘EPEL’ repository listed above

Mail Server Configuration

Prerequisites: * Let’s Encrypt certificate for * OpenVPN configured to allow the mail gateway to talk to the delivery mailhost (via LMTP) and for the local mailhost (usually the delivery host) to use talk the mail gateway to submit mail for relaying (i.e. the local mailhost is what clients will talk to and the local mailhost will talk to the gateway to send mail to to the internet). * Some guides for OpenVPN: * TBD

SMTP Inbound Preliminaries

  1. Install main “Mail Server” packages above
  2. Edit /etc/postfix/

    1. Set myhostname to your host’s hostname (e.g. myhostname =
    2. Set myorigin to $mydomain
    3. Set mydestination to the following – We don’t distinguish between mail from different subdomains in this HOWTO. If you need that you need to add virtual hosting parameters:

      $myhostname, localhost.$mydomain, localhost.localdomain, localhost, $mydomain, subdomain.$mydomain, ...
      1. Use unknown_local_recipient_reject_code = 550
      2. Use local_recipient_maps = $alias_maps /etc/postfix/rcpt_mailboxes
      3. Set mynetworks_style to host, and add:
        smtpd_relay_restrictions =
        smtpd_recipient_restrictions =
        smpd_data_restrictions = reject_unauth_pipelining
      4. Comment out home_mailbox
      5. Comment out mail_spool_directory
      6. Comment out mailbox_command
      7. Set mailbox_transport = lmtp:inet:<openvpn_ip_of_delivery_mailhost>:<port>
      8. Comment out smtpd_banner, and set: bash smtpd_tls_cert_file = /etc/letsenrypt/live/ smtpd_tls_key_file = /etc/letsencrypt/live/ smtpd_tls_security_level = may smtpd_tls_auth_only = yes
    4. For LMTP to the delivery mailhost you may wish to enable TLS as well. If so you, also need:

      lmtp_tls_CAfile = /path/to/possibly-private-CA.crt
      lmtp_tls_cert_file = /path/to/client-cert-on-possibly-private-CA.crt
      lmtp_tls_key_file = /path/to/client-key-on-possibly-private-CA.key
      lmtp_tls_security_level = encrypt

      Virus Detection / Filtering

      NB: To avoid becoming a backscatter source it is important that your Gateway MX does not accept mail your other gateways do not. That means using the same Antivirus/Antispam measures as in all your MXes, for the same users. Also you must not queue mail then bounce it. Mail should either be rejected at the SMTP stage, or delivered (even if only to to quarantine or junk mail folder).

      SELinux Tweaks

      1. Create a file clamavmilter.te (e.g. in /root) as below
        module clamavmilter 1.0;
        require {
            type antivirus_t;
            type milter_port_t;
            class tcp_socket { name_bind };
        #============= antivirus_t ==============
        allow antivirus_t milter_port_t:tcp_socket name_bind;
      2. Execute checkmodule -M -m clamavmilter.te -o clamavmilter.mod
      3. Perform semodule_package -o clamavmilter.pp -m clamavmilter.mod
      4. Run semodule -i clamavmilter.pp

      Antivirus Mail Filter (Milter)

      1. Install “Virus Detection / Filtering” packages from the list earlier in this document.
      2. Edit /etc/clamd.d/scan.conf with the following (in addition to defaults)
      3. Comment out Example
      4. LogFacility LOG_MAIL
      5. LocalSocket /var/run/clamd.scan/clamd.sock
      6. LocalSocketGroup virusgroup
      7. LocalSocketMode 660
      8. FixStateSocket yes
      9. PhishingScanURLs no
      10. ExitOnOOM yes
      11. And setsebool -P antivirus_use_jit on
      12. Run freshclam
      13. Execute systemctl enable clamd@scan && systemctl restart clamd@scan
      14. Edit /etc/mail/clamav-milter.conf with the following (in addition to defaults)
      15. Comment out Example
      16. MilterSocket inet:8894@localhost
      17. ClamdSocket unix:/var/run/clamd.scan/clamd.sock
      18. OnInfected Reject
      19. AddHeader Add
      20. LogFacility LOG_MAIL
      21. LogInfected Basic
      22. SupportMultipleRecipients yes
      23. Run semanage port --add -t milter_port_t -p tcp 8894
      24. Execute usermod -a -G clamscan clamilt
      25. Perform systemctl enable clamav-milter && systemctl restart clamav-milter
      26. To /etc/postfix/ add txt smtpd_milters = inet:localhost:8894 milter_default_action = tempfail
  3. Do postfix reload

Antispam / Antispoofing

  1. Install “Antispam / Antispoofing” packages from list earlier in this document.
  2. Edit /etc/opendkim.conf such that the following are set (in addition to defaults):
    1. ReportAddress " Postmaster" <>
    2. QueryCache yes
    3. Comment out KeyFile /etc/opendkim/keys/default.private
  3. Perform systemctl enable opendkim && systemctl start opendkim
  4. Add the following script as /etc/cron.daily/opendmarc_public_suffix_list and make it executable.

    /usr/bin/wget -q -N -P /etc/opendmarc
    /bin/chown opendmarc:opendmarc /etc/opendmarc/effective_tld_names.dat

  5. Edit /etc/opendmarc.conf such that the following are set (in addition to defaults):

    1. AuthservID HOSTNAME
    2. Autorestart true
    3. AutorestartRate 3/1m
    4. PublicSuffixList /etc/opendmarc/effective_tld_names.dat
    5. SyslogFacility mail
  6. Run systemctl enable opendmarc && systemctl start opendmarc

  7. To /etc/postfix/ add (assuming also doing antivirus above)

    smtpd_milters = inet:localhost:8894 inet:localhost:8891 inet:localhost:8893
    non_smtpd_milters = inet:localhost:8891 inet:localhost:8893
    milter_default_action = tempfail

  8. Do postfix reload

Firewall: Allow SMTP

  1. Execute the following:

    firewall-cmd --permanent --add-service smtp
    firewall-cmd --complete-reload

    DNS Setup

    • For IPv4 make sure you have DNS A records for your mail host e.g. points to (note this should not be a CNAME for the configuration in this HOWTO).
    • Also make sure reverse DNS entries are present: This is usually a service provided by your hosting provider (since they own the IP block and you can’t setup IP -> Name records without them) or ISP (if hosting on static IPs on an ISP that allows mail hosting on your own hardware).
    • Likewise for DNS for ipv6 if you support it (AAAA records in that case).
    • Assuming you already have an MX record for your primary MX, with priority < 20, create an an second DNS MX type entry for your domain (e.g. with contents 20 If you have any additional backup MXes, repeat, replacing the 20 with larger numbers (e.g. 30, 40, …). The number represents the priority for remote mail hosts sending mail to your domain, with lower numbers being higher priority (earlier in the ordering of hosts).
    • The details depend on the DNS provider or server you are using.

    SMTP Outbound Preliminaries

    NB If you wish outbound can be on a different server. Also note this configuration is predicated on the assumption you are using something like Dovecot’s Submission relay over a secure channel (i.e. not exposed to the internet at large). Dovecot in this scenario authenticates the user and it’s Dovecot that authenticates using the credentials below.

    1. Add the following to /etc/postfix/ bash smtp_tls_cert_file = /etc/letsenrypt/live/ smtp_tls_key_file = /etc/letsencrypt/live/ smtp_tls_security_level = may
  2. Create Submission user(s): If using Dovecot Submission relay auth is already done by Dovecot so you only need one user for Dovecot to auth to Postfix (for this HOWTO).

    1. Ensure /etc/sasl2/smtpd.conf exists as below (plain and login are required and smtpd_tls_auth_only ensures that they will only be used over encrypted channels).
      pwcheck_method: auxprop
      mech_list: PLAIN LOGIN CRAM-MD5 DIGEST-MD5
    2. Restart saslauthd: systemctl enable saslauthd && systemctl restart saslauthd
    3. Create a user: saslpasswd2 -c -u submission
    4. Execute chown root:postfix /etc/sasldb2
    5. Do chmod 640 /etc/sasldb2

SMTP (Outbound from SMTP AUTH users only)

Authentication against the Dovecot user database (below) is required via SMTP AUTH in order to send emails to the outside world.

  1. Execute semanage port --add -t smtp_port_t -p tcp 32841 This way we’re not exposing the normal submission port which avoids a lot of log spam from unsolicited logins.
  2. To /etc/postfix/, below the line with #tlsproxy ... add:

    32841 inet n       -       n       -       -       smtpd
      -o syslog_name=postfix/submission
      -o smtpd_sasl_path=sasl2/smtpd.conf
      -o smtpd_tls_security_level=encrypt
      -o smtpd_sasl_auth_enable=yes
      -o smtpd_sasl_security_options=noanonymous,noplaintext
      -o smtpd_sasl_tls_security_options=noanonymous
      -o smtpd_sasl_local_domain=$mydomain
      -o smtpd_client_restrictions=permit_sasl_authenticated,reject
      -o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject
      -o smtpd_relay_restrictions=permit_sasl_authenticated,reject_unauth_destination
    1. Do firewall-cmd --permanent --new-service=altsubmission
    2. Do firewall-cmd --permanent --service=altsubmission --add-port=32841/tcp
    3. Execute firewall-cmd --permanent --zone=work --add-service=altsubmission
    4. Run firewall-cmd --complete-reload

    Antispoofing (Outbound)

    In order to help other servers ensure they are only accepting legitimate mail from your server(s), you should enable SPF and DKIM signing of outbout mail.

    1. Enable SPF for your domain. This is a DNS exercise. I recommend reading the various documents under “Deploying SPF” on the Open SPF Site
    2. Enable DKIM signing for your outbound mail gateway:
      1. cd /etc/opendkim/keys
      2. Execute opendkim-genkey -s $(hostname -s) --domain=$(hostname -d) --testmode (assuming you want your selector to by your gateway’s short name (e.g. mail2) and you are doing this for the domain your host reports by default (e.g. the results of hostname -d). Testmode reduces likeliehood you mail will be rejected until your remove the “t=y;” from your DNS TXT record (see below)
      3. Assuming your hostname is you will have two files in this directory, named and mail2.txt. mail2.private is your private key for DKIM signing and needs restrictive permissions (the generator does this by default).
      4. chown opendkim:opendkim /etc/opendkim/keys/mail2.private
      5. Edit /etc/opendkim.conf as follows:
        1. Set Mode sv
        2. Set Domain (obviously this should be your domain)
        3. Set Selector mail2
        4. Set KeyFile /etc/opendkim/keys/mail2.private
      6. Execute systemctl restart opendkim
      7. At your DNS provider create a TXT record name mail2._domainkey with the part of the contents of mail2.txt which looks similar to
        "v=DKIM1; k=rsa; t=y; "
        NB If your provider only accepts a single line for TXT records, use a record such as:
        v=DKIM1; k=rsa; t=y; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHY7Zl+n3SUldTYRUEU1BErHkKN0Ya52gazp1R7FA7vN5RddPxW/sO9JVRLiWg6iAE4hxBp42YKfxOwEnxPADbBuiELKZ2ddxo2aDFAb9U/lp47k45u5i2T1AlEBeurUbdKh7Nypq4lLMXC2FHhezK33BuYR+3L7jxVj7FATylhwIDAQAB"
      8. Send a test email to
      9. If everything checks out okay, remove t=y; from your DNS record and (after TTL expired) send one more verification test.

    Stats Configuration

    Prerequisites: AWStats non-CGI setup such as the one described in Yet Another Web Server HOWTO

    AWStats Configuration

    1. In /etc/awstats
    2. For mail server (SMTP) log stats
      1. cp awstats.localhost.localdomain.conf awstats.mail2.conf
      2. Set LogFile="/usr/share/awstats/tools/ standard < /var/log/maillog* |
      3. Set LogType=M
      4. Set the following:
        LogFormat="%time2 $email %email_r %host %host_r %method %url %code %bytesd"
      5. Comment out DirIcons=/awstatsicons

    Cron Configuration

    1. To /etc/cron.hourly/awstats, add:
      /usr/share/awstats/tools/ -config=mail2 -update -configdir="/etc/awstats" -awstatsprog="/usr/share/awstats/wwwroot/cgi-bin/ -dir=/var/www/vhosts/" >/dev/null
    2. And for full stats for the entire year, to /etc/cron.daily/awstats, add:
      /usr/share/awstats/tools/ -config=mail2 -update -month=all -configdir="/etc/awstats" -awstatsprog="/usr/share/awstats/wwwroot/cgi-bin/ -dir=/var/www/vhosts/" >/dev/null

    Attack Detection/Blocking

    1. To /etc/fail2bain/jail.local add the following:

      port = smtp,submission,32841
      enabled = true
      port = smtp,submission,32841
      enabled = true
      port = smtp,submission,32841
      enabled = true

    2. And finally execute

      systemctl enable fail2ban && systemctl restart fail2ban