Given that GMail with cease to provide free hosting service for custom domains I've decided to deploy a fully featured personal email server and migrate everything to my Arch Linux box.
mail-tester.com tests reports on Jul 7, 2022 and Jul 29, 2023 look pretty good, 10 out of 10. All the details below, let's begin…
1. Dovecot
One of the most popular IMAP/POP3 server is Dovecot.
1.1 Installation
pacman -S dovecot
1.2 Configuration
We are going to use doveconf tool or just cat the files when needed.
Default config
mkdir /etc/dovecot
cp -r /usr/share/doc/dovecot/example-config/ /etc/dovecot
Just copy the default provided config, very good as a starting point.
Mail location and type
doveconf -n mail_location namespace inbox
mail_location = mbox:~/mail:INBOX=/var/mail/%u namespace inbox { inbox = yes location = mailbox Drafts { special_use = \Drafts } mailbox Junk { special_use = \Junk } mailbox Sent { special_use = \Sent } mailbox "Sent Messages" { special_use = \Sent } mailbox Trash { special_use = \Trash } prefix = }
mail_location is one of the most important config, it basically says that it expects MTA (Postfix) to deliver email in INBOX=/var/mail/USERNAME and store mailboxes in ~/mail
Add dovecot to mail group
usermod -aG mail dovecot
id dovecot
uid=76(dovecot) gid=76(dovecot) groups=76(dovecot),12(mail)
Adds mail group to dovecot user that will allow MTA to have access to /var/mail dir.
SSL certificates
Check Wildcard SSL certificate blog post to see how to aquire a Let's Encrypt SSL certificate; or generate a self-signed one below.
Self-signed certificate
config
cat /etc/ssl/dovecot-openssl.cnf
[ req ] default_bits = 2048 encrypt_key = yes distinguished_name = req_dn x509_extensions = cert_type prompt = no [ req_dn ] # country (2 letter code) C=RO # State or Province Name (full name) ST=Iasi # Locality Name (eg. city) L=Iasi # Organization (eg. company) O=Matrix # Organizational Unit Name (eg. section) OU=Email server # Common Name (*.example.com is also possible) CN=*.costan.ro # E-mail contact emailAddress=postmaster@costan.ro [ cert_type ] nsCertType = server
generate
cat /usr/lib/dovecot/mkcert.sh
#!/bin/sh # Generates a self-signed certificate. # Edit dovecot-openssl.cnf before running this. umask 077 OPENSSL=${OPENSSL-openssl} SSLDIR=${SSLDIR-/etc/ssl} OPENSSLCONFIG=${OPENSSLCONFIG- /etc/ssl/dovecot-openssl.cnf} CERTDIR=$SSLDIR/certs KEYDIR=$SSLDIR/private CERTFILE=$CERTDIR/dovecot.pem KEYFILE=$KEYDIR/dovecot.pem if [ ! -d $CERTDIR ]; then echo "$SSLDIR/certs directory doesn't exist" exit 1 fi if [ ! -d $KEYDIR ]; then echo "$SSLDIR/private directory doesn't exist" exit 1 fi if [ -f $CERTFILE ]; then echo "$CERTFILE already exists, won't overwrite" exit 1 fi if [ -f $KEYFILE ]; then echo "$KEYFILE already exists, won't overwrite" exit 1 fi $OPENSSL req -new -x509 -nodes -config $OPENSSLCONFIG -out $CERTFILE -keyout $KEYFILE -days 365 || exit 2 chmod 0600 $KEYFILE echo $OPENSSL x509 -subject -fingerprint -noout -in $CERTFILE || exit 2
Generate /etc/ssl/{certs,private}/dovecot.pem cert files.
openssl dhparam -out /etc/dovecot/dh.pem 4096
Generate /etc/dovecot/dh.pem file.
SSL
doveconf ssl ssl_cert ssl_key ssl_dh ssl_require_crl
ssl = yes ssl_cert = </etc/letsencrypt/live/costan.ro/fullchain.pem ssl_key = </etc/letsencrypt/live/costan.ro/privkey.pem ssl_dh = </etc/dovecot/dh.pem ssl_require_crl = yes
Path to SSL certificate files.
Authentication
doveconf -n userdb passdb
userdb { driver = passwd } passdb { driver = pam }
Where/how user/pass is looked up, users in /etc/passwd and passwords in PAM.
cat /etc/pam.d/dovecot
#%PAM-1.0 auth include system-auth account include system-auth session include system-auth password include system-auth
PAM configuration is complex and out of the scope of this blog post, take it for granted.
Create system user
useradd iulian -m
passwd iulian
Create system user that need to send/receive email.
Final conf
doveconf -n
# 2.3.18 (9dd8408c18): /etc/dovecot/dovecot.conf # OS: Linux 5.17.4-arch1-1 x86_64 # Hostname: rig mail_location = mbox:~/mail:INBOX=/var/mail/%u namespace inbox { inbox = yes location = mailbox Drafts { special_use = \Drafts } mailbox Junk { special_use = \Junk } mailbox Sent { special_use = \Sent } mailbox "Sent Messages" { special_use = \Sent } mailbox Trash { special_use = \Trash } prefix = } passdb { driver = pam } service auth { unix_listener /var/spool/postfix/private/auth { group = postfix mode = 0660 user = postfix } } ssl_cert = </etc/ssl/certs/dovecot.pem ssl_key = # hidden, use -P to show it userdb { driver = passwd }
The whole Dovecot config is long / complex, these are only the non-defaults values.
1.3 Service
systemctl start dovecot.service
ufw limit "IMAPS"
ufw limit "Mail"
Start/enable dovecot.service and open the ports in UFW firewall.
1.4 Testing tools
Just basic connectivity/speed IMAPS testing, we'll run more advanced tests later on.
2. Postfix
I know Sendmail is the classic, widely used mail transfer agent but it is a bit old-fashion to me and I'll use Postfix instead.
2.1 Installation
pacman -S postfix
2.2 Configuration
Again, we will use postconf to show/manage configuration.
Directories
postconf -n | grep -E "directory\s"
command_directory = /usr/bin daemon_directory = /usr/lib/postfix/bin data_directory = /var/lib/postfix html_directory = no manpage_directory = /usr/share/man meta_directory = /etc/postfix queue_directory = /var/spool/postfix readme_directory = /usr/share/doc/postfix sample_directory = /etc/postfix shlib_directory = /usr/lib/postfix
This is mostly Arch Linux specific but is worth seeing where things are installed/stored.
Domain
postconf -n | grep ^my
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain mydomain = costan.ro myhostname = smtp.$mydomain myorigin = $mydomain
mydomain, mydestination specify what email recipients should be accepted by my server.
Aliases
postconf -n | grep -E "^alias|newaliases"
alias_database = $alias_maps alias_maps = lmdb:/etc/postfix/aliases newaliases_path = /usr/bin/newaliases
Email aliases if any; dont forget to run newaliases command to rebuild aliases db.
Catch-all email
postconf -n luser_relay local_recipient_maps
luser_relay = iulian local_recipient_maps =
Redirect all (mind spam) unknown email recipients to given username.
Secure email with TLS (Transport Layer Security)
receiving
postconf -n | grep -E "smtpd_tls|smtpd_use_tls"
smtpd_tls_auth_only = yes smtpd_tls_cert_file = /etc/letsencrypt/live/costan.ro/fullchain.pem smtpd_tls_key_file = /etc/letsencrypt/live/costan.ro/privkey.pem smtpd_tls_loglevel = 1 smtpd_tls_security_level = may smtpd_use_tls = yes
smtpd_tls_auth_only to reject plain auth over unsecured connections.
sending
postconf -n | grep smtp_
smtp_tls_loglevel = 1 smtp_tls_security_level = may
smtp_tls_security_level optional TLS when sending, since TLS is not enabled in all MTAs.
Authentication/authorization
Postfix auth config
postconf -n | grep ^smtpd_sasl
smtpd_sasl_auth_enable = yes smtpd_sasl_local_domain = $mydomain smtpd_sasl_path = private/auth smtpd_sasl_security_options = noanonymous, noplaintext smtpd_sasl_tls_security_options = noanonymous smtpd_sasl_type = dovecot
smtpd_sasl_type, smtpd_sasl_path - backend and unix socket for SASL smtpd_sasl_tls_security_options - allow plain text auth over TLS, but no anonymous
Dovecot auth integration
doveconf -n service auth
service auth { unix_listener /var/spool/postfix/private/auth { group = postfix mode = 0660 user = postfix } }
The other side of the Unix socket configured in Dovecot.
Relay and restrictions
postconf -n | grep -E "helo|relay"
smtpd_helo_required = yes smtpd_helo_restrictions = reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
smtpd_relay_restrictions - no open relay ever OK?
Mail submission
postconf -M submission
submission inet n - n - - smtpd -o syslog_name=postfix/submission -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes -o smtpd_tls_auth_only=yes -o smtpd_reject_unlisted_recipient=no -o smtpd_relay_restrictions= -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject -o milter_macro_daemon_name=ORIGINATING
The MSA service that listen on 587/tcp port for mail submission from a MUA (email client).
DNS TXT record
drill -Q costan.ro TXT
drill -Q smtp.costan.ro TXT
"v=spf1 a mx ip4:86.124.145.184 ~all" "v=spf1 a mx ip4:86.124.145.184 ~all"
Sender Policy Framework (SPF) is required to detect some forged sender addreses.
Reverse DNS record
drill -Q 86.124.145.184 -x
smtp.costan.ro.
And last, one of the most important configuration, get in touch with your ISP to setup the Reverse DNS (rDNS); otherwise your emails will, most probably, be marked as spam.
Final conf
postconf -n
alias_database = $alias_maps alias_maps = hash:/etc/postfix/aliases command_directory = /usr/bin compatibility_level = 3.7 daemon_directory = /usr/lib/postfix/bin data_directory = /var/lib/postfix debug_peer_level = 2 debugger_command = PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin ddd $daemon_directory/$process_name $process_id & sleep 5 html_directory = no inet_protocols = ipv4 local_recipient_maps = luser_relay = iulian mail_owner = postfix mailq_path = /usr/bin/mailq manpage_directory = /usr/share/man meta_directory = /etc/postfix milter_default_action = accept mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain mydomain = costan.ro myhostname = smtp.$mydomain myorigin = $mydomain newaliases_path = /usr/bin/newaliases non_smtpd_milters = $smtpd_milters queue_directory = /var/spool/postfix readme_directory = /usr/share/doc/postfix sample_directory = /etc/postfix sendmail_path = /usr/bin/sendmail setgid_group = postdrop shlib_directory = /usr/lib/postfix smtp_tls_loglevel = 1 smtp_tls_security_level = may smtpd_helo_required = yes smtpd_helo_restrictions = reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname smtpd_milters = inet:localhost:8891, inet:localhost:8893 smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination smtpd_sasl_auth_enable = yes smtpd_sasl_local_domain = $mydomain smtpd_sasl_path = private/auth smtpd_sasl_security_options = noanonymous, noplaintext smtpd_sasl_tls_security_options = noanonymous smtpd_sasl_type = dovecot smtpd_tls_auth_only = yes smtpd_tls_cert_file = /etc/letsencrypt/live/costan.ro/fullchain.pem smtpd_tls_key_file = /etc/letsencrypt/live/costan.ro/privkey.pem smtpd_tls_loglevel = 1 smtpd_tls_security_level = may smtpd_use_tls = yes unknown_local_recipient_reject_code = 550
Again, these are only the non-default config values.
2.3 Service
systemctl start postfix.service
ufw limit "SMTP"
2.4 Testing tools
- https://mxtoolbox.com/diagnostic.aspx - excelent tool for MX, DNS
- https://www.appmaildev.com/
- https://www.checktls.com/index.html
- https://decoder.link/sslchecker/smtp.costan.ro/25
For all geeks out there you can use openssl to do basic SMTP testing.
openssl s_client -connect smtp.costan.ro:25 -starttls smtp
3. DomainKeys Identified Mail - DKIM
DKIM is an email authentication method used to detect forged sender addresses.
3.1 Installation
pacman -S opendkim
3.2 Configuration
Minimal config
grep -v -e '^#' -e '^[[:space:]]*$' /etc/opendkim/opendkim.conf
Canonicalization relaxed/simple Domain costan.ro KeyFile /etc/opendkim/rig.private Selector rig Socket inet:8891@localhost Syslog Yes UserID opendkim:postfix
Nothing too complex, domain, private key location and the socket.
Generate key file
opendkim-genkey --restrict --selector rig --domain costan.ro --directory /etc/opendkim
Generate rig.private and rig.txt files.
Postfix integration
postconf -n | grep milter
milter_default_action = accept non_smtpd_milters = $smtpd_milters smtpd_milters = inet:localhost:8891, inet:localhost:8893
Socket communication via inet:localhost:8891.
DNS TXT record
cat /etc/opendkim/rig.txt
rig._domainkey IN TXT ( "v=DKIM1; k=rsa; s=email; " "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQFlti46dceD5rk3+RGnoYStK6np+cIucrOrkMHbjoRLcOxNikOfi0ABgG2CxK/0X+VNmiL5PsaWWnXhYGOJWz82LM0zhDzoD1bQ0OIb/PWyPMz22udwnPa6FRypEEnjAdC6c8g7tX8fMovqX/09PHKKjLq4zX0X3CMT+t3QhXlQIDAQAB" ) ; ----- DKIM key rig for costan.ro
drill -Q rig._domainkey.costan.ro TXT
"v=DKIM1; k=rsa; s=email; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQFlti46dceD5rk3+RGnoYStK6np+cIucrOrkMHbjoRLcOxNikOfi0ABgG2CxK/0X+VNmiL5PsaWWnXhYGOJWz82LM0zhDzoD1bQ0OIb/PWyPMz22udwnPa6FRypEEnjAdC6c8g7tX8fMovqX/09PHKKjLq4zX0X3CMT+t3QhXlQIDAQAB"
Public key published as TXT record.
3.3 Service
systemctl start opendkim.service
3.4 Testing tools
4. Domain-based Message Authentication, Reporting and Conformance - DMARC
DMARC is an email authentication protocol that extends SPF and DKIM to protect domain from email spoofing.
4.1 Installation
pacman -S opendmarc
4.2 Configuration
Minimal config
grep -v -e '^#' -e '^[[:space:]]*$' /etc/opendmarc/opendmarc.conf
AuthservID HOSTNAME IgnoreAuthenticatedClients true Socket inet:8893@localhost SPFSelfValidate true UMask 002
Socket and some other basic stuff.
Postfix integration
postconf -n | grep milter
milter_default_action = accept non_smtpd_milters = $smtpd_milters smtpd_milters = inet:localhost:8891, inet:localhost:8893
Socket communication via inet:localhost:8893
DNS TXT record
drill -Q _dmarc.costan.ro TXT
"v=DMARC1; p=quarantine; rua=mailto:postmaster@costan.ro; ruf=mailto:forensic@costan.ro; adkim=s; aspf=s; fo=1; pct=25"
Enable p=quarantine policy for pct=25 percent of the emails that fail the validation.
4.3 Service
systemctl start opendmarc.service
4.4 Testing tools
5. Imapsync
And finally, migrate all emails from Gmail to my personal email server with imapsync tool.
5.1 Installation
pacman -S imapsync
5.2 Migration
imapsync --gmail1 --user1 <SRC_USER> --password1 <SRC_PASS> \
--host2 localhost --user2 <DST_USER> --password2 <DST_PASS> \
--exclude "INBOX|Drafts|Important|Spam|Trash" \
--f1f2 "[Gmail]/All Mail"="Archive" \
--folderlast "[Gmail]All Mail" \
--dry
Imapsync tool has a lots of params but the default automap works just fine, I only need to map Gmail's All Mail to Archive folder (to be synced last) and exclude the folders that I do not want.
Mind the –dry at the end, to play safe and test out the whole migration first.
References
- https://wiki.archlinux.org/title/Postfix
- https://www.postfix.org/documentation.html
- https://www.postfix.org/STANDARD_CONFIGURATION_README.html
- https://www.arubacloud.com/tutorial/how-to-configure-a-smtp-mail-server-with-postfix-on-ubuntu-18-04.aspx
- https://doc.dovecot.org/configuration_manual/quick_configuration/
- https://doc.dovecot.org/
- https://wiki.dovecot.org/
- https://www.arubacloud.com/tutorial/how-to-configure-a-pop3-imap-mail-server-with-dovecot-on-ubuntu-18-04.aspx
- https://kevwells.com/it-knowledge-base/installing-dovecot-imap-server/
- https://wiki.archlinux.org/title/OpenDKIM
- http://opendkim.org/
- http://dkim.org/
- https://wiki.archlinux.org/title/OpenDMARC
- https://wiki.debian.org/opendkim
- https://www.linuxbabe.com/mail-server/secure-email-server-ubuntu-postfix-dovecot
- https://www.abuseat.org/helocheck.html
- https://clean.email/email-blacklist-check
- https://en.wikipedia.org/wiki/Email_authentication
- https://www.oreilly.com/library/view/postfix-the-definitive/0596002122/ch04s07.html
- https://imapsync.lamiral.info/
Updates
- [2023-02-21] Use costan.ro certs instead of self-generated Dovecot certs