Own mail server based on Dovecot, Postfix, MySQL, Rspamd and Debian 9 Stretch https://thomas-leister.de/en/mailserver-debian-stretch/ Mail server features To get a basic idea of what this mail server will be capable of: Sending and receiving e-mails for various domains of your choice Adding and removing domains, accounts, aliases, redirects and TLS policies via a MySQL DB backend Per-user mailbox quota Global and user-specific sieve rules High performance spam server detection via postscreen Extended spam message detection via Rspamd (+ web interface for spam stats) “Send only” accounts e.g. for NextCloud / blog software / … DKIM-signing for outbound e-mails Set hostname and FQDN ---------------------- Your server gets to names: A hostname and a FQDN (Fully qualified domain name). Local hostname: For identifying your server in nyour local infrastructure. E.g. “mail”. FQDN (Fully Qualified Domain Name): For identifying your server on the internet. E.g. “mail.mysystems.tld” You do not need to fit your mail server’s FQDN to any domain you want to serve with it. These domains do not need to be the same or similar. Set your local hostname as follows: hostnamectl set-hostname --static mail The configuration file /etc/hosts contains FQDN and local hostname next to each other. It should be similar to this one: 127.0.0.1 localhost 127.0.1.1 mail.mysystems.tld mail ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters If you enter the “hostname” and “hostname --fqdn” commands, this should be your output: root@mail:~# hostname mail root@mail:~# hostname --fqdn mail.mysystems.tld Furthermore, the FQDN (“mail.mysystems.tld”) is copied to /etc/mailname: echo $(hostname -f) > /etc/mailname Install Unbound DNS Resolver ---------------------------- Install unbound: apt install unbound Update DNSSEC Root key and reload Unbound service: su -c "unbound-anchor -a /var/lib/unbound/root.key" - unbound systemctl reload unbound NOTA: The following commands will only work if the “dnsutils” package is installed to your Debian system! A dig @127.0.0.1 denic.de +short +dnssec should result in a similar output like this: dig @127.0.0.1 denic.de +short +dnssec 81.91.170.12 A 8 2 3600 20170814090000 20170731090000 26155 denic.de. Jo90qnkLkZ6gI4qNHj19BMguFuGof9hCPhdeSh/fSePSQ/WXlWMmfjW1 sNDJ/bcITRMyz8DQdDzmWPDIeSJ/qPyfoZ+BjUZxtaXcs0BAl4KX8q7h R05TGmAbgPhrYBoUKJkU/q8T+jWKHAJRUeWbCd8QOJsJbneGcUKxRAPe i6Rq51/OL/id6zUCtalhclah2TfLLaqku9PmKwjbGdZm11BXSr8b56LB WX/rdLIrKWNpE+jHGAUMmDsZL84Kx3Oo If the dig-command worked, you can now set Unbound as the primary DNS resolver for your mail system: apt install resolvconf echo "nameserver 127.0.0.1" >> /etc/resolvconf/resolv.conf.d/head result of nslookup denic.de | grep Server should now be: Server: 127.0.0.1 Now you have your own DNS resolver up and running. Let’s continue with DNS configuration... DNS zone setup -------------- You need some DNS records set up for your mailsystem to work. Browse the management interface of your DNS provider and look for the zone files of your domains. Then add the records shown below: Point your system’s FQDN to its IPv4 address (and - if you have one - to your IPv6 address) mail.mysystems.tld. 86400 IN A 5.1.76.155 mail.mysystems.tld. 86400 IN AAAA 2a00:f820:417::be19:7b23 “imap.mysystems.tld” and “smtp.mysystems.tld” are aliases for “mail.mysystems.tld”. They are not absolutely necessary, but considered “good style”. Many mail clients are looking for these domain names if you set up a new mail account. imap.mysystems.tld. 86400 IN CNAME mail.mysystems.tld. smtp.mysystems.tld. 86400 IN CNAME mail.mysystems.tld. Now let’s create some MX records: Other mail servers need these to figure out, which host is responsible for mails on a certain domain. We want the host on “mail.mysystems.tld” to responsible for all e-mails, which go to @mysystemd.tld mail addresses. mysystems.tld. 86400 IN MX 0 mail.mysystems.tld. … and we also want it to handle e-mails for “domain2” and “domain3”: domain2.tld. 86400 IN MX 0 mail.mysystems.tld. domain3.tld. 86400 IN MX 0 mail.mysystems.tld. Reverse DNS ----------- A reverse DNS record (also called “PTR record” matches a FQDN to an IP-address. Many popular e-mail providers check other mail server’s PTR records and deny receiving e-mails from them, if they cannot find a proper domain name to their IPs. You will need PTR records for all your server’s IP-addresses. They all must point to mail.mysystems.tld. In most cases reverse DNS entries can be set via your server hoster’s web interface or via the server support team. SPF records SPF records were invented to support the fight against spam servers. Unfortunately it turned out as a miss-conception and you cannot rely on these records anymore. SPF describes which mail servers are allowed to send e-mails for a domain and which are not. In some cases (e.g. mailing lists) the SPF concept does not work. Still some providers expect you to set a SPF record - if you don’t, you’ll get some point off your “spam credibility score”. So let’s make a compromise and provide a neutral SPF record: mysystems.tld. 3600 IN TXT v=spf1 a:mail.mysystems.tld ?all Old-schooled, bad mail providers will be satisfied, and yet we don’t support bad SPF practices. The SPF record is also set for your other domains “domain2” and “domain3”, but in a slightly different way: domain2.tld. 3600 IN TXT v=spf1 include:mysystems.tld ?all DMARC records ------------- DMARC records set rules for foreign mail servers, which tell them how to deal with non-authenticated or incorrectly authenticated e-mails. If a spammer sends a fake e-mail and uses your domain mysystems.tld, the receiving server will consult the DNS and ask for the DMARC record. If it finds out that SPF or DKIM fail (which will be the case), the server will proceed according to the DMARC record. It is a good idea to reject such e-mails: _dmarc.mysystems.tld. 3600 IN TXT v=DMARC1; p=reject; Set the DMARC record for your other domains accordingly: _dmarc.domain2.tld. 3600 IN TXT v=DMARC1; p=reject; You can create your own DMARC policy records on https://elasticemail.com/dmarc/. Set up TLS certificates ----------------------- A modern e-mail server can’t be operated seriously without TLS certificates. We will use Let’s Encrypt certificates for this purpose, as they are free and yet accepted by all browsers, mail clients and operating systems. If you already have valid certificates, you can use them instead. apt install certbot certbot certonly --standalone --rsa-key-size 4096 -d mail.mysystems.tld -d imap.mysystems.tld -d smtp.mysystems.tld After having agreed to the terms of use and having provided your e-mail address you will instantly get valid certificates. They are saved at: /etc/letsencrypt/live/mail.mysystems.tld and are valid for your three mail system domain names: mail.mysystems.tld imap.mysystems.tld smtp.mysystems.tld Your additional domains “domain2” and “domain3” do not need to be included into this certificate. There are some new files in /etc/letsencrypt/live/mail.mysystems.tld which you will need: cert.pem: Your mailserver certificate chain.pem: CA certificate fullchain.pem: mailserver certificate + CA certificate privkey.pem: Private key for mailserver certificate Later in this guide only the letter two certificate files will be used. Auto-Renewal ------------ Let’s encrypt certificates are valid for 90 days only, so they should be renewed in fixed intervals. You can achieve that via a cronjob: @weekly certbot renew --pre-hook "systemctl stop nginx" --post-hook "systemctl start nginx" --renew-hook "systemctl reload nginx; systemctl reload dovecot; systemctl reload postfix" --quiet This entry is added to the crontab file via crontab -e [ ...... ] 🔗 Install and configure Postfix ----------------------------- Install: apt install postfix postfix-mysql During installation of the Postfix packages you will be asked what type of configuration you want to create. Select “No configuration”. Then stop Postfix: systemctl stop postfix Although you selected “No configuration” there will be configuration files in /etc/postfix. Delete some of them: cd /etc/postfix rm -r sasl rm master.cf main.cf.proto master.cf.proto Then create the following new config files in /etc/postfix: main.cf ## ## Network settings ## mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 inet_interfaces = 127.0.0.1, ::1, 5.1.76.152, 2a00:f820:417::7647:b2c2 myhostname = mail.mysystems.tld ## ## Mail queue settings ## maximal_queue_lifetime = 1h bounce_queue_lifetime = 1h maximal_backoff_time = 15m minimal_backoff_time = 5m queue_run_delay = 5m ## ## TLS settings ### tls_ssl_options = NO_COMPRESSION tls_high_cipherlist = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA ### Outbound SMTP connections (Postfix as sender) smtp_tls_security_level = dane smtp_dns_support_level = dnssec smtp_tls_policy_maps = mysql:/etc/postfix/sql/tls-policy.cf smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache smtp_tls_protocols = !SSLv2, !SSLv3 smtp_tls_ciphers = high smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt ### Inbound SMTP connections smtpd_tls_security_level = may smtpd_tls_protocols = !SSLv2, !SSLv3 smtpd_tls_ciphers = high smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache smtpd_tls_cert_file=/etc/letsencrypt/live/mail.mysystems.tld/fullchain.pem smtpd_tls_key_file=/etc/letsencrypt/live/mail.mysystems.tld/privkey.pem ## ## Local mail delivery to Dovecot via LMTP ## virtual_transport = lmtp:unix:private/dovecot-lmtp ## ## Spam filter and DKIM signatures via Rspamd ## smtpd_milters = inet:localhost:11332 non_smtpd_milters = inet:localhost:11332 milter_protocol = 6 milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen} milter_default_action = accept ## ## Server Restrictions for clients, cecipients and relaying ## (concerning S2S-connections. Mailclient-connections are configured in submission-section in master.cf) ## ### Conditions in which Postfix works as a relay. (for mail user clients) smtpd_relay_restrictions = reject_non_fqdn_recipient reject_unknown_recipient_domain permit_mynetworks reject_unauth_destination ### Conditions in which Postfix accepts e-mails as recipient (additional to relay conditions) ### check_recipient_access checks if an account is "sendonly" smtpd_recipient_restrictions = check_recipient_access mysql:/etc/postfix/sql/recipient-access.cf ### Restrictions for all sending foreign servers ("SMTP clients") smtpd_client_restrictions = permit_mynetworks check_client_access hash:/etc/postfix/without_ptr reject_unknown_client_hostname ### Foreign mail servers must present a valid "HELO" smtpd_helo_required = yes smtpd_helo_restrictions = permit_mynetworks reject_invalid_helo_hostname reject_non_fqdn_helo_hostname reject_unknown_helo_hostname # Block clients, which start sending too early smtpd_data_restrictions = reject_unauth_pipelining ## ## Restrictions for MUAs (Mail user agents) ## mua_relay_restrictions = reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_mynetworks,permit_sasl_authenticated,reject mua_sender_restrictions = permit_mynetworks,reject_non_fqdn_sender,reject_sender_login_mismatch,permit_sasl_authenticated,reject mua_client_restrictions = permit_mynetworks,permit_sasl_authenticated,reject ## ## Postscreen Filter ## ### Postscreen Whitelist / Blocklist postscreen_access_list = permit_mynetworks cidr:/etc/postfix/postscreen_access postscreen_blacklist_action = drop # Drop connections if other server is sending too quickly postscreen_greet_action = drop ### DNS blocklists postscreen_dnsbl_threshold = 2 postscreen_dnsbl_sites = ix.dnsbl.manitu.net*2 zen.spamhaus.org*2 postscreen_dnsbl_action = drop ## ## MySQL queries ## virtual_alias_maps = mysql:/etc/postfix/sql/aliases.cf virtual_mailbox_maps = mysql:/etc/postfix/sql/accounts.cf virtual_mailbox_domains = mysql:/etc/postfix/sql/domains.cf local_recipient_maps = $virtual_mailbox_maps ## ## Miscellaneous ## ### Maximum mailbox size (0=unlimited - is already limited by Dovecot quota) mailbox_size_limit = 0 ### Maximum size of inbound e-mails (50 MB) message_size_limit = 52428800 ### Do not notify system users on new e-mail biff = no ### Users always have to provide full e-mail addresses append_dot_mydomain = no ### Delimiter for "Address Tagging" recipient_delimiter = + Settings to adjust: inet_interfaces: IP addresses of your server. 5.1.76.152, 2a00:f820:417::7647:b2c2 must be replaced by your own IPv4- and IPv6-address. myhostname: Replace by your own hostname smtpd_tls_cert_file: Path to certificate file smtpd_tls_key_file: Path to certificate key master.cf # ========================================================================== # service type private unpriv chroot wakeup maxproc command + args # (yes) (yes) (no) (never) (100) # ========================================================================== smtp inet n - y - 1 postscreen -o smtpd_sasl_auth_enable=no smtpd pass - - y - - smtpd dnsblog unix - - y - 0 dnsblog tlsproxy unix - - y - 0 tlsproxy submission inet n - y - - smtpd -o syslog_name=postfix/submission -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes -o smtpd_sasl_type=dovecot -o smtpd_sasl_path=private/auth -o smtpd_sasl_security_options=noanonymous -o smtpd_client_restrictions=$mua_client_restrictions -o smtpd_sender_restrictions=$mua_sender_restrictions -o smtpd_relay_restrictions=$mua_relay_restrictions -o milter_macro_daemon_name=ORIGINATING -o smtpd_sender_login_maps=mysql:/etc/postfix/sql/sender-login-maps.cf -o smtpd_helo_required=no -o smtpd_helo_restrictions= -o cleanup_service_name=submission-header-cleanup pickup unix n - y 60 1 pickup cleanup unix n - y - 0 cleanup qmgr unix n - n 300 1 qmgr tlsmgr unix - - y 1000? 1 tlsmgr rewrite unix - - y - - trivial-rewrite bounce unix - - y - 0 bounce defer unix - - y - 0 bounce trace unix - - y - 0 bounce verify unix - - y - 1 verify flush unix n - y 1000? 0 flush proxymap unix - - n - - proxymap proxywrite unix - - n - 1 proxymap smtp unix - - y - - smtp relay unix - - y - - smtp showq unix n - y - - showq error unix - - y - - error retry unix - - y - - error discard unix - - y - - discard local unix - n n - - local virtual unix - n n - - virtual lmtp unix - - y - - lmtp anvil unix - - y - 1 anvil scache unix - - y - 1 scache submission-header-cleanup unix n - n - 0 cleanup -o header_checks=regexp:/etc/postfix/submission_header_cleanup Header cleanup rules Create a new file /etc/postfix/submission_header_cleanup with this content: ### Removes headers of MUAs for privacy reasons /^Received:/ IGNORE /^X-Originating-IP:/ IGNORE /^X-Mailer:/ IGNORE /^User-Agent:/ IGNORE SQL configuration SQL queries for Postfix sit in the sql/ subdirectory: mkdir /etc/postfix/sql && cd /etc/postfix/sql/ Create these files with their corresponding content: accounts.cf user = vmail password = vmaildbpass hosts = 127.0.0.1 dbname = vmail query = select 1 as found from accounts where username = '%u' and domain = '%d' and enabled = true LIMIT 1; aliases.cf user = vmail password = vmaildbpass hosts = 127.0.0.1 dbname = vmail query = select concat(destination_username, '@', destination_domain) as destinations from aliases where source_username = '%u' and source_domain = '%d' and enabled = true; domains.cf user = vmail password = vmaildbpass hosts = 127.0.0.1 dbname = vmail query = SELECT domain FROM domains WHERE domain='%s' recipient-access.cf user = vmail password = vmaildbpass hosts = 127.0.0.1 dbname = vmail query = select if(sendonly = true, 'REJECT', 'OK') AS access from accounts where username = '%u' and domain = '%d' and enabled = true LIMIT 1; sender-login-maps.cf user = vmail password = vmaildbpass hosts = 127.0.0.1 dbname = vmail query = select concat(username, '@', domain) as 'owns' from accounts where username = '%u' AND domain = '%d' and enabled = true union select concat(destination_username, '@', destination_domain) AS 'owns' from aliases where source_username = '%u' and source_domain = '%d' and enabled = true; tls-policy.cf user = vmail password = vmaildbpass hosts = 127.0.0.1 dbname = vmail query = SELECT policy, params FROM tlspolicies WHERE domain = '%s' Don’t forget to modify vmaildbpass in all of the above files, in case you are using another password! Set proper permissions for /etc/postfix/sql: chmod -R 640 /etc/postfix/sql More Postfix configuration files Create two new files in /etc/postfix. You can leave them empty. touch /etc/postfix/without_ptr touch /etc/postfix/postscreen_access In without_ptr you can define entries like this: 1.2.3.3 OK This will result in a policy, which allows server 1.2.3.3 to send e-mails to this host even if it does not have a valid PTR-record. After every change, without_ptr has to be converted into a database file and Postfix must be reloaded: postmap /etc/postfix/without_ptr systemctl reload postfix For the moment, just create an empty database file: postmap /etc/postfix/without_ptr In postscreen_access you can define exceptions for the postscreen filter. If any mail server is blocked by postscreen and you want to grant access for any reason, add an entry similar to the following: 1.2.3.3 permit You can do the opposite, too: If you always want to block a certain server, add “reject” instead of “permit”. Execute newaliases to create the alias database file /etc/aliases.db. This file is expected by Postfix by default.