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: master.cf
- dovecot: dovecot.conf
- Complete Files: postfix main.cf
- Complete Files: postfix master.cf
- Complete Files: dovecot dovecot.conf
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
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}
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.
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
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}
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
}
}