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 latest Debian version makes a leap to Dovecot 2.4 with the included Dovecot version. This means that old Dovecot configurations are no longer compatible!

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.

You can find a complete configuration file at the end of this post.

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.2
  • disable_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, and sieve become:
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
}