Set up a Mail Server with PostfixAdmin on Debian 9

 

In this tutorial, we will show you how to set up a mail server with PostfixAdmin on a Debian 9 VPS. We will configure a mail server with Postfix, Dovecot, and MariaDB.

PostfixAdmin is a web based GUI to the database that is used by Postfix. You can easily manage your mail server, like adding and removing domains, email accounts, aliases, and quotas through the use of Postfixadmin. The web-based GUI makes these functions and features more accessible, and allows you to change and access information while away from your terminal. Let’s get started.

Prerequisites:

  • a Debian 9 VPS
  • SSH access with root privileges, or access to a user that has sudo privileges

Step 1: Log in via SSH and Update the System

Log in to your Debian 9 VPS:

# ssh root@IP_Address -p Port_number

You can check whether you have the proper Debian version installed on your server with the following command:

# lsb_release -a

You should get this output:

Distributor ID: Debian
Description: Debian GNU/Linux 9.9 (Stretch)
Release: 9.9
Codename: stretch

Then, run the following command to make sure that all installed packages on the server are updated to their latest available versions:

# apt update && apt upgrade

Step 2: Create a System User

For security reasons, we will create a new system user who will be the owner of all mailboxes.

# useradd -r -u 150 -g mail -d /var/vmail -s /sbin/nologin -c "Virtual Mail User" vmail
# mkdir -p /var/vmail
# chmod -R 770 /var/vmail
# chown -R vmail:mail /var/vmail

Step 3: Install MariaDB Server

PostfixAdmin supports MySQL/MariaDB, PostgreSQL, and SQLite database systems. In this tutorial, we will use MySQL/MariaDB as the database storage engine.

# apt install mariadb-server

To start the MariaDB service and enable it to start on boot, execute the following commands:

# systemctl start mariadb
# systemctl enable mariadb

Now, you can skip the following step if you prefer not to have a MySQL root password.

# mysql_secure_installation

When prompted, answer the questions below by following the guide.

Enter current password for root (enter for none): Just press the [Enter] key since there is no default password
Set root password? [Y/n]: Y
New password: Enter password
Re-enter new password: Repeat password
Remove anonymous users? [Y/n]: Y
Disallow root login remotely? [Y/n]: Y
Remove test database and access to it? [Y/n]: Y
Reload privilege tables now? [Y/n]: Y

If you followed the above step, then you would have a newly set password for MySQL root user.

Next, run this command to access the MySQL shell.

# mysql -u root -p

Remember to enter your root password if you ran the secure installation script.

Let’s proceed with creating a database for PostfixAdmin:

MariaDB [(none)]> CREATE DATABASE postfixadmin;
MariaDB [(none)]> GRANT ALL PRIVILEGES ON postfixadmin.* TO 'postfixadmin'@'localhost' IDENTIFIED BY 'M0d1fyth15';
MariaDB [(none)]> FLUSH PRIVILEGES;
MariaDB [(none)]> \q

Make sure to replace the password for the database user to a good unique password.

Step 4: Install PHP and all Necessary PHP Modules

This is

apt install apache2 php php-mbstring php-imap php-mysql libapache2-mod-php7.0

Step 5: Install Postfix

Install Postfix with the command below:

# apt install postfix-mysql


Choose Internet Site and click on Ok to continue.

You will be asked for the system mail name, make sure this is the same with your hostname.

Once the installation is completed, we need to create configuration files:

# mkdir -p /etc/postfix/sql/

From here, several configuration files will be created. Make sure to replace any and all values that are different here to the necessary values, or your configuration may not work.

# nano /etc/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf
user = postfixadmin
password = M0d1fyth15
hosts = localhost
dbname = postfixadmin
query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'
# nano /etc/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf
user = postfixadmin
password = M0d1fyth15
hosts = localhost
dbname = postfixadmin
query = SELECT maildir FROM mailbox,alias_domain WHERE alias_domain.alias_domain = '%d' and mailbox.username = CONCAT('%u', '@', alias_domain.target_domain) AND mailbox.active = 1 AND alias_domain.active='1'
# nano /etc/postfix/sql/mysql_virtual_alias_domain_maps.cf
user = postfixadmin
password = M0d1fyth15
hosts = localhost
dbname = postfixadmin
query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'
# nano /etc/postfix/sql/mysql_virtual_alias_maps.cf
user = postfixadmin
password = M0d1fyth15
hosts = localhost
dbname = postfixadmin
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'
# nano /etc/postfix/sql/mysql_virtual_domains_maps.cf
user = postfixadmin
password = M0d1fyth15
hosts = localhost
dbname = postfixadmin
query = SELECT domain FROM domain WHERE domain='%s' AND active = '1'
# nano /etc/postfix/sql/mysql_virtual_mailbox_limit_maps.cf
user = postfixadmin
password = M0d1fyth15
hosts = localhost
dbname = postfixadmin
query = SELECT quota FROM mailbox WHERE username='%s' AND active = '1'
# nano /etc/postfix/sql/mysql_virtual_mailbox_maps.cf
user = postfixadmin
password = M0d1fyth15
hosts = localhost
dbname = postfixadmin
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'

In the configuration files above, we use ‘m0d1fyth15‘ as the password, please change it to your own password.

Now, let’s edit the main.cf file by invoking the following commands:

postconf -e "myhostname = $(hostname -f)"
postconf -e "virtual_mailbox_domains = proxy:mysql:/etc/postfix/sql/mysql_virtual_domains_maps.cf"
postconf -e "virtual_alias_maps = proxy:mysql:/etc/postfix/sql/mysql_virtual_alias_maps.cf, proxy:mysql:/etc/postfix/sql/mysql_virtual_alias_domain_maps.cf, proxy:mysql:/etc/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf"
postconf -e "virtual_mailbox_maps = proxy:mysql:/etc/postfix/sql/mysql_virtual_mailbox_maps.cf, proxy:mysql:/etc/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf"
postconf -e "smtpd_tls_cert_file = /etc/ssl/certs/ssl-cert-snakeoil.pem"
postconf -e "smtpd_tls_key_file = /etc/ssl/private/ssl-cert-snakeoil.key"
postconf -e "smtpd_use_tls = yes"
postconf -e "smtpd_tls_auth_only = yes"
postconf -e "smtpd_sasl_type = dovecot"
postconf -e "smtpd_sasl_path = private/auth"
postconf -e "smtpd_sasl_auth_enable = yes"
postconf -e "smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination"
postconf -e "mydestination = localhost"
postconf -e "mynetworks = 127.0.0.0/8"
postconf -e "inet_protocols = ipv4"
postconf -e "inet_interfaces = all"
postconf -e "virtual_transport = lmtp:unix:private/dovecot-lmtp"

Open the master.cf file, find submission inet n and smtps inet n sections and edit as follows:

# nano /etc/postfix/master.cf
smtp inet n - y - - smtpd
#smtp inet n - y - 1 postscreen
#smtpd pass - - y - - smtpd
#dnsblog unix - - y - 0 dnsblog
#tlsproxy unix - - y - 0 tlsproxy
submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
# -o smtpd_reject_unlisted_recipient=no
# -o smtpd_client_restrictions=$mua_client_restrictions
# -o smtpd_helo_restrictions=$mua_helo_restrictions
# -o smtpd_sender_restrictions=$mua_sender_restrictions
# -o smtpd_recipient_restrictions=
# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
smtps inet n - y - - smtpd
-o syslog_name=postfix/smtps
# -o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
# -o smtpd_reject_unlisted_recipient=no
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
# -o smtpd_client_restrictions=$mua_client_restrictions
# -o smtpd_helo_restrictions=$mua_helo_restrictions
# -o smtpd_sender_restrictions=$mua_sender_restrictions
# -o smtpd_recipient_restrictions=
# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING

Enable and restart the Postfix service:

# systemctl enable postfix
# systemctl restart postfix

Step 6: Install Dovecot

Install Dovecot using the command below:

# apt install dovecot-mysql

Open the /etc/dovecot/conf.d/10-mail.conf file and change the following values:

# nano /etc/dovecot/conf.d/10-mail.conf
mail_location = maildir:/var/vmail/%d/%n
mail_privileged_group = mail
mail_uid = vmail
mail_gid = mail
first_valid_uid = 150
last_valid_uid = 150

Open the /etc/dovecot/conf.d/10-auth.conf file and change the following values:

# nano /etc/dovecot/conf.d/10-auth.conf
auth_mechanisms = plain login
#!include auth-system.conf.ext
!include auth-sql.conf.ext

Edit the dovecot-sql.conf.ext file, then append these lines:

# nano /etc/dovecot/dovecot-sql.conf.ext
driver = mysql
connect = host=localhost dbname=postfixadmin user=postfixadmin password=M0d1fyth15
default_pass_scheme = MD5-CRYPT
password_query = SELECT username as user, password, '/var/vmail/%d/%n' as userdb_home, 'maildir:/var/vmail/%d/%n' as userdb_mail, 150 as userdb_uid, 8 as userdb_gid FROM mailbox WHERE username = '%u' AND active = '1'
user_query = SELECT '/var/vmail/%d/%u' as home, 'maildir:/var/vmail/%d/%u' as mail, 150 AS uid, 8 AS gid, concat('dirsize:storage=', quota) AS quota FROM mailbox WHERE username = '%u' AND active = '1'

Remember to replace the password as well as any other different values here as well.

In the /etc/dovecot/conf.d/10-ssl.conf file, enable SSL support:

# nano /etc/dovecot/conf.d/10-ssl.conf
ssl = yes

Open the /etc/dovecot/conf.d/15-lda.conf file and set the postmaster_address email address.

# nano /etc/dovecot/conf.d/15-lda.conf
postmaster_address = postmaster@yourdomain.com

Open the /etc/dovecot/conf.d/10-master.conf file, find the service lmtp section and change it to the following:

# nano /etc/dovecot/conf.d/10-master.conf
service lmtp {
     unix_listener /var/spool/postfix/private/dovecot-lmtp {
     mode = 0600
     user = postfix
     group = postfix
     }
}

find the service auth section and change it to:

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

unix_listener auth-userdb {
     mode = 0600
     user = vmail

}
user = dovecot
}

Change the service auth-worker section to the following:

service auth-worker {
user = vmail
}

Set the permissions:

# chown -R vmail:dovecot /etc/dovecot
# chmod -R o-rwx /etc/dovecot

Enable and restart the Dovecot service

# systemctl enable dovecot
# systemctl restart dovecot

Step 7: Install PostfixAdmin

In this step, we will download PostfixAdmin from Github and store it in the /opt directory.

# apt install git
# cd /opt
# git clone https://github.com/postfixadmin/postfixadmin.git

# ln -s /opt/postfixadmin/public/ /var/www/html/mailadmin

Insert these lines to /opt/postfixadmin/config.local.php

# nano /opt/postfixadmin/config.local.php
<?php
$CONF['database_type'] = 'mysqli';
$CONF['database_host'] = 'localhost';
$CONF['database_user'] = 'postfixadmin';
$CONF['database_password'] = 'M0d1fyth15';
$CONF['database_name'] = 'postfixadmin';
$CONF['configured'] = true;

?>
# mkdir /opt/postfixadmin/templates_c

# chown -R www-data: /opt/postfixadmin

Connect to http://1.2.3.4/mailadmin/setup.php, making sure to replace 1.2.3.4 with your server actual IP address. If everything is okay, scroll down and create a setup password.

Need a fast and easy fix?
✔ Unlimited Managed Support
✔ Supports Your Software
✔ 2 CPU Cores
✔ 2 GB RAM
✔ 50 GB PCIe4 NVMe Disk
✔ 1854 GeekBench Score
✔ Unmetered Data Transfer
NVME 2 VPS

Now just $43 .99
/mo

GET YOUR VPS

click on generate hash, and you will see this:

You need to paste the setup_password hash as shown in the picture above to /opt/postfixadmin/config.local.php, it should look like this

<?php
$CONF['database_type'] = 'mysqli';
$CONF['database_host'] = 'localhost';
$CONF['database_user'] = 'postfixadmin';
$CONF['database_password'] = 'M0d1fyth15';
$CONF['database_name'] = 'postfixadmin';
$CONF['configured'] = true;

$CONF['setup_password'] = 'd7b5b4ed7e91d81f246d802a6f09474a:69478ddf65feb4036215cb2c6f48c3dfd815ee8c';
?>

Save the file, then you can go back to the setup page to create a PostfixAdmin admin user. The setup password in that page is the same password you used in the previous step – fill the other blanks as you wish, then click on the ‘Add admin’ button.

Now, you can go to http://1.2.3.4/mailadmin/login.php to add your domains and create email addresses there.

That’s it! You now have PostfixAdmin set up and running on your Debian 9 server.


Of course, you don’t have to set up a mail server with PostfixAdmin on Debian 9 if you use one of our Mail Server Hosting services, in which case you can simply ask our expert Linux admins to set up a mail server for you. They are available 24×7 and will take care of your request immediately.

PS. If you liked this post on how to set up a mail server with PostfixAdmin on Debian 9, or if you found it helpful, please share it with your friends on the social networks using the share shortcuts, or simply leave a comment in the Comments Section below. Thanks.

9 thoughts on “Set up a Mail Server with PostfixAdmin on Debian 9”

  1. I followed your guide, and I am having issues with my users on looking for mail. I perhaps missed something on my configs. Dovecot is looking for the mailbox at /var/vmail/mydomain.com/user@mydomain.com yet the mailbox is at var/vmail/mydomain.com/user. I am not sure what I did wrong in my configs:

    May 6 20:29:59 serverhostname dovecot: pop3(user@mydomain.com): Debug: Effective uid=150, gid=8, home=/var/vmail/mydomain.com/user@mydomain.com
    May 6 20:29:59 serverhostname dovecot: pop3(user@mydomain.com): Debug: Namespace inbox: type=private, prefix=, sep=, inbox=yes, hidden=no, list=yes, subscriptions=yes location=maildir:/var/vmail/mydomain.com/user@mydomain.com
    May 6 20:29:59 serverhostname dovecot: pop3(user@mydomain.com): Debug: maildir++: root=/var/vmail/mydomain.com/user@mydomain.com, index=, indexpvt=, control=, inbox=/var/vmail/mydomain.com/user@mydomain.com, alt=

    Please helps

    Reply
    • For Kazishe et al, there is a mistake in the given config for /etc/dovecot/dovecot-sql.conf.ext where password_query and user_query are using two different directory formats.

      password_query is using /var/vmail/%d/%n as the maildir, but user_query is using /var/vmail/%d/%u

      %n expands to “user”, where %u expands to “user@domian.tld”. This is probably why there are issues with Dovecot saving under a different directory than it is later looking for the messages in. You need to make them both the same with %n. Better yet, use /var/vmail/%Ld/%Ln so that the domain and username are lower-cased. This way if a message gets through with a uSeR@domain.tld it doesn’t get stored in a different directory from user@domain.tld.

      Here is the full file:

      # /etc/dovecot/dovecot-sql.conf.ext
      driver = mysql
      connect = host=/var/run/mysqld/mysqld.sock dbname=postfix user=postfix password=PICKAPASSWORD
      default_pass_scheme = MD5-CRYPT
      password_query = SELECT username as user, password, ‘/var/vmail/%Ld/%Ln’ as userdb_home, ‘maildir:/var/vmail/%Ld/%Ln’ as userdb_mail, 150 as userdb_uid, 8 as userdb_gid FROM mailbox WHERE username = ‘%u’ AND active = ‘1’
      user_query = SELECT ‘/var/vmail/%Ld/%Ln’ as home, ‘maildir:/var/vmail/%Ld/%Ln’ as mail, 150 AS uid, 8 AS gid, concat(‘dirsize:storage=’, quota) AS quota FROM mailbox WHERE username = ‘%u’ AND active = ‘1’
      iterate_query = SELECT username AS user FROM mailbox WHERE active = ‘1’ ORDER BY username

      Reply
  2. Hi friends,
    I’m having trouble after following your guide. I configure an email account with imap and try to send an email, it tells me the following message “Your message was sent but a copy was not placed in the sent folder (Sent)”
    No errors are seen from the server.
    Can you help me?
    Thanks

    Reply
  3. I’m check the dovecot.conf and have this config rules
    my dovecot -n is :

    # Pigeonhole version 0.5.4 ()
    # OS: Linux 4.19.0-16-amd64 x86_64 Debian 10.9 ext4
    # Hostname: mail.xxxxx.com
    auth_debug = yes
    auth_debug_passwords = yes
    auth_mechanisms = plain login
    auth_verbose = yes
    auth_verbose_passwords = yes
    debug_log_path = /var/log/dovecot-debug
    log_path = /var/log/dovecot
    mail_debug = yes
    mail_location = maildir:/var/mail/%d/%n:INDEX=/var/mail/%d/%n/indexes
    mail_privileged_group = mail
    namespace inbox {
    inbox = yes
    location =
    mailbox Drafts {
    special_use = \Drafts
    }
    mailbox Enviados {
    special_use = \Sent
    }
    mailbox Junk {
    special_use = \Junk
    }
    mailbox Sent {
    special_use = \Sent
    }
    mailbox «Sent Messages» {
    special_use = \Sent
    }
    mailbox Trash {
    special_use = \Trash
    }
    prefix =
    separator = .
    }
    passdb {
    args = /etc/dovecot/dovecot-mysql.conf.ext
    driver = sql
    }
    plugin {
    sieve = file:~/sieve;active=~/.dovecot.sieve
    }
    protocols = » imap lmtp pop3 lmtp»
    service auth-worker {
    user = vmail
    }
    service auth {
    unix_listener /var/spool/postfix/private/auth {
    group = postfix
    mode = 0666
    user = postfix
    }
    unix_listener auth-userdb {
    mode = 0600
    user = vmail
    }
    user = dovecot
    }
    service lmtp {
    unix_listener /var/spool/postfix/private/dovecot-lmtp {
    group = postfix
    mode = 0600
    user = postfix
    }
    }
    service stats {
    unix_listener stats-reader {
    group = vmail
    mode = 0660
    user = vmail
    }
    unix_listener stats-writer {
    group = vmail
    mode = 0660
    user = vmail
    }
    }
    ssl_cert = </etc/letsencrypt/live/mail.xxxxx.com/fullchain.pem
    ssl_client_ca_dir = /etc/ssl/certs
    ssl_dh = # hidden, use -P to show it
    ssl_key = # hidden, use -P to show it
    userdb {
    args = /etc/dovecot/dovecot-mysql.conf.ext
    driver = sql
    }
    protocol lmtp {
    mail_plugins = " sieve"
    }
    protocol lda {
    mail_plugins = " quota sieve"
    postmaster_address = admin@xxxx.com
    }
    protocol imap {
    mail_max_userip_connections = 10
    }
    I can't find the error

    Reply
  4. I did this and works flawless. One issue I had was the current git repository for postfixadmin, the encryption is set to phpcrypt instead of md5crypt that dovecot is using, so that causes the client to not authenticate.

    Reply

Leave a Comment