Skip to content

Instantly share code, notes, and snippets.

@bmatthewshea
Last active March 5, 2026 21:50
Show Gist options
  • Select an option

  • Save bmatthewshea/93579a6c4af3ed3a6a4da5c3a04a48d3 to your computer and use it in GitHub Desktop.

Select an option

Save bmatthewshea/93579a6c4af3ed3a6a4da5c3a04a48d3 to your computer and use it in GitHub Desktop.
Dovecot 2.4 - Working configuration for use with PostfixAdmin and SQL Virtual Users
### /etc/dovecot/local.conf - OVERRIDES TO DEFAULTS ONLY! ###
### By: Brady Shea - 05MAR2026
### Original location: https://gist.github.com/bmatthewshea/93579a6c4af3ed3a6a4da5c3a04a48d3
# You may or may not need this:
#dovecot_config_version = 2.4.2
protocols {
lmtp = yes
imap = yes
pop3 = yes
sieve= yes
}
################
# 10-auth.conf #
################
auth_mechanisms = plain login
# IMPORTANT: To use sql/mysql as the sole authentication method,
# The following line is commented in 'conf.d/10-auth.conf':
# -> #!include auth-system.conf.ext
# ^ In 2.4 'ext' files are not suppose to be used, so I am not sure if this is needed anymore to stop local user pam lookups
sql_driver = mysql
# Some local-only varients - socket is used below:
# -> mysql /var/run/mysqld/mysqld.sock {
# -> mysql 127.0.0.1 {
# -> mysql localhost {
mysql /var/run/mysqld/mysqld.sock {
user = (DATABASE USERNAME HERE)
password = (PASSWORD HERE)
dbname = (DATABASE NAME HERE)
}
# In 2.4 to allow md5 passwords, or other weaker schemes (See "passdb sql" below)
# https://doc.dovecot.org/2.3/configuration_manual/authentication/password_schemes/
# Enable this if using a weaker scheme like MD5:
# auth_allow_weak_schemes = yes
#
# The quota stored in postfixadmin database looks like it is stored in bytes (so the B designator is concatenated on end of quota #).
#
# "iterate_query" is used for the 'doveadm users' command.
userdb sql {
query = SELECT concat(quota, 'B') AS quota_storage_size \
FROM mailbox WHERE username = '%{user}' AND active = '1';
iterate_query = SELECT username \
FROM mailbox WHERE active = '1';
}
passdb sql {
default_password_scheme = sha512-crypt
query = SELECT username as user, domain, password \
FROM mailbox WHERE username = '%{user}' AND active = '1';
}
###################
# 10-logging.conf #
###################
# Additional logging setup wil be needed in rsyslog and logrotate
# I store logs in /var/log/dovecot/
log_path = syslog
syslog_facility = local5
auth_verbose = yes
# Debug
#mail_debug = yes
#auth_debug_passwords = yes
#auth_debug = no
#verbose_ssl = yes
################
# 10-mail.conf #
################
# The first two are definitely be needed. As for the last 3, it works so I left it in (they're from an older config)
mail_uid = vmail
mail_gid = mail
first_valid_uid = 150
last_valid_uid = 150
mail_privileged_group = mail
# READ THIS.
# The 'mail_location' directive has been redefined:
# ** In 2.4, the new home/mail location directives are extremely important to get right. **
# It also took me hours to figure out I need a mail_inbox_path (bug?) (see "mail_inbox_path" below).
# If I didn't include inbox path, 2.4 kept wanting to store email in '/var/mail'.
# NOTE: I made the mistake of setting the home and mail locations the same on a server in the past.
# You will want to store emails/imapfolders in a separate directory ('~/mail' in example below).
# (I use "~/mail" below but "~/Maildir" or anything could be used as far as I know.)
# Once you set the home directory, you simply point everything there (~).
# See: https://doc.dovecot.org/2.3/configuration_manual/home_directories_for_virtual_users/#home-directories-for-virtual-users
# "Home directory shouldn’t be the same as mail directory with mbox or Maildir formats" (We are using Maildir format)
mail_driver = maildir
# "Home" stores dovecot data files/etc:
mail_home = /var/vmail/%{user|domain}/%{user|username}
# The mail path stores actual {new,cur,tmp}/inbox folders for incoming emails:
mail_path = ~/mail
# RE: mail_inbox_path
# This seems to be a bug?: If I do not include this,
# the 'mail_path' seems to override and defaults back to /var/mail/.
# This may not be needed in your own config - test w/o first:
mail_inbox_path = ~/mail
##################
# 10-master.conf #
##################
service auth {
unix_listener auth-userdb {
mode = 0660
user = vmail
group = mail
}
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
user = dovecot
}
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0660
user = postfix
group = postfix
}
}
service auth-worker {
user = vmail
}
service managesieve-login {
inet_listener sieve {
port = 4190
}
}
service stats {
unix_listener stats-reader {
user = vmail
group = mail
mode = 0660
}
unix_listener stats-writer {
user = vmail
group = mail
mode = 0660
}
}
###############
# 10-ssl.conf #
###############
ssl = yes
# core email domain
ssl_server_cert_file = /etc/letsencrypt/live/example.com/fullchain.pem
ssl_server_key_file = /etc/letsencrypt/live/example.com/privkey.pem
ssl_server_dh_file = /etc/dovecot/dhparam.pem
ssl_min_protocol = TLSv1.2
# OPTIONAL: multi domain / site specific - if not found dovecot defaults to ^core certficate
# Additional domains will utilize SNI and may not work on all clients - though, this advice is getting outdated quickly.
#
#local_name smtp.example.org {
# ssl_server_cert_file = /etc/letsencrypt/live/example.org/fullchain.pem
# ssl_server_key_file = /etc/letsencrypt/live/example.org/privkey.pem
#}
#local_name mail.example.com {
# ssl_server_cert_file = /etc/letsencrypt/live/example.net/fullchain.pem
# ssl_server_key_file = /etc/letsencrypt/live/example.net/privkey.pem
#}
# add more as needed..
###############################
# 15-lda.conf / 20-lmtp.conf #
###############################
postmaster_address = postmaster@example.net
recipient_delimiter = +
# add sieve
protocol lmtp {
auth_username_format = %{user}
mail_plugins {
sieve = yes
notify = yes
push_notification = yes
}
}
#####################
# 15-mailboxes.conf #
#####################
namespace inbox {
inbox = yes
separator = /
#
#mailbox name {
# auto = create -> will automatically create this mailbox.
# auto = subscribe -> will both create and subscribe to the mailbox.
# auto = no -> defines it - nothing else
# RFC 6154: \All \Archive \Drafts \Flagged \Junk \Sent \Trash :
# special_use = What is its function?
#}
mailbox Drafts {
auto = subscribe
special_use = \Drafts
}
# In 2.4, sieve junk learning is now called in this section.
# I assume we could also add a "Spam" mailbox and put the same script in it.
# In this fashion, you should be able to detail more than one junk mailbox to learn spam.
# I have not tried this yet, though.
mailbox "Junk E-mail" { # Outlook
auto = subscribe
special_use = \Junk
sieve_script LearnSpam {
cause = COPY
driver = file
path = /var/vmail/sieve/global/learn-spam.sieve
type = before
}
}
mailbox Archives {
auto = subscribe
special_use = \Archive
}
mailbox "Deleted Items" { #Outlook
auto = subscribe
special_use = \Trash
}
mailbox "Templates" { #Thunderbird
}
mailbox "Notes" {
}
mailbox "Public" {
type = public
}
# Generally outlook/thunderbird:
mailbox "Sent Items" {
auto = subscribe
special_use = \Sent
}
# sieve 'learn HAM' is now in inbox namespace:
imapsieve_from Spam {
sieve_script LearnHam {
cause = COPY
driver = file
path = /var/vmail/sieve/global/learn-ham.sieve
type = before
}
}
} # Ends inbox namespace
##########################
# 20-imap.conf overrides #
##########################
protocol imap {
mail_plugins {
quota = yes
imap_quota = yes
imap_sieve = yes
}
mail_max_userip_connections = 50
}
###############
#20-pop3.conf #
###############
# no overrides
#################
# 90-sieve.conf #
#################
sieve_plugins {
sieve_imapsieve = yes
sieve_extprograms = yes
}
sieve_script spam-global {
type = before
driver = file
path = /var/vmail/sieve/global/spam-global.sieve
}
sieve_script personal {
type = personal
driver = file
path = /var/vmail/sieve/%{user|domain}/%{user|username}/scripts
active_path = /var/vmail/sieve/%{user|domain}/%{user|username}/active-script.sieve
}
sieve_pipe_bin_dir = /usr/bin
sieve_extensions {
vnd.dovecot.pipe = yes
}
### QUOTA ###
quota_exceeded_message = User %{user} has exceeded allowed storage space.
quota "User quota" {
driver = count
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment