Migrating old Dovecot configuration to Dovecot 2.4 (Debian Trixie)
The new Debian version “Trixie” has been available for upgrading from Debian “Bookworm” for several weeks now – and as a result, some of my readers have decided to update their mail server setup from my “Mail server with Dovecot, Postfix, MySQL, and Rspamd on Debian 10 Buster [v1.0] (German)”. The setup described for Debian Buster works just as well for Debian Bookworm – but Debian Trixie brings a change:
The configuration syntax has changed significantly in some respects, and old configurations from previous Dovecot versions can no longer be read. This article discusses the changes in the new version 2.4 and explains the migration using the example of the mail server instructions mentioned above. All changes take place in the file /etc/dovecot/dovecot.conf. No further changes (e.g., to the database schema or similar) are necessary.
Overall, the following has changed:
- The way configuration parameters can be nested
- Names of individual parameters
- Variable names and functions
All changes are described in the Dovecot documentation. However, if you followed my instructions when setting up your mail server, you can simply continue reading and follow the steps described to achieve a working configuration.
Step 1: Add config version numbers
Dovecot 2.4 introduces versioning of the configuration syntax. Therefore, the following lines must be added at the beginning of the file:
# Dovecot config and storage versions
dovecot_config_version = 2.4.0
dovecot_storage_version = 2.4.0
It seems that the omissions of the past have now been rectified ;-). Future changes to the syntax can then be intercepted by Dovecot itself or warnings can be issued in the event of changes.
SSL settings
The SSL settings are renamed or changed as follows:
ssl_cert=>ssl_server_cert_file(and omit the angle bracket at the beginning)ssl_key=>ssl_server_key_file(and omit the angle bracket at the beginning)ssl_dh=>ssl_server_dh_file(and omit the angle bracket at the beginning)ssl_prefer_server_ciphers=>ssl_server_prefer_ciphers. Values should not be “yes” or “no”, but ‘client’ or “server”. Default: Client. Setting can be removed.ssl_min_protocol: can be omitted, default is already TLSv1.2disable_plaintext_auth=yes=>auth_allow_cleartext=no. Is default - can be omitted.
Overall, this results in:
ssl = required
ssl_server_cert_file = /etc/acme.sh/mydomain.tld/fullchain.pem
ssl_server_key_file = /etc/acme.sh/mydomain.tld/privkey.pem
ssl_server_dh_file = /etc/dovecot/dh4096.pem
ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384: ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
PassDB / UserDB and auth_username_format
The two PassDB and UserDB sections become:
passdb sql {
query = SELECT username AS user, domain, password FROM accounts WHERE username = ‘%{user | username | lower}’ AND domain = ‘%{user | domain | lower}’ and enabled = true;
}
userdb sql {
query = SELECT concat(quota, ‘M’) AS quota_storage_size FROM accounts WHERE username = ‘%{user | username | lower}’ AND domain = ‘%{user | domain | lower}’ AND sendonly = false;
iterate_query = SELECT username, domain FROM accounts where sendonly = false;
}
Pay special attention to the customized variables, e.g., %{user | username | lower }. Behind concat(quota, ‘M’) AS quota_storage_size there is also a small bug fix that I want to include here. ;-)
In addition, the variable is also customized for auth_username_format:
auth_mechanisms = plain login
auth_username_format = %{user | lower }
In order for the UserDB and PassDB sections just defined to work at all, the access data for the MySQL database is defined in a new SQL section:
sql_driver = mysql
mysql /var/run/mysqld/mysqld.sock {
user = vmail
password = mypassword
dbname = vmail
}
mypassword must of course be customized!
The file /etc/dovecot/dovecot-sql.conf can be deleted completely—it is no longer needed.
Mail location
The definition of the mail storage location mail_location is split into several parameters and replaced as follows:
mail_driver = maildir
mail_path = ~/mail
mailbox_list_layout = fs
In addition, the mail_home parameter is given new variable names:
mail_home = /var/vmail/mailboxes/%{user | domain }/%{user | username }
Protocols
The protocols setting and the two sections protocol imap and protocol lmtp become this block:
protocols {
lmtp = yes
imap = yes
sieve = yes
}
protocol imap {
mail_plugins {
imap_quota = yes
imap_sieve = yes
}
mail_max_userip_connections = 50
imap_idle_notify_interval = 29 mins
}
protocol lmtp {
mail_plugins {
sieve = yes
notify = yes
push_notification = yes
}
postmaster_address = postmaster@mydomain.tld
}
Plugins Section
The plugins segment
plugins {
...
}
no longer exists. Instead, the content becomes global.
Sieve Plugin Settings
sieve_plugins,sieve_before, andsievebecome:
sieve_plugins {
sieve_imapsieve = yes
sieve_extprograms = yes
}
sieve_script spam-global {
type = before
path = /var/vmail/sieve/global/spam-global.sieve
}
sieve_script personal {
type = personal
path = /var/vmail/sieve/%{user | domain }/%{user | username }/scripts
active_path = /var/vmail/sieve/%{user | domain }/%{user | username }/active-script.sieve
}
All imapsieve* parameters become:
# From Mailbox to Spam
mailbox Spam {
sieve_script spam {
type = before
cause = copy
path = /var/vmail/sieve/global/learn-spam.sieve
}
}
# From Spam to another folder (learn HAM)
imapsieve_from Spam {
sieve_script ham {
type = before
cause = copy
path = /var/vmail/sieve/global/learn-ham.sieve
}
}
sieve_global_extensions becomes:
sieve_global_extensions {
vnd.dovecot.pipe = yes
}
Quota
quota and quota_exceeded_message become:
quota “User quota” {
driver = count
}
quota_exceeded_message = User %{user} has exceeded the storage volume. / User %{user} has exhausted allowed storage space.
The old “fs” driver for quota should no longer be used. Instead, count is now used. quota_exceeded_message also gets new variable names.
The quota plugin is activated in the new mail_plugins blog:
mail_plugins {
quota = yes
}
imap_quota has also been activated in the previously mentioned protocol imap block.
Complete sample configuration
For easier comparison, here is the complete file /etc/dovecot/dovecot.conf again:
# Dovecot config and storage versions
dovecot_config_version = 2.4.0
dovecot_storage_version = 2.4.0
###
### Protocol settings
#############################
protocols {
lmtp = yes
imap = yes
sieve = yes
}
protocol imap {
mail_plugins {
imap_quota = yes
imap_sieve = yes
}
mail_max_userip_connections = 50
imap_idle_notify_interval = 29 mins
}
protocol lmtp {
mail_plugins {
sieve = yes
notify = yes
push_notification = yes
}
postmaster_address = postmaster@mydomain.tld
}
##
## TLS Config
## Quelle: https://ssl-config.mozilla.org/#server=dovecot&version=2.3.9&config=intermediate&openssl=1.1.1d&guideline=5.4
##
ssl = required
ssl_server_cert_file = /etc/acme.sh/mydomain.tld/fullchain.pem
ssl_server_key_file = /etc/acme.sh/mydomain.tld/privkey.pem
ssl_server_dh_file = /etc/dovecot/dh4096.pem
ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
###
### Dovecot services
################################
service imap-login {
inet_listener imap {
port = 143
}
}
service managesieve-login {
inet_listener sieve {
port = 4190
}
}
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0660
group = postfix
user = postfix
}
user = vmail
}
service auth {
### Auth socket für Postfix
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
### Auth socket für LMTP-Dienst
unix_listener auth-userdb {
mode = 0660
user = vmail
group = vmail
}
}
###
### SQL settings
###
sql_driver = mysql
mysql /var/run/mysqld/mysqld.sock {
user = vmail
password = mypassword
dbname = vmail
}
###
### Client authentication
#############################
auth_mechanisms = plain login
auth_username_format = %{user | lower }
passdb sql {
query = SELECT username AS user, domain, password FROM accounts WHERE username = '%{user | username | lower }' AND domain = '%{user | domain | lower}' and enabled = true;
}
userdb sql {
query = SELECT concat(quota, 'M') AS quota_storage_size FROM accounts WHERE username = '%{user | username | lower }' AND domain = '%{user | domain | lower}' AND sendonly = false;
iterate_query = SELECT username, domain FROM accounts where sendonly = false;
}
##
### Address tagging
###
recipient_delimiter = +
###
### Mail location
#######################
mail_uid = vmail
mail_gid = vmail
mail_privileged_group = vmail
mail_home = /var/vmail/mailboxes/%{user | domain }/%{user | username }
mail_driver = maildir
mail_path = ~/mail
mailbox_list_layout = fs
###
### Mailbox configuration
########################################
namespace inbox {
inbox = yes
mailbox Spam {
auto = subscribe
special_use = \Junk
}
mailbox Trash {
auto = subscribe
special_use = \Trash
}
mailbox Drafts {
auto = subscribe
special_use = \Drafts
}
mailbox Sent {
auto = subscribe
special_use = \Sent
}
}
###
### Mail plugins
############################
mail_plugins {
quota = yes
}
sieve_plugins {
sieve_imapsieve = yes
sieve_extprograms = yes
}
sieve_script spam-global {
type = before
path = /var/vmail/sieve/global/spam-global.sieve
}
sieve_script personal {
type = personal
path = /var/vmail/sieve/%{user | domain }/%{user | username }/scripts
active_path = /var/vmail/sieve/%{user | domain }/%{user | username }/active-script.sieve
}
# From Mailbox to Spam
mailbox Spam {
sieve_script spam {
type = before
cause = copy
path = /var/vmail/sieve/global/learn-spam.sieve
}
}
# From Spam to another folder (learn HAM)
imapsieve_from Spam {
sieve_script ham {
type = before
cause = copy
path = /var/vmail/sieve/global/learn-ham.sieve
}
}
# Sieve extensions only allowed in global context
sieve_global_extensions {
vnd.dovecot.pipe = yes
}
sieve_pipe_bin_dir = /usr/bin
###
### IMAP Quota
###########################
quota_exceeded_message = Benutzer %{user} hat das Speichervolumen ueberschritten. / User %{user} has exhausted allowed storage space.
quota "User quota" {
driver = count
}