postfix/dovecot Configurations

I recently had to configure postfix and dovecot 2.4 on a VPS running Debian Trixie to handle both system and virtual users. This post shares the working configurations for both daemons, and some notes about what I learned along the way. They were developed based on several days of research and AI usage (Gemini Pro and ChatGPT). The full configurations are available at the end of the post.

Table of Contents

postfix: main.cf

postfix’s configuration is mostly handled by /etc/postfix/main.cf and /ext/postfix/master.cf. I’ll start with main.cf.

I think you can rely on the default settings for much of what this first block defines…but I like to spell things out explicitly in config files for complex apps like postscript.

Lines 4 and 5 define the VPS’ users as being part of theboilingfrog.net domain. As you’ll see, I’m configuring postscript and dovecot to handle more than one domain. The other domains will be virtual, while theboilingfrog.net will be the system domain. This has important consequences for how you configure postfix and dovecot to work together, because dovecot authenticates virtual and system users differently.

# Which domain that locally-originated mail appears to come from.
# Debian policy suggests to read this value from /etc/mailname.
# but we're defining things explicitly here
myhostname = mail.theboilingfrog.net
mydomain = theboilingfrog.net
myorigin = $mydomain

# List of domains (maptype:mapname allowed) that this machine considers
# itself the final destination for.
mydestination = $myhostname, 
    localhost.$mydomain, 
    localhost,
    $mydomain


I ran into some problems with Exchange 365 (which hosts my primary personal email) when postfix was allowed to use IPv6 connections. This block limits postfix to use only ipV4 addresses.

# IP protocols to use: ipv4, ipv6, or all
# (set this explicitly so `post-install upgrade-configuration' wont complain)
inet_protocols = ipv4

This next block defines the virtual domains and users I want postfix to handle. The actual definitions are contained in separate config files, most of which are hashmapped. I’m not sure why virtual_mailbox_base isn’t hashmapped…but it’s what ChatGPT said I should do. I’m also explicitly defining the virtual userids and groupids, even though the defaults might work.

# Virtual domains are everything EXCEPT the system domain (theboilingfrog.net).
# theboilingfrog.net must NOT appear in /etc/postfix/virtual_domains --
# it is a local/system domain and is delivered by local(8) below.
virtual_mailbox_domains = hash:/etc/postfix/virtual_domains
virtual_mailbox_base = /var/mail/vhosts
virtual_mailbox_maps = hash:/etc/postfix/virtual_mailboxes
virtual_alias_maps = hash:/etc/postfix/virtual_aliases

virtual_minimum_uid = 100
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000

You have to follow the required format for the hashmap files. Here’s what mine look like. First, virtual_domains, which defines the virtual domains postfix will serve in addition to the system domain:

# /etc/postfix/virtual_domains

jumpforjoysoftware.com		OK
ardsleyhigh73.com		OK
make-america-smart-again.com	OK

The OK is meaningless, but must be included as a placeholder. Next, virtual_aliases, which maps certain email addresses in each of the domains to a specific virtual or system user (or nobody, which acts as a bit bucket):

# /etc/postfix/virtual_aliases

do-not-reply@theboilingfrog.net                 nobody
do-not-reply@ardsleyhigh73.com                  nobody
do-not-reply@make-america-smart-again.com       nobody
admin@ardsleyhigh73.com                         mark@ardsleyhigh73.com mark@arcabama.com
website@ardsleyhigh73.com                       mark@ardsleyhigh73.com mark@arcabama.com
topics@theboilingfrog.net                       mark@theboilingfrog.net seth@theboilingfrog.net


You can forward emails to an aliased user to multiple recipients (lines 6 – 8).

And, finally, here is the virtual_mailboxes file, which defines what virtual mailboxes postfix will manage:

# /etc/postfix/virtual_mailboxes

# the trailing slash forces the use of Maildir format,
# which is recommended
support@jumpforjoysoftware.com          jumpforjoysoftware.com/mark/
mark@jumpforjoysoftware.com             jumpforjoysoftware.com/mark/
mark@ardsleyhigh73.com                  ardsleyhigh73.com/mark/
mark@make-america-smart-again.com       make-america-smart-again.com/mark/

This next block ensures email access can only be done via secured connections.

# SMTP server RSA key and certificate in PEM format
smtpd_tls_key_file = /etc/letsencrypt/live/mail.alldomains/privkey.pem
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.alldomains/fullchain.pem

# SMTP Server security level: none|may|encrypt
smtpd_tls_security_level = may
smtpd_tls_loglevel = 1

smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache

#Enforce TLSv1.3 or TLSv1.2
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

# List of CAs for SMTP Client to trust
# Prefer this over -CApath when smtp is running chrooted
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt

# SMTP Client TLS security level: none|may|encrypt|...
smtp_tls_security_level = may

# SMTP Client TLS session cache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

This next block sets up how SASL authentication is handled by dovecot:

# SASL Authentication via Dovecot
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
broken_sasl_auth_clients = yes
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
smtpd_tls_auth_only = yes


The all-important relay restrictions that help keep your email servers from being treated as spam generators.

smtpd_relay_restrictions = permit_mynetworks,
    permit_sasl_authenticated,
    reject_unauth_destination

smtpd_client_restrictions = permit_mynetworks,
    permit_sasl_authenticated,
    reject_unknown_client_hostname,
    reject_rbl_client bl.spamcop.net

smtpd_recipient_restrictions = permit_mynetworks,
    permit_sasl_authenticated,
    reject_unauth_destination

This block enables use of the DNSBL spam and blacklist checker on incoming emails. I’ve redacted my personal id, which you get when you set up an account with DNSBL.

postscreen_dnsbl_sites = redacted.zen.dq.spamhaus.net*3,
    redacted.dbl.dq.spamhaus.net*2,
    redacted.zrd.dq.spamhaus.net*2

postscreen_dnsbl_reply_map = hash:/etc/postfix/dnsbl_reply

This block defines how postfix will deliver mail, once it’s been checked. Note that system users are handled directly by postfix (line 4) while virtual users are passed off to dovecot for delivery. Theoretically, dovecot could handle both system and virtual users…but several days of frustration and, finally, a recommendation to just let postfix handle system user mail delivery, convinced me to go this split way, with dovecot (and lmtp) handling just the virtual users.

# --- Transport and Delivery Pipelines ---
# postfix handles system users directly, but uses dovecot for
# delivery of all virtual domain emails
home_mailbox = Maildir/
virtual_transport = lmtp:unix:private/dovecot-lmtp

This last block tells postfix how to interact with OpenDKIM, which is a very important service that helps assure emails being sent out by postfix don’t get treated as spam by their recipients.

# OpenDKIM Integration
milter_protocol = 6
milter_default_action = accept
smtpd_milters = inet:localhost:8891
non_smtpd_milters = inet:localhost:8891

return to table of contents

postfix: master.cf

I find postfix’s master.cf file more confusing than the main.cf file, which restricts the comments I’ll make.

Here is the primary configuration information in master.cf, which I think defines how postfix handles smtp (port 25; traditional, unsecured email submissions), submission (port 587, secured via starting with plaintext but immediately upgrading to encrypted TLS)1 and smtps (port 465, which starts out using SSL/TLS right away, no STARTTLS negotiation needed) activities:

# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (no)    (never) (100)
# ==========================================================================
smtp      inet  n       -       y       -       -       smtpd

submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_tls_wrappermode=no
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth

smtps     inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth

Further down the following lines define how postfix will hand off mail to dovecot:

dovecot   unix  -       n       n       -       -   pipe
  flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/dovecot-lda -f ${sender} -d ${recipient}

You need to have set up the vmail user and the vmail group with appropriate permissions.

This next set of lines defines how postfix interacts with a local SPF evaluation daemon. This, too, is a way of detecting and block spam. In my system, I installed a separate package, postfix-policyd-spf-python, to provide the service.

# start the SPF policy daemon
policyd-spf unix -      n       n       -       0       spawn
   user=policyd-spf argv=/usr/bin/policyd-spf

This last block defines how postfix will interact with spamassassin, a separately-installed package used to detect spam email messages.

spamassassin unix -     n       n       -       -       pipe
  user=debian-spamd argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}

return to table of contents

dovecot: dovecot.conf

This dovecot configuration is for dovecot version 2.4+. There were many breaking changes in dovecot’s configuration syntax in version 2.4. What you see here is pretty much guaranteed not to work for versions below 2.4.

dovecot is configured through a “master” configuration file, dovecot.conf, and, optionally, a number of files contained in the subdirectory /etc/dovecot/conf.d. Because my setup is relatively simple — and it appears the recently-recommended approach is not to use the subdirectory files — my entire dovecot configuration is in the one file /etc/dovecot/dovecot.conf. That’s why you won’t find any include directives in what you see below. In fact, I recommend you remove any include directives from the default dovecot.conf file if you decide to use what I’m sharing.

You can test a dovecot.conf configuration for syntax errors by running:

sudo doveconf -n

I’m pretty sure not putting the following block at the top of dovecot.conf will trigger an error:

dovecot_config_version = 2.4.1
dovecot_storage_version = 2.4.1

You should use the correct version for your dovecot app, of course.

While the next two lines are commented out, I left them in the file because uncommenting them generates a lot of useful diagnostic information when you’re trying to dix configuration problems.

# auth_verbose = yes
# log_debug = category = auth

This next block defines the protocols dovecot will use to handle email:

protocols {
  imap = yes
  lmtp = yes
}

IMAP is an email protocol for accessing and managing emails remotely via a client program. dovecot also supports POP3, but I only needed, and wanted, IMAP, so I left POP3 out2.

LMTP stands for local mail transfer/transport protocol. It’s a final delivery agent used by dovecot to put emails into a user’s email directories.

Be aware neither IMAP nor LMTP support is part of the core dovecot package, at least under Debian. You have to install all three packages separately:

sudo aptitude update
sudo aptitude install dovecot-core dovecot-imapd dovecot-lmtpd

These next two lines prevent unencrypted (i.e., via SSL/TLS) connections:

auth_allow_cleartext = no
auth_mechanisms = plain

This next set of lines was where I spent days tweaking various settings to try and get dovecot to handle both system and virtual users. In the end, at ChatGPT’s suggestion, I gave up on having dovecot handle system users and instead had it just handle virtual users (in my setup, postfix handles delivery of system user emails by itself).

# this requires FQDN globally (including in lmtp)
# that would be a problem for deliving system user emails
# ...but we don't let dovecot handle them (they're handled
# directly by postfix)
auth_username_format = %{user | lower}

# the order of the passdb/userdb blocks is important!
# we put the virtual users first, because some virtual
# users (e.g., mark@jumpforjoysoftware.com) have the
# exact same username component (mark) as system accounts
# ...and dovecot proceeds on the first match it finds

# these next two blocks are to support virtual users 
# (i.e., users not part of theboilingfrog.net)
passdb passwd-file {
  passwd_file_path = /etc/dovecot/passwd
}

userdb passwd-file {
  passwd_file_path = /etc/dovecot/passwd

  # this shouldn't be necessary...but just in case
  userdb_fields {
    uid = 5000
    gid = 5000
    home = /var/mail/vhosts/%{user | domain | lower }/%{user | username | lower }
    mail_driver = maildir
    mail_path = ~/Maildir
  }
}

# these next two blocks are to support system users
# I don't know why these blocks are needed, since 
# postfix doesn't use dovecot to deliver system user
# emails
passdb pam {
  auth_username_format = %{user | username | lower}

  service_name = dovecot
}

userdb passwd {
  auth_username_format = %{user | username | lower}

  userdb_fields {
    home = /home/%{user}
    mail_driver = maildir
    mail_path = ~/Maildir
  }
}

dovecot is capable of using multiple ways to authenticate users based on an email’s “to” address, including both Linux’ default system user authentication system (PAM), a static user file, SQL, etc. Since I only have a few users I went with the static user file approach.

But even though dovecot does not handle system users in my setup — and so you’d think I didn’t need to configure anything related to PAM — I found not including the PAM blocks (the ones starting out passdb pam and userdb password at the end of the lines in this section) generated delivery errors. So, I left them in.

BTW, the order of the authentication configuration blocks is important. dovecot starts authenticating an email address with whatever authentication system is defined first in the configuration file. In my case that’s passwd-file, meaning “look up users in a static file” (/etc/dovecot/passwd, in my setup). dovecot will only test subsequently-configured authentication systems if the prior ones fail (it stops as soon as it can authenticate a user, and delivers the email to whatever destination is defined in the associated userdb block).

There’s also the question of what parts of an email address should be used to authenticate (e.g., user name alone, user name and domain name), whether the relevant tokens should have their case adjusted, etc.

In most cases you can define these extraction and formatting rules separately for each “authentication channel”…but not all. Specifically, you cannot change how the PAM authentication channel uses an email address — it uses the entire thing, exactly as contained in the email message, regardless of what you might define in its dovecot.conf configuration block.

Why the dovecot developers chose to do this I have no idea…but it cost me days of tinkering and researching to learn that you couldn’t override it for PAM authentication. And it was the precise reason why I gave up trying to have dovecot deliver system user emails. Because an email addressed to a system user (from an external source, at least) will always be fully-qualified: user name plus domain name. You can’t deliver the email, otherwise. And the dovecot PAM authentication system will therefore always try to authenticate using that fully-qualified string, which will never match a system user’s name (which is not fully-qualified).

At the end of the day, the way I set up dovecot says, by default, all authentication mechanisms should use the full email address (line 5). Since that’s the default, I didn’t need to specify it within the static file authentication blocks (lines 15 – 17 and 19 – 30).

I included a custom format string for the PAM mechanisms (lines 37 and 43), but solely as a reminder of what the PAM mechanisms will do — dovecot ignores the custom string I defined when using PAM, and always uses the full email address (which, to reiterate, will not work).

So what does that static authentication file look like? Here it is (/etc/dovecot/passwd, on my system):

mark@jumpforjoysoftware.com:{SHA256-CRYPT}redacted:5000:5000:::
mark@ardsleyhigh73.com:{SHA256-CRYPT}redacted:5000:5000:::
mark@make-america-smart-again.com:{SHA256-CRYPT}redacted:5000:5000:::

Each line defines a virtual user and an encrypted hash of its password (I’ve redacted the encrypted hashes for security reasons).

You generate those encrypted hashes using a dovecot utility:

doveadm pw -s SHA512-CRYPT

You can choose a variety of encryption methods. If you don’t specify one it defaults to CRYPT. I use SHA512-CRYPT instead because I read somewhere that it’s more secure than CRYPT.

When you run doveadm pw, it’ll ask you to enter the email address/account you want to create the hash for, its password and a confirmation of its password. It then spits out the string you’ll need to paste into the passwd file for that user.

Be aware that the format of each line is pretty persnickety. You must keep track of the number of fields on each line, which are separated by colons. As I recall, you cannot leave out empty colon-delimited fields; you have to include them. There are 8 fields in each line, only one of which is optional, meaning there must be at least 6 and no more than 7 colons. Here are the field definitions:

  • user (fully-qualified email address)
  • password (the encrypted hash generated by doveadm)
  • uid (the user id having access to the mail files and folders; 5000 is customary)
  • gid (the group id having access to the mail files and folders; 5000 is customary)
  • gecos (a comment field unused by dovecot)
  • home (the user’s home or base directory path; not set because we set them globally in dovecot.conf)
  • shell (user login shell, unused by dovecot)
  • extra_fields (space-separated key = value pairs to pass extra configuration information; thank goodness I didn’t need to use this) 🙂

It is likely some of what I defined in my passwd file is redundant with what’s in dovecot.conf, or could be handled by default values. I’ll plead configuration exhaustion for not researching and correcting this.

In my configuration file, the next set of lines may well be unnecessary (configuration exhaustion again):

# because dovecot drops root privileges, we must define
# the following service
# may not be necessary because dovecot doesn't handle
# system user emails and so shouldn't need to check
# the shadow file, but...
service auth-worker {
  # Instructs Dovecot 2.4 to inherit the shadow group permissions
  user = $SET:default_internal_user
  group = shadow
}

mailbox_list_layout = fs

# not sure why this is here
protocol lmtp {
  postmaster_address = postmaster@theboilingfrog.net
}

# these next two blocks probably aren't necessary
# because I believe they're the defaults. But...
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0660
    user = postfix
    group = postfix
  }
}

service auth {
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix
  }
}

But the next set of lines is absolutely necessary to support the SSL connections I required:

# configuration to support using SSL
ssl = required
ssl_min_protocol = TLSv1.2
ssl_client_ca_dir = /etc/ssl/certs
ssl_server_dh_file = /usr/share/dovecot/dh.pem
ssl_server_prefer_ciphers = server
ssl_server_cert_file = /etc/letsencrypt/live/mail.alldomains/fullchain.pem
ssl_server_key_file = /etc/letsencrypt/live/mail.alldomains/privkey.pem

I use Let’s Encrypt to generate and maintain my SSL certificates. It’s a great, free service you should definitely check out if you want to use encrypted connections for accessing your email. And you should want SSL.

This last set of lines defines how an IMAP client should set up its local folders to interact with dovecot:

# this defines the layout of how a client displays
# IMAP emails when it connects for the first time
# I think most are default values, but...
namespace inbox {
  inbox = yes

  mailbox Drafts {
    auto = subscribe
    special_use = \Drafts
  }

  mailbox Junk {
    auto = subscribe
    special_use = \Junk
  }

  mailbox Trash {
    auto = subscribe
    special_use = \Trash
  }

  # For \Sent mailboxes there are two widely used names. We'll mark both of
  # them as \Sent. User typically deletes one of them if duplicates are created.
  mailbox Sent {
    auto = subscribe
    special_use = \Sent
  }

  mailbox "Sent Messages" {
    auto = subscribe
    special_use = \Sent
  }
}

Having both Sent and Sent Messages folders is a little weird. At some point I’ll look into whether both are truly necessary.

return to table of contents

Complete Files: postfix main.cf

# See /usr/share/postfix/main.cf.dist for a commented, more complete version

# See http://www.postfix.org/COMPATIBILITY_README.html
compatibility_level = 3.9

# Which domain that locally-originated mail appears to come from.
# Debian policy suggests to read this value from /etc/mailname.
# but we're defining things explicitly here
myhostname = mail.theboilingfrog.net
mydomain = theboilingfrog.net
myorigin = $mydomain

# List of domains (maptype:mapname allowed) that this machine considers
# itself the final destination for.
mydestination = $myhostname, 
    localhost.$mydomain, 
    localhost,
    $mydomain

# Text that follows the 220 code in the SMTP server's greeting banner.
# You MUST specify $myhostname at the start due to an RFC requirement.
smtpd_banner = $myhostname ESMTP $mail_name (Debian)

# IP protocols to use: ipv4, ipv6, or all
# (set this explicitly so `post-install upgrade-configuration' wont complain)
inet_protocols = ipv4

# List of "trusted" SMTP clients (maptype:mapname allowed) that have more
# privileges than "strangers".  If mynetworks is not specified (the default),
mynetworks_style = host
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

# Maximum size of a user mailbox
mailbox_size_limit = 0

# Optional external command to use instead of mailbox delivery.  If set,
# you must set up an alias to forward root mail to a real user.
mailbox_command = 

# List of alias maps to use to lookup local addresses.
# Per Debian Policy it should be /etc/aliases.
alias_maps = hash:/etc/aliases

# List of alias maps to make indexes on, when running newaliases.
alias_database = hash:/etc/aliases

# Notify (or not) local biff service when new mail arrives.
# Rarely used these days.
biff = no

# Virtual domains are everything EXCEPT the system domain (theboilingfrog.net).
# theboilingfrog.net must NOT appear in /etc/postfix/virtual_domains --
# it is a local/system domain and is delivered by local(8) below.
virtual_mailbox_domains = hash:/etc/postfix/virtual_domains
virtual_mailbox_base = /var/mail/vhosts
virtual_mailbox_maps = hash:/etc/postfix/virtual_mailboxes
virtual_alias_maps = hash:/etc/postfix/virtual_aliases

virtual_minimum_uid = 100
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000

# Separator between user name and address extension (user+foo@domain)
#recipient_delimiter = +
recipient_delimiter = +

# A host to send "other" mail to
relayhost = 

# Where to look for Cyrus SASL configuration files.  Upstream default is unset
# (use compiled-in SASL library default), Debian Policy says it should be
# /etc/postfix/sasl.
cyrus_sasl_config_path = /etc/postfix/sasl

# SMTP server RSA key and certificate in PEM format
smtpd_tls_key_file = /etc/letsencrypt/live/mail.alldomains/privkey.pem
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.alldomains/fullchain.pem

# SMTP Server security level: none|may|encrypt
smtpd_tls_security_level = may
smtpd_tls_loglevel = 1

smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache

#Enforce TLSv1.3 or TLSv1.2
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

# List of CAs for SMTP Client to trust
# Prefer this over -CApath when smtp is running chrooted
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt

# SMTP Client TLS security level: none|may|encrypt|...
smtp_tls_security_level = may

# SMTP Client TLS session cache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

inet_interfaces = all

# SASL Authentication via Dovecot
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
broken_sasl_auth_clients = yes
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
smtpd_tls_auth_only = yes

smtpd_relay_restrictions = permit_mynetworks,
    permit_sasl_authenticated,
    reject_unauth_destination

smtpd_client_restrictions = permit_mynetworks,
    permit_sasl_authenticated,
    reject_unknown_client_hostname,
    reject_rbl_client bl.spamcop.net

smtpd_recipient_restrictions = permit_mynetworks,
    permit_sasl_authenticated,
    reject_unauth_destination

postscreen_dnsbl_sites = redacted.zen.dq.spamhaus.net*3,
    redacted.dbl.dq.spamhaus.net*2,
    redacted.zrd.dq.spamhaus.net*2

postscreen_dnsbl_reply_map = hash:/etc/postfix/dnsbl_reply

# --- Transport and Delivery Pipelines ---
# postfix handles system users directly, but uses dovecot for
# delivery of all virtual domain emails
home_mailbox = Maildir/
virtual_transport = lmtp:unix:private/dovecot-lmtp

# OpenDKIM Integration
milter_protocol = 6
milter_default_action = accept
smtpd_milters = inet:localhost:8891
non_smtpd_milters = inet:localhost:8891

return to table of contents

Complete Files: postfix master.cf

#
# Postfix master process configuration file.  For details on the format
# of the file, see the master(5) manual page (command: "man 5 master" or
# on-line: https://www.postfix.org/master.5.html).
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (no)    (never) (100)
# ==========================================================================
smtp      inet  n       -       y       -       -       smtpd

submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_tls_wrappermode=no
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth

smtps     inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth

#628       inet  n       -       y       -       -       qmqpd
pickup    unix  n       -       y       60      1       pickup
cleanup   unix  n       -       y       -       0       cleanup
qmgr      unix  n       -       n       300     1       qmgr
#qmgr     unix  n       -       n       300     1       oqmgr
tlsmgr    unix  -       -       y       1000?   1       tlsmgr
rewrite   unix  -       -       y       -       -       trivial-rewrite
bounce    unix  -       -       y       -       0       bounce
defer     unix  -       -       y       -       0       bounce
trace     unix  -       -       y       -       0       bounce
verify    unix  -       -       y       -       1       verify
flush     unix  n       -       y       1000?   0       flush
proxymap  unix  -       -       n       -       -       proxymap
proxywrite unix -       -       n       -       1       proxymap
smtp      unix  -       -       y       -       -       smtp
relay     unix  -       -       y       -       -       smtp
        -o syslog_name=${multi_instance_name?{$multi_instance_name}:{postfix}}/$service_name
showq     unix  n       -       y       -       -       showq
error     unix  -       -       y       -       -       error
retry     unix  -       -       y       -       -       error
discard   unix  -       -       y       -       -       discard
local     unix  -       n       n       -       -       local
virtual   unix  -       n       n       -       -       virtual
lmtp      unix  -       -       y       -       -       lmtp
anvil     unix  -       -       y       -       1       anvil
scache    unix  -       -       y       -       1       scache
postlog   unix-dgram n  -       n       -       1       postlogd

#
# ====================================================================
# Interfaces to non-Postfix software. Be sure to examine the manual
# pages of the non-Postfix software to find out what options it wants.
#
# Many of the following services use the Postfix pipe(8) delivery
# agent.  See the pipe(8) man page for information about ${recipient}
# and other message envelope options.
# ====================================================================
#
dovecot   unix  -       n       n       -       -   pipe
  flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/dovecot-lda -f ${sender} -d ${recipient}
#
# start the SPF policy daemon
policyd-spf unix -      n       n       -       0       spawn
   user=policyd-spf argv=/usr/bin/policyd-spf

# added 2025-2-2 per https://raw.org/tutorial/seting-up-email-server-with-postfix-dovecot-and-mysql/
# 2025-2-4 corrected to use the correct user
spamassassin unix -     n       n       -       -       pipe
  user=debian-spamd argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}

return to table of contents

Complete Files: dovecot.conf

# Dovecot configuration file

# If you're in a hurry, see https://doc.dovecot.org/latest/core/config/guides/quick.html

# "doveconf -n" command gives a clean output of the changed settings. Use it
# instead of copy&pasting files when posting to the Dovecot mailing list.

# '#' character and everything after it is treated as comments. Extra spaces
# and tabs are ignored. If you want to use either of these explicitly, put the
# value inside quotes, eg.: key = "# char and trailing whitespace  "

dovecot_config_version = 2.4.1
dovecot_storage_version = 2.4.1

# auth_verbose = yes
# log_debug = category = auth

protocols {
  imap = yes
  lmtp = yes
}

auth_allow_cleartext = no
auth_mechanisms = plain

# this requires FQDN globally (including in lmtp)
# that would be a problem for deliving system user emails
# ...but we don't let dovecot handle them (they're handled
# directly by postfix)
auth_username_format = %{user | lower}

# the order of the passdb/userdb blocks is important!
# we put the virtual users first, because some virtual
# users (e.g., mark@jumpforjoysoftware.com) have the
# exact same username component (mark) as system accounts
# ...and dovecot proceeds on the first match it finds

# these next two blocks are to support virtual users 
# (i.e., users not part of theboilingfrog.net)
passdb passwd-file {
  passwd_file_path = /etc/dovecot/passwd
}

userdb passwd-file {
  passwd_file_path = /etc/dovecot/passwd

  # this shouldn't be necessary...but just in case
  userdb_fields {
    uid = 5000
    gid = 5000
    home = /var/mail/vhosts/%{user | domain | lower }/%{user | username | lower }
    mail_driver = maildir
    mail_path = ~/Maildir
  }
}

# these next two blocks are to support system users
# I don't know why these blocks are needed, since 
# postfix doesn't use dovecot to deliver system user
# emails
passdb pam {
  auth_username_format = %{user | username | lower}

  service_name = dovecot
}

userdb passwd {
  auth_username_format = %{user | username | lower}

  userdb_fields {
    home = /home/%{user}
    mail_driver = maildir
    mail_path = ~/Maildir
  }
}

# because dovecot drops root privileges, we must define
# the following service
# may not be necessary because dovecot doesn't handle
# system user emails and so shouldn't need to check
# the shadow file, but...
service auth-worker {
  # Instructs Dovecot 2.4 to inherit the shadow group permissions
  user = $SET:default_internal_user
  group = shadow
}

mailbox_list_layout = fs

# not sure why this is here
protocol lmtp {
  postmaster_address = postmaster@theboilingfrog.net
}

# these next two blocks probably aren't necessary
# because I believe they're the defaults. But...
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0660
    user = postfix
    group = postfix
  }
}

service auth {
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix
  }
}

# configuration to support using SSL
ssl = required
ssl_min_protocol = TLSv1.2
ssl_client_ca_dir = /etc/ssl/certs
ssl_server_dh_file = /usr/share/dovecot/dh.pem
ssl_server_prefer_ciphers = server
ssl_server_cert_file = /etc/letsencrypt/live/mail.alldomains/fullchain.pem
ssl_server_key_file = /etc/letsencrypt/live/mail.alldomains/privkey.pem

# this defines the layout of how a client displays
# IMAP emails when it connects for the first time
# I think most are default values, but...
namespace inbox {
  inbox = yes

  mailbox Drafts {
    auto = subscribe
    special_use = \Drafts
  }

  mailbox Junk {
    auto = subscribe
    special_use = \Junk
  }

  mailbox Trash {
    auto = subscribe
    special_use = \Trash
  }

  # For \Sent mailboxes there are two widely used names. We'll mark both of
  # them as \Sent. User typically deletes one of them if duplicates are created.
  mailbox Sent {
    auto = subscribe
    special_use = \Sent
  }

  mailbox "Sent Messages" {
    auto = subscribe
    special_use = \Sent
  }
}

return to table of contents


  1. which you define as STARTTLS in your client-side configuration ↩

  2. my sense is POP3 is on the way out, as email protocols go ↩

Leave a Comment

Your email address will not be published. Required fields are marked *

Categories
Archives