iTranslated by AI
Notes on setting up a mail server on Ubuntu 22.04
I built my own mail server even in this day and age (the Reiwa era), so here are my notes.
The OS is Ubuntu 22.04.
Automatic Reboot After Package Updates
Although not directly related to email, I configured it to reboot automatically if there are updates via apt that require a restart.
/etc/apt/apt.conf.d/50unattended-upgrades:
Unattended-Upgrade::Automatic-Reboot "true";
Let's Encrypt
I installed certbot to create TLS certificates. Since I am using Sakura Cloud's DNS, I also added the corresponding module.
# apt install certbot python3-certbot-dns-sakuracloud
Follow the instructions at https://certbot-dns-sakuracloud.readthedocs.io/en/stable/ and create /root/.secrets/certbot/sakuracloud.ini:
dns_sakuracloud_api_token = XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
dns_sakuracloud_api_secret = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Create the certificate:
# certbot certonly --dns-sakuracloud --dns-sakuracloud-credentials ~/.secrets/certbot/sakuracloud.ini -m hoge@example.com -d \*.example.com -d \*.example.net
Configure cron to automatically renew the certificate as the expiration date approaches:
/etc/cron.weekly/letsencrypt:
#!/bin/bash
certbot renew
[Update] ↑ Since /etc/cron.d/certbot is created automatically, this was actually unnecessary.
Postfix
Install Postfix as an SMTP server.
# apt install postfix postfix-policyd-spf-python
Configure emails addressed to root to be forwarded to your user.
/etc/aliases:
root: tommy
# newaliases
Uncomment and enable submission (port 587) and smtps (port 465) in master.cf.
/etc/postfix/master.cf:
submission inet n - y - - 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_client_restrictions=$mua_client_restrictions
-o smtpd_helo_restrictions=$mua_helo_restrictions
-o smtpd_sender_restrictions=$mua_sender_restrictions
-o smtpd_recipient_restrictions=
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
smtps inet n - y - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_reject_unlisted_recipient=no
-o smtpd_client_restrictions=$mua_client_restrictions
-o smtpd_helo_restrictions=$mua_helo_restrictions
-o smtpd_sender_restrictions=$mua_sender_restrictions
-o smtpd_recipient_restrictions=
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
Perform SPF checks on incoming emails.
/etc/postfix/master.cf:
spfcheck unix - n n - 0 spawn
user=policyd-spf argv=/usr/bin/policyd-spf /etc/postfix-policyd-spf-python/policyd-spf.conf
The main.cf looks like this. It shouldn't be doing anything too unusual.
/etc/postfix/main.cf:
compatibility_level = 3.6
alias_maps = hash:/etc/aliases
myhostname = host.example.com
mydestination = $myhostname, $mydomain
home_mailbox = Maildir/
smtpd_relay_restrictions =
permit_mynetworks
permit_sasl_authenticated
reject_unauth_destination
check_policy_service unix:private/spfcheck
smtpd_helo_required = yes
mailbox_size_limit = 0
message_size_limit = 102400000
recipient_delimiter = +-
strict_rfc821_envelopes = yes
smtpd_use_tls = yes
smtpd_tls_cert_file = /etc/letsencrypt/live/example.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/example.com/privkey.pem
smtpd_tls_loglevel = 1
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
mua_client_restrictions =
mua_helo_restrictions =
mua_sender_restrictions =
Since IMAP uses Dovecot, I am using Dovecot for SASL and just setting up check_policy_service for SPF checks.
mua_*_restrictions are referenced in the submission and smtps sections of master.cf, so if they aren't configured, warnings will appear. Set them to empty values.
Dovecot
Install Dovecot as an IMAP server.
# apt install dovecot-imapd
Disable port 143 since it won't be used.
/etc/dovecot/conf.d/10-master.conf:
service imap-login {
inet_listener imap {
port = 0
}
inet_listener imaps {
port = 993
ssl = yes
}
Specify the socket file for Postfix authentication.
/etc/dovecot/conf.d/10-master.conf:
service auth {
〜〜
unix_listener /var/spool/postfix/private/auth {
mode = 0666
user = postfix
group = postfix
}
〜〜
}
The mailbox is Maildir located directly under each user's home directory.
/etc/dovecot/conf.d/10-mail.conf:
mail_location = maildir:~/Maildir
For the TLS certificate, use the one from Let's Encrypt.
/etc/dovecot/conf.d/10-ssl.conf:
ssl_cert = </etc/letsencrypt/live/example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/example.com/privkey.pem
By default, Dovecot uses the same password as the OS user, but since this service is exposed to the internet, I want to use a different password from the OS account.
Comment out auth-system.conf.ext in /etc/dovecot/conf.d/10-auth.conf and add one line:
#!include auth-system.conf.ext
!include auth-hoge.conf.ext
Create a dedicated authentication configuration file.
/etc/dovecot/conf.d/auth-hoge.conf.ext:
passdb {
driver = passwd-file
args = scheme=SHA512-CRYPT username_format=%u /etc/dovecot/users
}
userdb {
# <doc/wiki/AuthDatabase.Passwd.txt>
driver = passwd
# [blocking=no]
#args =
# Override fields from passwd
#override_fields = home=/home/virtual/%u
}
passdb was copied from auth-passwdfile.conf.ext, and userdb was copied from auth-system.conf.ext.
The password file /etc/dovecot/users follows this format:
username:{SHA512-CRYPT}$6$Gm.4X5ktmas.00pC$z9zEBRJvyyAtuczb81eyr26K/sdjkt9uZ.9mgQT1RR6s6JijHxdnyhQtDGnu70DV9v9Ijkn0bvWYFfkOFWZij0
You can create the hash string as follows:
# doveadm pw -s SHA512-CRYPT -p hogehoge
{SHA512-CRYPT}$6$Gm.4X5ktmas.00pC$z9zEBRJvyyAtuczb81eyr26K/sdjkt9uZ.9mgQT1RR6s6JijHxdnyhQtDGnu70DV9v9Ijkn0bvWYFfkOFWZij0
fail2ban
Install fail2ban to block connections from an IP address when authentication for Postfix or Dovecot fails multiple times from that same address. By default, it is also effective for SSH authentication.
# apt install fail2ban
/etc/fail2ban/jail.d/postfix.conf:
[postfix-sasl]
enabled = true
/etc/fail2ban/jail.d/dovecot.conf:
[dovecot]
enabled = true
By default, it blocks an IP for 10 minutes if authentication fails 5 times within 10 minutes.
Reload services when the certificate is renewed
I added this section after a comment on Hatebu suggested that services might need to be reloaded when the certificate is updated.
It was exactly as pointed out 🙏
If you place executable files under /etc/letsencrypt/renewal-hooks/post, they will be executed when the certificate is renewed. I placed the following files there:
/etc/letsencrypt/renewal-hooks/post/postfix
#!/bin/bash
/usr/sbin/postfix reload
/etc/letsencrypt/renewal-hooks/post/dovecot
#!/bin/bash
/usr/sbin/dovecot reload
For Postfix, the certificate file is read by the smtpd process. Since smtpd processes automatically restart after a certain period of operation, this reload isn't strictly necessary. However, doing it properly provides more peace of mind.
For Dovecot, the imap-login process handles TLS and is started for each connection, but the certificate file is read by a separate process called config, which does not reload the file unless explicitly instructed.
DNS Settings
I decided to add this section as I thought it might be better to document it properly.
As mentioned earlier, I use Sakura Cloud's DNS service. It is very affordable at 44 yen/month per zone.
Since this is a mail service, MX, A, SPF (TXT), and PTR records are essential.
I configured them like this:
@ MX 10 mx.example.com.
@ TXT v=spf1 mx -all
mx A 192.0.2.1
host A 192.0.2.1
The last entry host (host.example.com) is the hostname written in Postfix's myhostname. This should be set in the PTR record.
This is necessary because some MTAs may refuse connections if the forward lookup of the result obtained from a reverse DNS lookup of the client's IP address does not match that IP address.
PTR records can only be set by the entity managing the server's IP address. The procedure varies by provider.
I considered setting up the mail server on Oracle Cloud since it offers a free tier, but I abandoned that idea because PTR records cannot be configured on the free tier.
In the distant past, I used to run my own DNS servers, but it has become quite a hassle...
Discussion