Install and Configure Postfix and Dovecot on Ubuntu 26.04

This is the second post of our Setting up and configuring a mail server series. In this post we will show you how to install and configure Postfix and Dovecot, the two main components of our mail system.
Postfix is an open-source mail transfer agent (MTA), a service used to send and receive emails. Dovecot is an IMAP/POP3 server and in our setup it will also handle local delivery and user authentication.
This tutorial was written for Ubuntu 26.04, however the same steps with small modifications should work on any newer version of Ubuntu .
Prerequisites
Before continuing with this tutorial, make sure you are logged in as a user with sudo privileges
and that you have completed the first part of this series
, where we installed PostfixAdmin, the MySQL database, and the Let’s Encrypt certificate for mail.linuxize.com.
Install Postfix and Dovecot
Ubuntu 26.04 ships Postfix 3.10 and Dovecot 2.4 in the default repositories, so we no longer need the Dovecot community repository.
Set the Postfix mailer type and hostname so the package installer does not prompt for them, and install the Postfix and Dovecot packages along with the SQL adapters and the Sieve plugin:
sudo apt update
sudo debconf-set-selections <<< "postfix postfix/mailname string $(hostname -f)"
sudo debconf-set-selections <<< "postfix postfix/main_mailer_type string 'Internet Site'"
sudo apt install -y postfix postfix-mysql dovecot-core dovecot-imapd dovecot-lmtpd dovecot-pop3d dovecot-mysql dovecot-sieve dovecot-managesievedConfirm the installed versions:
postconf mail_version
dovecot --versionmail_version = 3.10.6
2.4.2 (0962ed2104)Postfix Configuration
We will set up Postfix to use virtual mailboxes and domains.
Start by creating the sql configuration files which instruct Postfix how to access the MySQL database
created in the first part of this series
:
sudo mkdir -p /etc/postfix/sql
sudo chmod 750 /etc/postfix/sqlOpen your text editor and create the following files:
user = postfixadmin
password = postfixadmin_db_password
hosts = 127.0.0.1
dbname = postfixadmin
query = SELECT domain FROM domain WHERE domain='%s' AND active = '1'user = postfixadmin
password = postfixadmin_db_password
hosts = 127.0.0.1
dbname = postfixadmin
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'user = postfixadmin
password = postfixadmin_db_password
hosts = 127.0.0.1
dbname = postfixadmin
query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' AND alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'user = postfixadmin
password = postfixadmin_db_password
hosts = 127.0.0.1
dbname = postfixadmin
query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' AND alias.address = CONCAT('@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'user = postfixadmin
password = postfixadmin_db_password
hosts = 127.0.0.1
dbname = postfixadmin
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'user = postfixadmin
password = postfixadmin_db_password
hosts = 127.0.0.1
dbname = postfixadmin
query = SELECT maildir FROM mailbox,alias_domain WHERE alias_domain.alias_domain = '%d' AND mailbox.username = CONCAT('%u', '@', alias_domain.target_domain) AND mailbox.active = 1 AND alias_domain.active='1'The SQL map files contain the database password, so restrict access to the Postfix group:
sudo find /etc/postfix/sql -type f -name '*.cf' -exec chgrp postfix {} +
sudo find /etc/postfix/sql -type f -name '*.cf' -exec chmod 640 {} +Once the SQL configuration files are in place, update the main Postfix configuration to include information about the virtual domains, users, and aliases:
sudo postconf -e "myhostname = mail.linuxize.com"
sudo postconf -e "mydomain = linuxize.com"
sudo postconf -e "myorigin = \$mydomain"
sudo postconf -e "inet_interfaces = all"
sudo postconf -e "inet_protocols = ipv4"
sudo postconf -e "mydestination = localhost"
sudo postconf -e "virtual_mailbox_domains = mysql:/etc/postfix/sql/mysql_virtual_domains_maps.cf"
sudo postconf -e "virtual_alias_maps = mysql:/etc/postfix/sql/mysql_virtual_alias_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_domain_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf"
sudo postconf -e "virtual_mailbox_maps = mysql:/etc/postfix/sql/mysql_virtual_mailbox_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf"The local delivery agent will deliver incoming emails to the user mailboxes. Run the following command to set Dovecot’s LMTP service as the default mail delivery transport:
sudo postconf -e "virtual_transport = lmtp:unix:private/dovecot-lmtp"Set the TLS parameters using the previously generated Let’s Encrypt SSL certificate:
sudo postconf -e "smtpd_tls_cert_file = /etc/letsencrypt/live/mail.linuxize.com/fullchain.pem"
sudo postconf -e "smtpd_tls_key_file = /etc/letsencrypt/live/mail.linuxize.com/privkey.pem"
sudo postconf -e "smtpd_tls_security_level = may"
sudo postconf -e "smtp_tls_security_level = may"Configure the authenticated SMTP settings and hand off authentication to Dovecot:
sudo postconf -e "smtpd_sasl_type = dovecot"
sudo postconf -e "smtpd_sasl_path = private/auth"
sudo postconf -e "smtpd_sasl_auth_enable = yes"
sudo postconf -e "smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination"Edit the Postfix master configuration file master.cf to enable the submission port (587):
Open the file with your text editor and uncomment or add the following lines:
submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_recipient_restrictions=permit_sasl_authenticated,rejectRun a configuration check before restarting Postfix:
sudo postfix checkIf postfix check reports that /var/spool/postfix/etc/resolv.conf is not owned by root, fix the chroot resolver file and rerun the check:
sudo chown root:root /var/spool/postfix/etc/resolv.conf
sudo chmod 644 /var/spool/postfix/etc/resolv.conf
sudo postfix checkRestart the Postfix service for the changes to take effect:
sudo systemctl restart postfixConfigure Dovecot
Dovecot 2.4 reorganizes how SQL authentication is configured. The legacy /etc/dovecot/dovecot-sql.conf.ext file is no longer used. SQL connection settings live in a top-level mysql { } block, and the passdb sql and userdb sql queries live inline. Configure them through /etc/dovecot/conf.d/auth-sql.conf.ext.
If a legacy dovecot-sql.conf.ext file exists from an earlier install, remove it:
sudo rm -f /etc/dovecot/dovecot-sql.conf.extBack up the current configuration tree before editing:
sudo cp -a /etc/dovecot /etc/dovecot.backup.$(date +%F)Open the conf.d/10-auth.conf file and edit the following lines so cleartext authentication is disabled and only the SQL passdb is used. Delete (not just comment) the !include auth-system.conf.ext line so the system PAM passdb is not loaded:
auth_allow_cleartext = no
auth_mechanisms = plain login
!include auth-sql.conf.extdisable_plaintext_auth setting with auth_allow_cleartext, with inverted meaning. Do not use disable_plaintext_auth on Dovecot 2.4.Open the conf.d/auth-sql.conf.ext file. The shipped file is fully commented out and uses obsolete Dovecot 2.3 syntax (args = ..., connect = ..., password_query = ...). Replace its contents with the Dovecot 2.4 form below:
sql_driver = mysql
mysql 127.0.0.1 {
user = postfixadmin
password = postfixadmin_db_password
dbname = postfixadmin
}
passdb sql {
default_password_scheme = ARGON2I
query = SELECT username AS "user", password FROM mailbox WHERE username = '%{user}' AND active='1'
}
userdb sql {
query = SELECT CONCAT('/var/mail/vmail/', maildir) AS home, 5000 AS uid, 5000 AS gid FROM mailbox WHERE username = '%{user}' AND active='1'
iterate_query = SELECT username AS "user" FROM mailbox WHERE active='1'
}Each query and iterate_query value must stay on a single physical line. Wrapped lines are parsed as new settings and produce errors such as Unknown setting: active. Note that %u was replaced by %{user} in 2.4, and default_pass_scheme was renamed to default_password_scheme.
Restrict access to the file because it contains the database password:
sudo chown root:dovecot /etc/dovecot/conf.d/auth-sql.conf.ext
sudo chmod 640 /etc/dovecot/conf.d/auth-sql.conf.extEdit the conf.d/10-mail.conf file and set the mail driver and storage location:
mail_driver = maildir
mail_home = /var/mail/vmail/%{user | domain}/%{user | username}
mail_path = /var/mail/vmail/%{user | domain}/%{user | username}
mail_uid = vmail
mail_gid = vmail
first_valid_uid = 5000
last_valid_uid = 5000If the file contains the default mbox settings, comment them out. Do not leave mail_inbox_path = /var/mail/%{user} active, because we are using virtual Maildirs.
Open the conf.d/10-master.conf file and set the lmtp and auth UNIX listeners so Postfix can talk to Dovecot:
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
}
service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
}Open the conf.d/10-ssl.conf file and configure SSL/TLS using the new Dovecot 2.4 keys:
ssl = required
ssl_server_cert_file = /etc/letsencrypt/live/mail.linuxize.com/fullchain.pem
ssl_server_key_file = /etc/letsencrypt/live/mail.linuxize.com/privkey.pem<file read-prefix on SSL settings. The old ssl_cert = </path form is now interpreted as a literal PEM string and produces Fatal: ... failed: File name too long at startup. Use ssl_server_cert_file and ssl_server_key_file with a plain path instead.Open the conf.d/15-lda.conf file and set the postmaster address:
postmaster_address = postmaster@linuxize.comOpen the conf.d/20-lmtp.conf file and set the LMTP protocol block as follows:
protocol lmtp {
postmaster_address = postmaster@linuxize.com
mail_plugins = sieve
auth_username_format = %{user | lower}
}mail_plugins = $mail_plugins sieve no longer expands the variable; the $mail_plugins token is treated as a literal module name and Dovecot fails with Plugin '$mail_plugins' not found from directory /usr/lib/dovecot/modules. Use mail_plugins = sieve directly. The new default auth_username_format = %{user | username | lower} strips the domain from the lookup key and breaks PostfixAdmin SQL lookups with 550 5.1.1 User does not exist. Override it to %{user | lower} so the full local@domain value reaches the SQL query.Open the conf.d/90-sieve.conf file and configure the Sieve script paths:
sieve_script personal {
driver = file
path = ~/sieve
active_path = ~/.dovecot.sieve
}plugin { } section and replaced the flat sieve = file:~/sieve;active=~/.dovecot.sieve form with a named sieve_script block. The old plugin { sieve = ... } block raises Unknown section name: plugin, and the flat sieve = ... key raises Unknown setting: sieve.Run a configuration check and then restart Dovecot and Postfix so all changes take effect together:
sudo doveconf -n
sudo systemctl restart dovecot postfixTest the Setup
Before moving on to Rspamd, verify that Postfix can resolve a virtual mailbox, that Dovecot accepts the test password, and that local delivery works end-to-end.
Test the Postfix SQL maps using the test mailboxes you created in PostfixAdmin:
sudo postmap -q linuxize.com mysql:/etc/postfix/sql/mysql_virtual_domains_maps.cf
sudo postmap -q user@linuxize.com mysql:/etc/postfix/sql/mysql_virtual_mailbox_maps.cf
sudo postmap -q postmaster@linuxize.com mysql:/etc/postfix/sql/mysql_virtual_alias_maps.cfEach command should return a non-empty result. An empty result means the mailbox or alias does not exist in the PostfixAdmin database.
Test that Dovecot can authenticate the test mailbox:
sudo doveadm auth test user@linuxize.comSend a test message through the local stack and confirm it lands in the Maildir:
sudo apt install -y swaks
swaks --to user@linuxize.com --from admin@linuxize.com --server 127.0.0.1
sudo find /var/mail/vmail -type f -path '*/new/*' | headOpen an authenticated submission session over port 587 and send a message through the full submission path:
swaks --to user@linuxize.com --from user@linuxize.com --server mail.linuxize.com --port 587 --auth LOGIN --auth-user user@linuxize.com --auth-password 'MAILBOX_PASSWORD' --tlsFinally, confirm the IMAP listener accepts a TLS connection on port 993:
openssl s_client -connect mail.linuxize.com:993 -servername mail.linuxize.comThe openssl s_client
output shows the certificate chain, negotiated protocol, cipher, and verification result, which helps confirm that Dovecot is serving the expected TLS certificate.
After the TLS handshake completes, log in and list the mailboxes, then log out:
a login user@linuxize.com MAILBOX_PASSWORD
b list "" "*"
c logoutTroubleshooting
LMTP delivery fails before the server greeting
If Postfix logs dsn=4.4.2, status=deferred (lost connection ... while receiving the initial server greeting) and journalctl -u dovecot shows Plugin '$mail_plugins' not found, your protocol lmtp block still uses the 2.3-style mail_plugins = $mail_plugins sieve. Change it to mail_plugins = sieve and restart Dovecot.
LMTP rejects virtual users as missing
If LMTP rejects messages with 550 5.1.1 <user@linuxize.com> User does not exist, your protocol lmtp block uses the new Dovecot 2.4 default auth_username_format = %{user | username | lower}, which strips the domain. Override it to %{user | lower} so the full address reaches the SQL query.
Dovecot reports File name too long for SSL settings
If Dovecot fails to start with Fatal: ... ssl_cert/ssl_key ... File name too long, you are still using the 2.3-style ssl_cert = </path. Replace it with ssl_server_cert_file and ssl_server_key_file and use a plain path.
postfix check reports a chroot resolver ownership warning
If postfix check reports a chroot ownership warning on /var/spool/postfix/etc/resolv.conf, fix the copied resolver file:
sudo chown root:root /var/spool/postfix/etc/resolv.conf
sudo chmod 644 /var/spool/postfix/etc/resolv.confConclusion
By now you should have a fully functional mail system with virtual users, authenticated submission, IMAP over TLS, and local delivery through Dovecot LMTP. In the next part of this series, we will show you how to install and integrate Rspamd so the server can scan messages for spam and sign outbound mail with DKIM.
Linuxize Weekly Newsletter
A quick weekly roundup of new tutorials, news, and tips.
About the authors

Dejan Panovski
Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 800+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.
View author page