Go back ⮌

Postfix with Dovecot and LDAP

Before we start we should make sure that we have OpenLDAP installed and ready to go. We will add scheme to it and supply some initial data for usage with Postfix and Dovecot. Scheme is arbitrary because we will write our own queries or create our own bindings between data.

wget https://raw.githubusercontent.com/68b32/postfix-ldap-schema/master/postfix.ldif
ldapadd -Y EXTERNAL -H ldapi:/// -f postfix.ldif

We have downloaded and used ldif file with sample schema. It adds new objectClass postfixUser with two attributes: maildrop which we will use as recipient address and mailacceptinggeneralid which will be used as alias.

dn: ou=Mail,dc=example,dc=com
changeType: add
objectClass: organzationalUnit
objectClass: top
ou: Mail

dn: uid=mailuser,ou=Mail,dc=example,dc=com
changeType: add
objectClass: account
objectClass: simpleSecurityObject
objectClass: postfixUser
objectClass: top
uid: mailuser
maildrop: mailuser@example.com
mailacceptinggeneralid: alias@example.com
userPassword: {SSHA}password

dn: ou=Manager,dc=example,dc=com
changeType: add
objectClass: organzationalUnit
objectClass: top
ou: Manager

dn: cn=mailReader,ou=Manager,dc=example,dc=com
objectClass: organizationalRole
objectClass: simpleSecurityObject
objectClass: top
cn: mailReader
userPassword: {SSHA}password

First we have added group for mail users and populated it with a single user and then we have created group for managers and added user that will be used by postfix and dovecot. One last thing in preparing LDAP is adding permissions in /etc/ldap/slapd.conf:

access to attrs=userPassword
	by self =xw
	by anonymous auth
	by * none

access to dn.subtree="ou=Mail,dc=example,dc=com"
	by dn.base="cn=mailReader,ou=Manager,dc=example,dc=com" read
	by * none

Now then, it looks like we are ready to install Postfix and Dovecot. Beware, though, Postfix will ask questions already during installation. For initial configuration we will take “Internet site” and enter our domain. This will generate sample config file to work with.

apt update
apt install postfix postfix-ldap dovecot-core dovecot-ldap dovecot-imapd dovecot-lmtpd

We are going tostart with Postfix configuration because it already provoked us. Firstly, create files we will fill with mappings and queries. After that edit main.cf and master.cf. Lastly, check changes we have made.

cd /etc/postfix
mkdir ldap
touch ldap/mailbox_maps.cf ldap/alias_maps.cf
chgrp -R postfix ldap
chmod 750 ldap
chmod 640 ldap/*

Each of created files will have common header that will contain information on how to connect to LDAP server. Then after it, they will have specific queries that will be used by postfix. Common part:

bind = yes
bind_dn = cn=mailReader,ou=Manager,dc=example,dc=com
bind_pw = <password>
server_host = ldap://
start_tls = yes
search_base = ou=Mail,dc=example,dc=com


query_filter = mailacceptinggeneralid=%s
result_attribute = maildrop


query_filter = maildrop=%s
result_attribute = uid

Now, move into /etc/postfix/main.cf file:

smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
append_dot_mydomain = no
readme_directory = no

smtpd_tls_cert_file = /etc/letsencrypt/live/www.ignore.pl/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/www.ignore.pl/privkey.pem
smtpd_use_tls = yes
smtpd_tls_auth_only = yes
smtp_tls_security_level = may
smtp_tls_note_starttls_offer = yes
smtpd_tls_security_level = may
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
smtpd_tls_received_header = yes

smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = ignore.pl
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydomain = ignore.pl
myorigin = $mydomain
mydestination = localhost.$mydomain
relayhost =
mynetworks = [::ffff:]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all

virtual_transport = lmtp:unix:private/dovecot-lmtp
virtual_mailbox_domains = ignore.pl
virtual_mailbox_maps = ldap:/etc/postfix/ldap/mailbox_maps.cf
virtual_alias_maps = ldap:/etc/postfix/ldap/alias_maps.cf

compatibility_level = 2

Of course this configuration can be extended. For instance we could add support for multiple domains of virtual mailboxes. Let’s leave it this way for now. We should enable submission and smtps in /etc/postfix/master.cf by uncommenting them along. We will change their arguments a little bit:

submission inet n       -       -       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING
smtps     inet  n       -       -       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

And that’s pretty much it (for now) in Postfix. Check it and reload.

systemctl reload postfix
postfix check
tail -f /var/log/mail.log

Before we proceed with Dovecot configuration here are some useful commands that will help us during the whole process. First one will display all out changes in configuration. Second one is a utility that will help in e.g. checking if dovecot can read LDAP properly, like shown in a third command which will print user details if they can be retrived from the directory.

doveconf -n
dovecot user mailuser

Rather than creating whole new directories, users and groups, then assigning permissions and even subgroups to them. Let’s just use defaults. On Ubuntu there should be /var/mail owned by root:mail. I should mention that it should be just used for privileged group because otherwise it will make lowest gid/uid setting somehow useless. Let us ignore that for now. In general Dovecot configuration files are quite well described inside. What I don’t like about them is that they are separated between multiple files yet still merged when read. We will start with /etc/dovecot/conf.d/10-mail.conf:

mail_uid = dovecot
mail_gid = mail
mail_privileged_group = mail
mail_location = maildir:/var/mail/%u

Then in /etc/dovecot/conf.d/10-auth.conf disable authentication via system (passwd) and enable ldap one. That means commenting and uncommenting specific include lines. Next one to edit is file we have just included, it should be /etc/dovecot/conf.d/auth-ldap.conf.ext:

passdb {
  driver = ldap
  args = /etc/dovecot/dovecot-ldap.conf.ext
userdb {
  driver = ldap
  args = /etc/dovecot/dovecot-ldap.conf.ext

Actually it can direct to any file, but we will use default one, as it is also well documented inside. Open it up, it should be in /etc/dovecot/dovecot-ldap.conf.ext, look for and change following parameters:

hosts =
dn = cn=mailReader,ou=Manager,dc=ignore,dc=pl
dnpass = <password>
tls = yes
debug_level = 0
auth_bind = yes
auth_bind_userdn = uid=%u,ou=Mail,dc=ignore,dc=pl
ldap_version = 3
base = ou=Mail,dc=ignore,dc=pl
scope = subtree
user_attrs = \
user_filter = (&(objectClass=postfixUser)(uid=%n))
pass_attrs = uid=user,userPassword=password
pass_filter = (&(objectClass=postfixUser)(uid=%u))
iterate_attrs = uid=user
iterate_filter = (objectClass=postfixAccount)

Now we will edit file that set ups Dovecot services, /etc/dovecot/conf.d/10-master.conf. Setting port to zero will disable non-encrypted imap.

service imap-login {
  inet_listener imap {
    port = 0
  inet_listener imaps {
    port = 993
    ssl = yes
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0600
    user = postfix
    group = postfix
service auth {
  unix_listener auth-userdb {
    mode = 0660
    user = dovecot
    group = mail
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix
  user = dovecot
service auth-worker {
  user = dovecot

Since we disabled non-encryted access, we should configure encrypted one in /etc/dovecot/conf.d/10-ssl.conf:

ssl = required
ssl_cert = </etc/letsencrypt/live/www.ignore.pl/fullchain.pem
ssl_key = </etc/letsencrypt/live/www.ignore.pl/privkey.pem

It should be ready to use, restart and reload everything:

systemctl restart dovecot
systemctl reload postfix
systemctl restart postfix

While it all should work by now, we still need to set up few more things to be able to send e-mails to “respected” mail providers. We will start with OpenDKIM which is an implementation of DKIM standard. First we need to install it.

apt update
apt install opendkim opendkim-tools

We will create new directory to keep additional configuration files of OpenDKIM.

mkdir -p /etc/opendkim
chown -R opendkim:opendkim /etc/opendkim
chmod 700 /etc/opendkim
cd /etc/opendkim

We will create table for keys in /etc/opendkim/KeyTable:

ignore.pl ignore.pl:mail:/etc/opendkim/keys/mail.private

We will also create signing table in /etc/opendkim/SigningTable:

*@ignore.pl mail._domainkey.ignore.pl

Another table with trusted hosts in /etc/opendkim/TrustedHosts:

Now, we will modify configuration file in /etc/opendkim.conf so it will contain following options. Just like in Dovecot file is decently documented so we can just read it and set things to our preferences. If parameters are missing from here, then assume you should comment them.

Syslog              yes
UMask               022
Canonicalization    relaxed/relaxed
Mode                sv
Socket              inet:8891@localhost
PidFile             /var/run/opendkim/opendkim.pid
UserID              opendkim:opendkim
AutoRestart         yes
AutoRestartRate     10/1h
SyslogSuccess       Yes
LogWhy              Yes
ExternalIgnoreList  refile:/etc/opendkim/TrustedHosts
InternalHosts       refile:/etc/opendkim/TrustedHosts
KeyTable            refile:/etc/opendkim/KeyTable
SigningTable        refile:/etc/opendkim/SigningTable

We should also make Postfix aware of what we are doing. Add or edit following parameters in /etc/postfix/main.cf:

milter_protocol = 2
milter_default_action = accept
smtpd_milters = inet:localhost:8891
non_smtpd_milters = inet:localhost:8891

We really should have DKIM key now, we will generate it in subdirectory with:

cd /etc/opendkim
mkdir keys
chmod 750 keys
cd keys
opendkim-genkey -s mail -d ignore.pl

We need to modify our DNS zone. While we are at it we will also add SPF entry. Entry for DKIM has been generated along with private key and should be located in /etc/opendkim/keys/mail.txt. We will add something similar to (obviously key will differ):

                600 IN TXT "v=spf1 a mx -all"
mail._domainkey 600 IN TXT ( "v=DKIM1; h=sha256; k=rsa; " "p=MIIBIjANB..." "..." )

Restart postfix and opendkim, give some time for DNS to propagate and we are ready to test it.

systemctl restart postfix opendkim

If you want to test DKIM then for instance you can send an email to google mail account and check Authentication-Results header. When we are done with it, there is one more important topic we should take care of - mail filtering. This will include both scanning and antispam. We will need to install some new software and change existing configurations. Hopefully I will spend some time after writing this and connect each section about certain piece of software into a single section. Anyway!

apt update
apt install amavisd-new spamassassin clamav-daemon
apt install dovecot-sieve
apt install arj cabextract cpio nomarch pax rar unrar unzip zip

First off, something we don’t really need to configure - ClamAV. Its defaults are quite enough in general use. If you are curious then configuration files can be found in /etc/clamav. One thing we should do is to add clamav user to amavis group and vice versa.

adduser clamav amavis
adduser amavis clamav

Spamassassin also doesn’t require a lot of changes. All we need to do is to change /etc/default/spamassassin and then restart service in systemd.


We will now enable amavis filtering in postfix. Both master.cf and main.cf need to be modified.

content_filter = smtp-amavis:[]:10024

While last two shouldn’t exist in master.cf, pickup will exist almost certainly, so edit it rather than adding.

pickup    unix  n       -       y       60      1       pickup
   -o content_filter=
   -o receive_override_options=no_header_body_checks
smtp-amavis   unix   -   -      -       -       2       smtp
   -o smtp_data_done_timeout=1200
   -o smtp_send_xforward_command=yes
   -o max_use=20 inet   n   -    -       -       -       smtpd
   -o content_filter=
   -o local_recipient_maps=
   -o relay_recipient_maps=
   -o smtpd_restriction_classes=
   -o smtpd_delay_reject=no
   -o smtpd_client_restrictions=permit_mynetworks,reject
   -o smtpd_helo_restrictions=
   -o smtpd_sender_restrictions=
   -o smtpd_recipient_restrictions=permi_mynetworks,reject
   -o smtpd_data_restrictions=reject_unauth_pipelining
   -o smtpd_end_of_data_restrictions=
   -o mynetworks=
   -o smtpd_error_sleep_time=0
   -o smtpd_soft_error_limit=1001
   -o smtpd_hard_error_limit=1000
   -o smtpd_client_connection_count_limit=0
   -o smtpd_client_connection_rate_limit=0
   -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters

We would like to use spam filtering and antivirus scans. We need to enable them in amavis. There are several configuration files in amavis, but we will use one that is intended for general usage: /etc/amavis/conf.d/50-user. Settings in this file can override toher settings. In that file there is place for our modifications so use it.

@local_domains_acl = ( ".%mydomain", "ignore.pl" );

@bypass_virus_checks_maps = (
   \%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re);

@bypass_spam_checks_maps = (
   \%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re);

We are almost there. Let’s edit Dovecot sieve plugin along with its own configuration files. We will need to find these options in various files. Once again, I should compile all configuration of each software into a single section…

protocol lmtp {
  mail_plugins = $mail_plugins sieve

plugin {
  sieve_before = /var/lib/dovecot/sieve.d/

And then create directory and file /var/lib/dovecot/sieve.d/spam.sieve. Add permissions to it for user mail:mail. In that file write:

require ["fileinto"];

if header :contains "X-Spam-Flag" "YES"
  fileinto "Junk";

Compile it, tweak permissions, restart everything and… It’s done!

sievec /var/lib/dovecot/sieve.d/spam.sieve
chown -R mail:mail /var/lib/dovecot/sieve.d
systemctl restart spamassassin dovecot postfix amavis clamav-daemon

While it is done, there are still possible tweaks to this configuration and the following section is just for such modifications. Hopefully it will grow and will be integrated into main document fast.