Install and Configure Postfix and Dovecot on Ubuntu 26.04

By 

Updated on

10 min read

Postfix and Dovecot mail flow illustration

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:

Terminal
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-managesieved

Confirm the installed versions:

Terminal
postconf mail_version
dovecot --version
output
mail_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 :

Terminal
sudo mkdir -p /etc/postfix/sql
sudo chmod 750 /etc/postfix/sql

Open your text editor and create the following files:

/etc/postfix/sql/mysql_virtual_domains_maps.cfcfg
user = postfixadmin
password = postfixadmin_db_password
hosts = 127.0.0.1
dbname = postfixadmin
query = SELECT domain FROM domain WHERE domain='%s' AND active = '1'
/etc/postfix/sql/mysql_virtual_alias_maps.cfcfg
user = postfixadmin
password = postfixadmin_db_password
hosts = 127.0.0.1
dbname = postfixadmin
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'
/etc/postfix/sql/mysql_virtual_alias_domain_maps.cfcfg
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'
/etc/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cfcfg
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'
/etc/postfix/sql/mysql_virtual_mailbox_maps.cfcfg
user = postfixadmin
password = postfixadmin_db_password
hosts = 127.0.0.1
dbname = postfixadmin
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'
/etc/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cfcfg
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:

Terminal
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:

Terminal
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"
Info
The postconf command displays the actual values of configuration parameters, changes configuration parameter values, or displays other configuration information about the Postfix mail system.

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:

Terminal
sudo postconf -e "virtual_transport = lmtp:unix:private/dovecot-lmtp"

Set the TLS parameters using the previously generated Let’s Encrypt SSL certificate:

Terminal
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:

Terminal
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:

/etc/postfix/master.cfini
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,reject

Run a configuration check before restarting Postfix:

Terminal
sudo postfix check

If postfix check reports that /var/spool/postfix/etc/resolv.conf is not owned by root, fix the chroot resolver file and rerun the check:

Terminal
sudo chown root:root /var/spool/postfix/etc/resolv.conf
sudo chmod 644 /var/spool/postfix/etc/resolv.conf
sudo postfix check

Restart the Postfix service for the changes to take effect:

Terminal
sudo systemctl restart postfix

Configure 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:

Terminal
sudo rm -f /etc/dovecot/dovecot-sql.conf.ext

Back up the current configuration tree before editing:

Terminal
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:

/etc/dovecot/conf.d/10-auth.confcfg
auth_allow_cleartext = no
auth_mechanisms = plain login

!include auth-sql.conf.ext
Info
Dovecot 2.4 replaced the disable_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:

/etc/dovecot/conf.d/auth-sql.conf.extcfg
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:

Terminal
sudo chown root:dovecot /etc/dovecot/conf.d/auth-sql.conf.ext
sudo chmod 640 /etc/dovecot/conf.d/auth-sql.conf.ext

Edit the conf.d/10-mail.conf file and set the mail driver and storage location:

/etc/dovecot/conf.d/10-mail.confcfg
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 = 5000

If 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:

/etc/dovecot/conf.d/10-master.confcfg
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:

/etc/dovecot/conf.d/10-ssl.confcfg
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
Info
Dovecot 2.4 dropped the <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:

/etc/dovecot/conf.d/15-lda.confcfg
postmaster_address = postmaster@linuxize.com

Open the conf.d/20-lmtp.conf file and set the LMTP protocol block as follows:

/etc/dovecot/conf.d/20-lmtp.confcfg
protocol lmtp {
  postmaster_address = postmaster@linuxize.com
  mail_plugins = sieve
  auth_username_format = %{user | lower}
}
Info
Two important Dovecot 2.4 changes apply to this block. The 2.3-style 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:

/etc/dovecot/conf.d/90-sieve.confcfg
sieve_script personal {
  driver = file
  path = ~/sieve
  active_path = ~/.dovecot.sieve
}
Info
Dovecot 2.4 and Pigeonhole removed the 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:

Terminal
sudo doveconf -n
sudo systemctl restart dovecot postfix

Test 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:

Terminal
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.cf

Each 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:

Terminal
sudo doveadm auth test user@linuxize.com

Send a test message through the local stack and confirm it lands in the Maildir:

Terminal
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/*' | head

Open an authenticated submission session over port 587 and send a message through the full submission path:

Terminal
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' --tls

Finally, confirm the IMAP listener accepts a TLS connection on port 993:

Terminal
openssl s_client -connect mail.linuxize.com:993 -servername mail.linuxize.com

The 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:

text
a login user@linuxize.com MAILBOX_PASSWORD
b list "" "*"
c logout

Troubleshooting

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:

Terminal
sudo chown root:root /var/spool/postfix/etc/resolv.conf
sudo chmod 644 /var/spool/postfix/etc/resolv.conf

Conclusion

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

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