Server — Mail — Postfix
Every article in the mail sub-series has been building to this one. Postfix is the component everything else connects to: Dovecot hands it the auth socket, Rspamd attaches as a milter, Fastmail is the relay for outbound. Getting Postfix right means understanding what each of those connections is for and how the configuration reflects them.
This article configures Postfix from installation through to a working send-and-receive mail setup. The Dovecot, Sieve, and Rspamd articles are prerequisites; this article assumes both are installed and their sockets are in place.
Installation
sudo apt install postfix postfix-utils libsasl2-modules mailutils
The installer presents a configuration dialog. Select Internet Site when prompted, then enter February’s fully qualified hostname, for example mail.yourdomain.com. This sets the initial value of myhostname in main.cf and writes the hostname to /etc/mailname.
Confirm Postfix is running:
sudo systemctl status postfix
postconf mail_version
Ubuntu 24.04 ships Postfix 3.8.x. The configuration lives in /etc/postfix/main.cf (the parameter database) and /etc/postfix/master.cf (the service process table).
main.cf — the complete configuration
Rather than presenting the configuration piecemeal, this article shows the complete main.cf with each section explained. Edit /etc/postfix/main.cf and replace the installer’s defaults with the following:
# -------------------------------------------------------------------
# Identity
# -------------------------------------------------------------------
myhostname = mail.yourdomain.com
mydomain = yourdomain.com
myorigin = $mydomain
# -------------------------------------------------------------------
# Network
# -------------------------------------------------------------------
inet_interfaces = all
inet_protocols = ipv4
mynetworks = 127.0.0.0/8 [::1]/128
# -------------------------------------------------------------------
# What Postfix considers local
# -------------------------------------------------------------------
# mydestination must NOT include domains handled as virtual
mydestination = $myhostname, localhost.$mydomain, localhost
# -------------------------------------------------------------------
# Virtual mailbox domains (handled by Dovecot via LMTP)
# -------------------------------------------------------------------
virtual_mailbox_domains = /etc/postfix/virtual_domains
virtual_transport = lmtp:unix:private/dovecot-lmtp
# -------------------------------------------------------------------
# Virtual aliases
# -------------------------------------------------------------------
virtual_alias_maps = hash:/etc/postfix/virtual_aliases
# -------------------------------------------------------------------
# Outbound relay via Fastmail
# -------------------------------------------------------------------
relayhost = [smtp.fastmail.com]:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_mechanism_filter = plain, login
smtp_tls_security_level = encrypt
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
smtp_tls_loglevel = 1
# -------------------------------------------------------------------
# Inbound TLS (for connections from other mail servers on port 25)
# -------------------------------------------------------------------
smtpd_tls_cert_file = /etc/ssl/mail/mail.crt
smtpd_tls_key_file = /etc/ssl/mail/mail.key
smtpd_tls_security_level = may
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_loglevel = 1
# -------------------------------------------------------------------
# Inbound SASL (auth delegated to Dovecot)
# -------------------------------------------------------------------
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_sasl_tls_security_options = noanonymous
# -------------------------------------------------------------------
# Restrictions
# -------------------------------------------------------------------
smtpd_helo_required = yes
smtpd_helo_restrictions =
permit_mynetworks,
reject_invalid_helo_hostname,
reject_non_fqdn_helo_hostname
smtpd_sender_restrictions =
permit_mynetworks,
reject_non_fqdn_sender,
reject_unknown_sender_domain
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination,
reject_non_fqdn_recipient,
reject_unknown_recipient_domain
smtpd_relay_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination
# -------------------------------------------------------------------
# Anti-spam basics
# -------------------------------------------------------------------
disable_vrfy_command = yes
smtpd_banner = $myhostname ESMTP
message_size_limit = 52428800
# -------------------------------------------------------------------
# Rspamd milter
# -------------------------------------------------------------------
smtpd_milters = inet:127.0.0.1:11332
non_smtpd_milters = inet:127.0.0.1:11332
milter_default_action = accept
milter_protocol = 6
Several things worth noting here.
mydestination does not include yourdomain.com or any domain in virtual_mailbox_domains. A domain can only appear in one of these; putting it in both causes a mail loop error.
inet_protocols = ipv4 limits Postfix to IPv4. If February has a working IPv6 address and reverse DNS, change this to all. Without correct IPv6 reverse DNS, leaving IPv6 enabled causes delivery problems.
The square brackets around smtp.fastmail.com in relayhost tell Postfix to connect directly without looking up an MX record. This is required for relay hosts.
The Rspamd milter listens on 127.0.0.1:11332 by default. The Rspamd install article covers confirming this. milter_default_action = accept means if Rspamd is unavailable, mail still passes through rather than being rejected. Change this to tempfail if you want mail to queue rather than bypass filtering during Rspamd outages.
Fastmail relay credentials
Create the SASL password file:
sudo nano /etc/postfix/sasl_passwd
Add one line:
[smtp.fastmail.com]:587 you@fastmail.com:your-app-password-here
The app password from the Fastmail article goes here. The format is [host]:port username:password.
Secure the file and build the index:
sudo chmod 600 /etc/postfix/sasl_passwd
sudo postmap /etc/postfix/sasl_passwd
sudo chmod 600 /etc/postfix/sasl_passwd.db
The sasl_passwd.db file is what Postfix actually reads. The plain text file should be kept but is only needed to regenerate the .db if credentials change.
master.cf — enabling the submission port
The installer enables port 25 (SMTP) by default. Port 587 (submission) needs to be explicitly enabled for authenticated mail clients to submit outbound mail. It is present but commented out in master.cf by default.
Edit /etc/postfix/master.cf and find the submission block. Uncomment and configure it:
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_relay_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
The submission service runs a separate smtpd instance with stricter settings than port 25: TLS is required (not just offered), SASL authentication is mandatory, and the relay restriction only allows authenticated users to send. This prevents unauthenticated senders from using the submission port.
The leading spaces before -o lines are significant in master.cf. They indicate continuation of the preceding service definition. Use spaces, not tabs.
UFW rules
Open the ports Postfix needs:
# Port 25: receive inbound mail from the internet
sudo ufw allow 25/tcp
# Port 587: authenticated submission from mail clients
# Restrict to LAN and VPN — clients use these networks
sudo ufw allow from 192.168.1.0/24 to any port 587
sudo ufw allow from 10.10.0.0/24 to any port 587
Port 25 must be open to the internet for other mail servers to deliver mail to February. Port 587 should be restricted: only clients on the LAN or VPN need to submit mail through February.
Applying the configuration
Validate main.cf syntax:
sudo postfix check
Fix any errors reported before restarting. Postfix is strict about syntax and will refuse to start with certain errors rather than silently ignoring them.
Restart Postfix:
sudo systemctl restart postfix
sudo systemctl status postfix
Check the mail log for startup messages:
sudo journalctl -u postfix -n 30
Confirm Postfix is listening on the right ports:
sudo ss -tlnp | grep master
You should see Postfix listening on 0.0.0.0:25 and 0.0.0.0:587.
Testing inbound delivery
Send a test message to your domain from an external address and watch the mail log:
sudo tail -f /var/log/mail.log
A successful inbound delivery shows a chain like:
postfix/smtpd: connect from mail.sender.com
postfix/cleanup: message-id=<...>
postfix/qmgr: from=<sender@example.com>, size=..., nrcpt=1
postfix/lmtp: to=<you@yourdomain.com>, relay=...[private/dovecot-lmtp], status=sent (250 2.0.0 ...)
postfix/qmgr: removed
The relay showing private/dovecot-lmtp confirms mail is being handed to Dovecot correctly.
Testing outbound relay
Send a test message to an external address:
echo "Test outbound mail from February" | mail -s "Test" you@gmail.com
Watch the mail log:
sudo tail -f /var/log/mail.log
A successful relay through Fastmail shows:
postfix/smtp: to=<you@gmail.com>, relay=smtp.fastmail.com[...]:587, status=sent (250 2.0.0 Ok)
If you see status=deferred with a SASL authentication error, the credentials in sasl_passwd are wrong or the .db file needs regenerating. If you see a TLS error, smtp_tls_CAfile may not include the certificate needed to verify Fastmail’s server.
Testing the open relay check
A correctly configured Postfix should refuse to relay mail for third parties who have not authenticated. Test this:
telnet february.home.arpa 25
EHLO test.example.com
MAIL FROM:<attacker@test.com>
RCPT TO:<victim@gmail.com>
Postfix should return 554 5.7.1 Relay access denied at the RCPT TO step. If it accepts the command, the relay restrictions are misconfigured. Check smtpd_relay_restrictions and smtpd_recipient_restrictions in main.cf.
Checking the queue
Mail that fails to deliver is held in the Postfix queue. Check it:
mailq
An empty queue on a working server is normal. If mail is stuck with deferred status, the reason appears alongside the queue entry. Common causes are relay authentication failures, TLS verification errors, or the destination server being temporarily unavailable.
Force immediate delivery of queued mail:
sudo postqueue -f
The complete picture
At this point, February’s mail stack is fully assembled:
Inbound mail arrives on port 25, passes through Postfix’s restriction checks, is evaluated by Rspamd via the milter interface, and is delivered to Dovecot via LMTP. Dovecot writes it to the correct virtual mailbox in Maildir format, running Sieve filters that sort spam to Junk. Mail clients connect to Dovecot on port 993 over IMAPS to read their mail.
Outbound mail from services on February is submitted to Postfix locally, which hands it to Fastmail’s SMTP server over an authenticated TLS connection on port 587. Fastmail delivers it using its established sending infrastructure. Mail clients submit outbound mail through port 587 with SASL authentication, which Postfix verifies via Dovecot.
The remaining articles cover the DNS records that make deliverability work, and an end-to-end test that verifies the whole stack.