Setting Up a Postfix + Dovecot Mail Server on Rocky Linux 9 (with CentOS 7 Differences)
When you need to own your mail flow yourself — sending alerts from internal systems or notifications to registered users — Postfix can handle sending (MTA) and Dovecot can handle receiving (IMAP/POP3); together they make a perfectly usable self-hosted mail service. The main environment of this post is Rocky Linux 9, and everything from installation through the dual config check to a closed local-delivery loop was verified in a container: postfix check produced no output, ports 25/143 listen normally, and the test mail left status=sent (delivered to maildir) in maillog.
Cross-server sending and receiving depend on MX records and port 25 reachability, which a container cannot verify — the verification boundary is marked at the end; the CentOS 7 differences longtime readers care about are gathered in the “Historical Version Differences” section.
Prerequisites
- Rocky Linux 9 (or a same-family RHEL 9 distribution), with root privileges;
- A usable domain name (example.com throughout this post) whose A/MX records can point to this machine;
- Ports 25/110/143 and the like not blocked by your cloud provider (most clouds block 25 by default; you must request an unblock to send).
1. Install the Required Software
Install Postfix, Dovecot, and cyrus-sasl-plain for secure communication:
1 | dnf install postfix dovecot cyrus-sasl-plain |
Right after installation, add one easily missed step: run newaliases to generate /etc/aliases.db. Without it, local delivery stays stuck in deferred and maillog reports alias database unavailable (hit in testing; see Common Errors).
1 | newaliases |
Then confirm the versions and write them into your deployment record. Verified output (Rocky Linux 9 container):
1 | postconf mail_version && dovecot --version |

2. Configure Postfix
Edit the main configuration file:
1 | vi /etc/postfix/main.cf |
Add or modify the following lines to stand up the basic mail service:
1 | myhostname = mail.example.com |
Note: the original article’s list also carried a line
mta_sts_policy = enforce. Verified on Postfix 3.5.25 it only draws anunused parameterwarning and does not block check, but it is not a valid configuration; the safe move is to delete it from main.cf, and implement MTA-STS via the dedicated approach in the official docs if you need it.
A few key items:
- myhostname: the mail server’s hostname.
- mydestination: the domains mail may be delivered to.
- home_mailbox: the user mailbox directory; the
Maildir/format means one file per message. - smtpd_sasl_auth_enable: enables SASL authentication, with the actual authentication delegated to Dovecot.
- smtpd_recipient_restrictions: only the local network and SASL-authenticated users may send; every other unknown destination is rejected, so the server cannot be abused as an open relay.
After editing, run a syntax check first — no output means the config is valid (verified, exit code 0):
1 | postfix check |
Then restart and enable at boot:
1 | systemctl restart postfix |
Verify the service status: systemctl status postfix should show active (running). The container has no systemd; in testing postfix start brought the service up directly, which is equivalent.
3. Configure Dovecot
Dovecot handles receiving (IMAP/POP3) and also provides the SASL authentication service to Postfix. Edit the main configuration file:
1 | vi /etc/dovecot/dovecot.conf |
Make sure it contains the following:
1 | disable_plaintext_auth = no |
mail_location points at the same mailbox set as Postfix’s home_mailbox = Maildir/; the unix_listener inside service auth is the very socket that Postfix’s smtpd_sasl_path = private/auth refers to — the two sides connect their authentication through it.
After saving, validate with doveconf -n — it prints the effective non-default configuration Dovecot is actually running; an error means a syntax problem. Verified output including the lines disable_plaintext_auth = no and mail_location = maildir:~/Maildir means the settings took effect.
Start Dovecot:
1 | systemctl restart dovecot |
In the container both services started fine without systemd (postfix start and the dovecot command), and ss -ltn confirmed both 25 and 143 listening. One container-specific trap: conf.d/10-ssl.conf defaults ssl_cert to the self-signed certificate /etc/pki/dovecot/certs/dovecot.pem, which does not exist in a minimal container, so dovecot refuses to start at all — add a self-signed certificate (or temporarily set ssl = no) and it comes up; production must use a real certificate.
4. Test the Mail Function
The local delivery loop closes end to end, as verified below. First create a local recipient user, then send a test mail:
1 | useradd mailtest |
After sending, tail /var/log/maillog; success means seeing status=sent. The verified last line was status=sent (delivered to maildir), a new mail file appeared under /home/mailtest/Maildir/new/, and cat showed subject and body character-for-character identical to what was sent:

On the receiving side, log in with a mail client, or read directly from the command line with mutt:
1 | mutt |
Note: container testing covered up to the closed local-delivery loop; cross-server sending and receiving depend on MX records and port 25 reachability and must be verified in a real environment. Sources: local delivery is from an actual run’s output; external sending/receiving follows the standard practice in the Postfix/Dovecot official documentation.
Historical Version Differences (CentOS 7)
- Versions: on CentOS 7 these are postfix 2.10.1 and dovecot 2.2.36; the package names are identical and the configuration syntax is the same — just swap
dnfback toyum. Every configuration item in this post is valid on both generations. - System repos: CentOS 7 went end-of-life on 2024-06-30 and its mirrorlist is gone; you must switch to the vault archive (see Common Errors). Verified: installation works normally after the fix.
- Tolerance of unknown parameters: Postfix 3.5.x on Rocky 9 only warns about parameters it does not recognize; 2.10.1’s behavior was not tested, and to be safe do not keep the
mta_sts_policyline on either generation.
When It Breaks
If a bad config edit keeps the services from starting, locate the syntax error with postfix check / doveconf -n first; for a full rollback, dnf remove postfix dovecot, reinstall, and configure again per this post — both programs keep their configuration under /etc/postfix/ and /etc/dovecot/, so backing up those two directories lets you restore the scene at any time.
Common Errors
- Local delivery stuck in deferred, maillog reports
alias database unavailable:/etc/aliases.dbwas never generated. In testing the mail sat in the queue (status=deferred (alias database unavailable)); runningnewaliasesdrained the queue intostatus=sentimmediately. - maillog is empty in a container: containers ship without a syslog daemon by default. Postfix 3.4+ can write its log directly by adding
maillog_file = /var/log/maillogto main.cf (verified working), or you can run a syslog service. - dovecot won’t start in a container, complaining ssl_cert can’t be opened:
conf.d/10-ssl.confreferences a self-signed certificate by default that a minimal container lacks; generate a self-signed certificate or temporarily setssl = no, and production must carry a real certificate. Cannot find a valid baseurl for repo: base/7: CentOS 7’s mirrorlist went offline at EOL. Replacemirror.centos.orgwithvault.centos.orgin/etc/yum.repos.d/*.repo, comment out the mirrorlist lines, and enable baseurl (verified: installation works normally after the fix).- Can send but not receive: check in order whether mydestination includes the recipient domain, whether the MX record points to this machine, and whether the firewall opens port 25.
Caveats
- Use firewalld or iptables for access control. A mail server sits on the public internet, so tighten the access policy.
- Production environments should configure SSL/TLS encryption to protect mail data in transit.
disable_plaintext_auth = noallows plaintext authentication; use it only temporarily in test environments, and before going live always pair it with TLS and switch it back to yes.- All three rules of
smtpd_recipient_restrictionsare required; withoutreject_unauth_destination, the server can be used by outsiders as a springboard for spam campaigns.
The full value of a self-hosted mail service is only realized when all three of “can send, can receive, won’t be used as a relay” hold together: install per this post, get both postfix check and doveconf -n to pass, verify status=sent in a local delivery test, then test one message along each of the send and receive paths in a real environment — only then has the service truly answered the need this post opened with.
This article was rebuilt from the author’s CSDN blog posts written between 2020 and 2024, originally published on CSDN.