Part 3: PostfixAdmin – Create Virtual Mailboxes on CentOS 8/RHEL 8 Mail Server

In previous articles, we discussed how to set up your own mail server on CentOS 8/RHEL 8 from scratch. In part 1 and part 2 of this tutorial series, we learned how to set up Postfix SMTP server and Dovecot IMAP server, but so far we can only have email addresses for users with local Unix account. This tutorial is going to show you how to create virtual mailboxes on CentOS 8/RHEL 8 mail server with PostfixAdmin, which is an open-source web-based interface to configure and manage a Postfix-based email server for many domains and users.

With virtual mailboxes, we don’t need to create local Unix account for each email address. If you are going to set up a mail server for a company or organization, it’s always better to have an easy way to create virtual mailboxes in a web-based interface, which also allows users to change their passwords. That’s where PostfixAdmin comes in.

PostfixAdmin Features

  • manage mailboxes, virtual domains and aliases
  • vacation/out-of-office messages (Personally I think this feature is better done in Roundcube Webmail.)
  • alias domains (forwarding one domain to another with recipient validation)
  • users can manage their own mailbox (change alias, password and vacation message)
  • quota support for single mailboxes and total quota of a domain
  • fetchmail integration: You can fetch emails from your original email address to your new email address.
  • command line client postfixadmin-cli for those who don’t want to click around in a web interface 😉

Note: Once you finish part 3, you can no longer use local Unix accounts as email addresses. You must create email addresses from the PostfixAdmin web interface.

Prerequisites

I assume that you have followed part 1 and part 2 of this tutorial series. If you followed mail server tutorials on other websites, I recommend purging your configurations and start over with my tutorial series, so you are not going to be confused by different setup processes.

Once the above requirements are met, let’s install and configure PostfixAdmin.

Step 1: Install MariaDB Database Server on CentOS 8/RHEL 8

PostfixAdmin is written in PHP and requires a database (MySQL/MariaDB, PostgreSQL or SQLite). This article will use MariaDB, which is a drop-in replacement for MySQL. It is developed by former members of MySQL team who are concerned that Oracle might turn MySQL into a closed-source product. Enter the following command to install MariaDB on CentOS 8/RHEL 8.

sudo dnf install mariadb-server mariadb -y

After it’s installed, we need to start it.

sudo systemctl start mariadb

Enable auto start at system boot time.

sudo systemctl enable mariadb

Check status:

systemctl status mariadb

output:

● mariadb.service - MariaDB 10.3 database server
   Loaded: loaded (/usr/lib/systemd/system/mariadb.service; enabled; vendor preset: disabled)
   Active: active (running) since Sat 2019-10-12 09:02:53 UTC; 33s ago
     Docs: man:mysqld(8)
           https://mariadb.com/kb/en/library/systemd/
 Main PID: 18608 (mysqld)
   Status: "Taking your SQL requests now..."
    Tasks: 30 (limit: 5092)
   Memory: 77.0M
   CGroup: /system.slice/mariadb.service
           └─18608 /usr/libexec/mysqld --basedir=/usr

Enabled” indicates that auto start at boot time is enabled and we can see that MariaDB server is running. Now we need to run the security script.

sudo mysql_secure_installation

When it asks you to enter MariaDB root password, press Enter key as the root password isn’t set yet. Then enter y to set the root password for MariaDB server.

install mariadb on redhat 8 centos 8

Next, you can press Enter to answer all remaining questions, which will remove anonymous user, disable remote root login and remove test database. This step is a basic requirement for MariaDB database security. (Note that the letter Y is capitalized, which means it’s the default answer.)

mysql_secure_installation rhel8 centos8

Step 2: Download PostfixAdmin on CentOS 8/RHEL 8 Server

Log into your mail server, then download PostfixAdmin install file onto your server. Go to PostfixAdmin Gitbub page to download the latest version. You can use the wget tool to download it from command line. The download link is always available in the format below. If a new version comes out, simply replace 3.3.8 with the new version number.

sudo dnf install wget

wget https://github.com/postfixadmin/postfixadmin/archive/postfixadmin-3.3.8.tar.gz

Once downloaded, extract the archive.

If you are using Apache, then extract it to /var/www/ directory and rename it to postfixadmin.

sudo dnf install tar
sudo tar xvf postfixadmin-3.3.8.tar.gz -C /var/www/
sudo mv /var/www/postfixadmin-postfixadmin-3.3.8 /var/www/postfixadmin

If you are using Nginx, extract it to /usr/share/nginx/ directory and rename it to postfixadmin.

sudo dnf install tar
sudo tar xvf postfixadmin-3.3.8.tar.gz -C /usr/share/nginx/
sudo mv /usr/share/nginx/postfixadmin-postfixadmin-3.3.8 /usr/share/nginx/postfixadmin

Step 3: Setting Up Permissions

PostfixAdmin requires a templates_c directory, and the web server needs read and write access to this directory. We also need to change the SELinux context to make it writable. So run the following commands.

Apache

sudo mkdir /var/www/postfixadmin/templates_c
sudo setfacl -R -m u:apache:rwx /var/www/postfixadmin/templates_c/
sudo chcon -t httpd_sys_rw_content_t /var/www/postfixadmin/templates_c/ -R

Nginx

sudo mkdir /usr/share/nginx/postfixadmin/templates_c
sudo setfacl -R -m u:nginx:rwx /usr/share/nginx/postfixadmin/templates_c/
sudo chcon -t httpd_sys_rw_content_t /usr/share/nginx/postfixadmin/templates_c/ -R

By default, SELinux forbids Apache/Nginx to make network requests to other servers, but later Apache/Nginx needs to request TLS certificate status from Let’s Encrypt CA server for OCSP stapling, so we need to tell SELinux to allow Apache/Nginx with the following command.

sudo setsebool -P httpd_can_network_connect 1

If you use Nginx, then you also need to run the following command to give the nginx user read and write permissions to 3 directories.

sudo setfacl -R -m u:nginx:rwx /var/lib/php/opcache/ /var/lib/php/session/ /var/lib/php/wsdlcache/

Restart Apache/Nginx.

sudo systemctl restart httpd

sudo systemctl restart nginx

Starting with Dovecot 2.3.11, the web server user needs permission to read Let’s Encrypt TLS certificate in order to do password hashing. Run the following two commands to grant permissions.

Apache

sudo setfacl -R -m u:apache:rx /etc/letsencrypt/live/ /etc/letsencrypt/archive/

Nginx

sudo setfacl -R -m u:nginx:rx /etc/letsencrypt/live/ /etc/letsencrypt/archive/

Step 4: Create a Database and User for PostfixAdmin

Log into MySQL/MariaDB shell as root with the following command. You will need to enter the MySQL/MariaDB root password.

mysql -u root -p

Once you are logged in, create a database for PostfixAdmin using the following command. I named it postfixadmin, but you can use whatever name you like. (Don’t leave out the semicolon.)

create database postfixadmin;

Then enter the command below to create a database user for PostfixAdmin. This command also grant all privileges of postfixadmin database to the user. Replace postfixadmin_password with your preferred password. Note that the password should not contain the # character, or you might not be able to log in later.

grant all privileges on postfixadmin.* to 'postfixadmin'@'localhost' identified by 'postfixadmin_password';

Flush the privileges table for the changes to take effect and then get out of MariaDB shell.

flush privileges;

exit;

Step 5: Configure PostfixAdmin

The default PostfixAdmin configuration file is config.inc.php. We need to create a config.local.php file and add custom configurations.

Apache

sudo nano /var/www/postfixadmin/config.local.php

Nginx

sudo nano /usr/share/nginx/postfixadmin/config.local.php

Add the following lines in the file, so PostfixAdmin can connect to MySQL/MariaDB database. Replace postfixadmin_password with the real PostfixAdmin password created in step 4.

<?php
$CONF['configured'] = true;
$CONF['database_type'] = 'mysqli';
$CONF['database_host'] = 'localhost';
$CONF['database_port'] = '3306';
$CONF['database_user'] = 'postfixadmin';
$CONF['database_password'] = 'postfixadmin_password';
$CONF['database_name'] = 'postfixadmin';
$CONF['encrypt'] = 'dovecot:BLF-CRYPT';
$CONF['dovecotpw'] = "/usr/bin/doveadm pw -r 12";

Save and close the file. Note that we will use the BLF-CRYPT password scheme.

Step 6: Create Apache Virtual Host or Nginx Config File for PostfixAdmin

Apache

If you use Apache web server, create a virtual host for PostfixAdmin.

sudo nano /etc/httpd/conf.d/postfixadmin.conf

Put the following text into the file. Replace postfixadmin.example.com with your real domain name and don’t forget to set DNS A record for it.

<VirtualHost *:80>
  ServerName postfixadmin.example.com
  DocumentRoot /var/www/postfixadmin/public/

  ErrorLog /var/log/httpd/postfixadmin_error.log
  CustomLog /var/log/httpd/postfixadmin_access.log combined

  <Directory />
    Options FollowSymLinks
    AllowOverride All
  </Directory>

  <Directory /var/www/postfixadmin/public/>
    Options FollowSymLinks MultiViews
    AllowOverride All
    Order allow,deny
    allow from all
  </Directory>

</VirtualHost>

Save and close the file. Reload Apache for the changes to take effect.

sudo systemctl reload httpd

Now you should be able to see the PostfixAdmin web-based install wizard at http://postfixadmin.example.com/setup.php.

Nginx

If you use Nginx web server, create a virtual host for PostfixAdmin.

sudo nano /etc/nginx/conf.d/postfixadmin.conf

Put the following text into the file. Replace postfixadmin.example.com with your real domain name and don’t forget to set DNS A record for it.

server {
   listen 80;
   listen [::]:80;
   server_name postfixadmin.example.com;

   root /usr/share/nginx/postfixadmin/public/;
   index index.php index.html;

   access_log /var/log/nginx/postfixadmin_access.log;
   error_log /var/log/nginx/postfixadmin_error.log;

   location / {
       try_files $uri $uri/ /index.php;
   }

   location ~ ^/(.+\.php)$ {
        try_files $uri =404;
        fastcgi_pass unix:/run/php-fpm/www.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include /etc/nginx/fastcgi_params;
   }
}

Save and close the file. Then test Nginx configuration.

sudo nginx -t

If the test is successful, reload Nginx for the changes to take effect.

sudo systemctl reload nginx

Now you should be able to see the PostfixAdmin web-based install wizard at http://postfixadmin.example.com/setup.php.

Step 7: Install Required and Recommended PHP Modules

PostfixAdmin requires the php-imap module to create subfolders in mailboxes, but php-imap isn’t included in the default CentOS 8/RHEL 8 repository, so we need to use the Remi repo to install this PHP module.

Install the Remi Repo.

sudo dnf install -y https://rpms.remirepo.net/enterprise/remi-release-8.rpm

Then reset PHP module streams.

sudo dnf module reset php

Enable the php:remi-7.4 module stream.

sudo dnf module enable php:remi-7.4 -y

Then you can run the following command to install PHP modules required or recommended by PostfixAdmin.

sudo dnf install -y php-fpm php-imap php-mbstring php-mysqlnd php-gd php-opcache php-json php-curl php-zip php-xml php-bz2 php-intl php-gmp

We  need to run the following command to tell SELinux to allow Apache to execute PHP code via PHP-FPM.

sudo setsebool -P httpd_execmem 1

If you use Nginx, edit the PHP-FPM config file:

nano /etc/php-fpm.d/www.conf

By default, PHP-FPM runs as the apache user. Since you are using Nginx web server, we need to change it. Find the following two lines.

user = apache
group = apache

Change them to

user = nginx
group = nginx

Save and close the file. Then start PHP-FPM.

sudo systemctl start php-fpm

Enable auto start at system boot time.

sudo systemctl enable php-fpm

Restart Apache or Nginx.

sudo systemctl restart httpd

sudo systemctl restart nginx

Step 8: Enabling HTTPS

To encrypt the HTTP traffic, we can enable HTTPS by installing a free TLS certificate issued from Let’s Encrypt.

If you use Apache, run this command to obtain and install TLS certificate.

sudo certbot --apache --agree-tos --redirect --hsts --staple-ocsp --email [email protected] -d postfixadmin.example.com

If you use Nginx,  run the following command to obtain and install TLS certificate.

sudo certbot --nginx --agree-tos --redirect --hsts --staple-ocsp --email [email protected] -d postfixadmin.example.com

Where:

  • --apache: Use the Apache plugin.
  • --nginx: Use the nginx plugin.
  • --agree-tos: Agree to terms of service.
  • --redirect: Force HTTPS by 301 redirect.
  • --hsts: Add the Strict-Transport-Security header to every HTTP response. Forcing browser to always use TLS for the domain. Defends against SSL/TLS Stripping.
  • --staple-ocsp: Enables OCSP Stapling. A valid OCSP response is stapled to the certificate that the server offers during TLS.
  • --email: Email used for registration and recovery contact.
  • -d flag is followed by a list of domain names, separated by comma. You can add up to 100 domain names.

The certificate should now be obtained and automatically installed, which is indicated by the messages below.

postfixadmin https

Step 9: Genereate a Custom SELinux Policy for the Web Server

During installation, PostfixAdmin needs to read the Dovecot configuration files in order to create password. By default, SELinux doesn’t allow the web server to read Dovecot configuration files. We need to create a custom SELinux policy to allow this action.

Install required packages.

sudo dnf install binutils rpm-build setools-console policycoreutils-python3 policycoreutils-devel

Generate a custom policy for httpd. (If you use Nginx, replace httpd with nginx.)

sudo sepolicy generate --init /usr/sbin/httpd

Edit the type enforcement file.

sudo nano httpd.te

Add the following line at the end of this file, so Apache/Nginx will be able to read Dovecot configuration files.

dovecot_read_config(httpd_t)

Save and close the file. Then install the new SELinux policy.

sudo ./httpd.sh

Now Apache/Nginx should be able to read Dovecot configuration files.

If you don’t know what to add to the httpd.te file, run the following command after a SELinux denial happens.

sudo ausearch -m AVC -ts recent | audit2allow -R

audit2allow will suggest changes.

audit2allow add custom selinux policy

Step 10: Enable Statistics in Dovecot

PostfixAdmin needs to read Dovecot statistics. Edit the Dovecot configuration file.

sudo nano /etc/dovecot/conf.d/10-master.conf

Add the following lines to the end of this file. If you use Nginx, change apache to nginx.

service stats {
    unix_listener stats-reader {
    user = apache
    group = apache
    mode = 0660
}

unix_listener stats-writer {
    user = apache
    group = apache
    mode = 0660
  }
}

Save and close the file.  Then add the web server to the dovecot group.

Apache

sudo gpasswd -a apache dovecot

NGinx

sudo gpasswd -a nginx dovecot

Restart Dovecot.

sudo systemctl restart dovecot

Step 11: Finish the Installation in Web Browser

Go to postfixadmin.example.com/setup.php to run the web-based setup wizard. First, you need to create a setup password for PostfixAdmin.

postfixadmin setup password

After creating the password hash, PostfixAdmin will display a line like below.

$CONF['setup_password'] = 'db1b019982a6ba878bdc6bd923bef03e:4e29fdd341b570364064a5ad69652f3d8bee0bb4';

You need to open the config.local.php file.

Apache

sudo nano /var/www/postfixadmin/config.local.php

Nginx

sudo nano /usr/share/nginx/postfixadmin/config.local.php

Add the line display on PostfixAdmin setup page to the end of the file like below.

centos 8 postfixadmin setup password

After saving the file, you need to refresh the PostfixAdmin setup page and enter the setup password again, then create the admin account. Please don’t use a Gmail, Yahoo Mail, or Microsoft email address for the admin account, or you might not be able to log in later. Use an email address on your own domain. You can create the email address later in PostfixAdmin.

postfixadmin create superadmin account

Once the superadmin account is created, you can log into PostfixAdmin at postfixadmin.example.com/login.php.

postfixadmin virtual mailbox domains login

If you see the following error when trying to create a superadmin account,

can’t encrypt password with dovecotpw, see error log for details

It’s because the web server user doesn’t have permission to read Let’s Encrypt TLS certificate. To fix it, run the following two commands to grant permissions.

Apache

sudo setfacl -R -m u:apache:rx /etc/letsencrypt/live/
sudo setfacl -R -m u:apache:rx /etc/letsencrypt/archive/

Nginx

sudo setfacl -R -m u:nginx:rx /etc/letsencrypt/live/
sudo setfacl -R -m u:nginx:rx /etc/letsencrypt/archive/

If you see the following error,

Invalid query: Specified key was too long; max key length is 1000 bytes

Then you need to log in to MySQL/MariaDB database server as root from command line,

mysql -u root -p

and change the default collation from utf8mb4_general_ci to utf8_general_ci.

MariaDB [(none)]> alter database postfixadmin collate ='utf8_general_ci';

Exit MySQL/MariaDB console and reload the setup.php page.

Step 12: Configure Postfix to Use MySQL/MariaDB Database

By default, Postfix delivers emails only to users with a local Unix account. To make it deliver emails to virtual users whose information is stored in the database, we need to configure Postfix to use virtual mailbox domains.

First, we need to add MySQL map support for Postfix by installing the postfix-mysql package.

sudo dnf install postfix-mysql

Then edit the Postfix main configuration file.

sudo nano /etc/postfix/main.cf

Add the following lines at the end of this file. (In Nano text editor, you can press Ctrl+W, then Ctrl+V to jump to the end of a file.)

virtual_mailbox_domains = proxy:mysql:/etc/postfix/sql/mysql_virtual_domains_maps.cf
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
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

Where:

  • virtual_mailbox_domains points to a file that will tell Postfix how to look up domain information from the database.
  • virtual_mailbox_maps points to files that will tell Postfix how to look up email addresses from the database.
  • virtual_alias_maps points to files that will tell Postfix how to look up aliases from the database.

We want to use dovecot to deliver incoming emails to the virtual users’ message store, so also add the following line at the end of this file.

virtual_transport = lmtp:unix:private/dovecot-lmtp

Configure Postfix to Use MySQL MariaDB Database

Save and close the file. Next, we need to create the .cf files one by one. Create the sql directory.

sudo mkdir /etc/postfix/sql/

Create the mysql_virtual_domains_maps.cf file.

sudo nano /etc/postfix/sql/mysql_virtual_domains_maps.cf

Add the following content. Replace postfixadmin_password with the postfixadmin password you set in Step 4.

user = postfixadmin
password = postfixadmin_password
hosts = localhost
dbname = postfixadmin
query = SELECT domain FROM domain WHERE domain='%s' AND active = '1'
#query = SELECT domain FROM domain WHERE domain='%s'
#optional query to use when relaying for backup MX
#query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '0' AND active = '1'
#expansion_limit = 100

Create the mysql_virtual_mailbox_maps.cf file.

sudo nano /etc/postfix/sql/mysql_virtual_mailbox_maps.cf

Add the following content.

user = postfixadmin
password = postfixadmin_password
hosts = localhost
dbname = postfixadmin
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'
#expansion_limit = 100

Create the mysql_virtual_alias_domain_mailbox_maps.cf file.

sudo nano /etc/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf

Add the following content.

user = postfixadmin
password = postfixadmin_password
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'

Create the mysql_virtual_alias_maps.cf file.

sudo nano /etc/postfix/sql/mysql_virtual_alias_maps.cf

Add the following content.

user = postfixadmin
password = postfixadmin_password
hosts = localhost
dbname = postfixadmin
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'
#expansion_limit = 100

Create the mysql_virtual_alias_domain_maps.cf file.

sudo nano /etc/postfix/sql/mysql_virtual_alias_domain_maps.cf

Add the following content.

user = postfixadmin
password = postfixadmin_password
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'

Create the mysql_virtual_alias_domain_catchall_maps file.

sudo nano /etc/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf

Add the following content.

# handles catch-all settings of target-domain
user = postfixadmin
password = postfixadmin_password
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'

Since the database passwords are stored in plain text so they should be readable only by user postfix and root, which is done by executing the following two commands.

sudo chmod 0640 /etc/postfix/sql/*
sudo setfacl -R -m u:postfix:rx /etc/postfix/sql/

Next, we need to change the value of the mydestination parameter in Postfix. Display the current value:

postconf mydestination

Sample output:

mydestination = linuxbabe.com, $myhostname, localhost.$mydomain, localhost

The mydestination parameter contains a list of domain names that will receive emails delivered to local Unix accounts. In part 1, we added the apex domain name (like linuxbabe.com) to mydestination. Since we are going to use virtual mailbox, we need to remove the apex domain name from the list by issuing the following command.

sudo postconf -e "mydestination = \$myhostname, localhost.\$mydomain, localhost"

Now let’s open the Postfix main configuration file again.

sudo nano /etc/postfix/main.cf

Add the following lines at the end of this file.

virtual_mailbox_base = /var/vmail
virtual_minimum_uid = 2000
virtual_uid_maps = static:2000
virtual_gid_maps = static:2000

The first line defines the base location of mail files. The remaining 3 lines define which user ID and group ID Postfix will use when delivering incoming emails to the mailbox. We use the user ID 2000 and group ID 2000.

Save and close the file. Restart Postfix for the changes to take effect.

sudo systemctl restart postfix

Next, we need to create a user named vmail with ID 2000 and a group with ID 2000.

sudo adduser vmail --system --uid 2000 --user-group --no-create-home

Create the mail base location.

sudo mkdir /var/vmail/

Make vmail as the owner.

sudo chown vmail:vmail /var/vmail/ -R

We also need to change the SELinux context to make it writable.

sudo chcon -t mail_spool_t /var/vmail/ -R

Step 13: Configure Dovecot to Use MySQL/MariaDB Database

We also need to configure the Dovecot IMAP server to query user information from the database. First, run the following command to add MySQL support for Dovecot.

sudo dnf install dovecot-mysql

Then edit the 10-mail.conf file.

sudo nano /etc/dovecot/conf.d/10-mail.conf

In part 2, we used the following mail_location. Email messages are stored under the Maildir directory under each user’s home directory.

mail_location = maildir:~/Maildir

Since we are using virtual mailbox domain now, we need to enable mail_home for the virtual users by adding the following line in the file, because virtual users don’t have home directories by default.

mail_home = /var/vmail/%d/%n

rhel centos rocky linux mail_home virtual users

Save and close the file. Then edit the 10-auth.conf file.

sudo nano /etc/dovecot/conf.d/10-auth.conf

In part 2, we used the following value for auth_username_format.

auth_username_format = %n

The %n would drop away the domain if it was given. Because in part 2 we were using local Unix account for the username of every email address, we must use %n to drop away the domain, so users were able to login with the full email address.

Now we are using virtual mailbox domains, which means the username of every email address includes the domain part, so we need to change the auth_username_format as follows. %u won’t drop away the domain. This allows users to login with the full email address.

auth_username_format = %u

Uncomment the following line at the end of the file, so Dovecot can query user information from the database.

!include auth-sql.conf.ext

Now you probably don’t want local Unix users to send emails without registering email addresses in PostfixAdmin, then comment out the following line by adding the # character at the beginning, so Dovecot won’t query the local /etc/passwd or /etc/shadow file.

#!include auth-system.conf.ext

It can be helpful to add the following two lines in this file to debug login issues. The login errors would be logged into /var/log/maillog file. (Once users can login without problems, you can comment out the following two lines.)

auth_debug = yes
auth_debug_passwords = yes

dovecot-mysql-Password-database-authentication

Save and close the file.

Create the dovecot-sql.conf.ext file.

sudo nano /etc/dovecot/dovecot-sql.conf.ext

Here is the content that you should have. Replace postfixadmin_password with the postfixadmin password you set in Step 4.

driver = mysql

connect = host=localhost dbname=postfixadmin user=postfixadmin password=postfixadmin_password

default_pass_scheme = BLF-CRYPT

password_query = SELECT username AS user,password FROM mailbox WHERE username = '%u' AND active='1'

user_query = SELECT maildir, 2000 AS uid, 2000 AS gid FROM mailbox WHERE username = '%u' AND active='1'

iterate_query = SELECT username AS user FROM mailbox

Save and close the file. And restart Dovecot.

sudo systemctl restart dovecot

When a user tries to log in, Dovecot would generate an BLF-CRYPT hash from the password entered by the user, then compare it with the password hash stored in the database.

Step 14: Add Domain and Mailboxes in PostfixAdmin

Log in to PostfixAdmin web interface as the admin. Click the Domain List tab and select New Domain to add a domain. You can choose how many aliases and mailboxes are allowed for this domain.

postfixadmin add domain

Then click Virtual List tab and select Add Mailbox to add a new email address for your domain.

postfixadmin add mailbox

Now fire up your desktop email client such as Mozilla Thunderbird and add a mail account. If Thunderbird found your mail server configuration like below, simply click Done button and you will be able to read and send emails.

mozilla thunderbird set up an existing email account

If Thunderbird didn’t found your mail server configuration, then click Manual config button to enter your mail server details.

  • In the incoming server section, select IMAP protocol, enter mail.your-domain.com as the server name, choose port 143 and STARTTLS. Choose normal password as the authentication method.
  • In the outgoing section, select SMTP protocol, enter mail.your-domain.com as the server name, choose port 587 and STARTTLS. Choose normal password as the authentication method.

ubuntu postfix dovecot letsencrypt https

Hint: You can also use port 993 with SSL/TLS encryption for IMAP, and use port 465 with SSL/TLS encryption for SMTP. You should not use port 25 as the SMTP port in mail clients to submit outgoing emails.

You should now be able to connect to your own email server and also send and receive emails with your desktop email client!

Troubleshooting Tips

As a rule of thumb, you should always check the mail log (/var/log/maillog) on your mail server when an error happens. The following is a list of specific errors and troubleshooting tips.

Can’t login from Mail Clients

If you can’t log into your mail server from a desktop mail client, scan your mail server to find if the ports are open. Note that you should run the following command from another Linux computer or server. If you run it on your mail server, then the ports will always appear to be open.

sudo nmap mail.your-domain.com

And check if Dovecot is running.

systemctl status dovecot

You can also check the mail log (/var/log/maillog), which may give you some clues. If Dovecot fails to start, the error might not be logged to the /var/log/maillog file, you can run the following command to see what’s wrong.

sudo journalctl -eu dovecot

If you see the following error in the mail log, it’s likely that you didn’t set a correct password in the .cf files under /etc/postfix/sql/ directory.

postfix/trivial-rewrite[28494]: warning: virtual_alias_domains: proxy:mysql:/etc/postfix/sql/mysql_virtual_alias_maps.cf: table lookup problem
postfix/trivial-rewrite[28494]: warning: virtual_alias_domains lookup failure

If you see the following error in the mail log, it’s because you forgot to add mail_location = maildir:~/Maildir in the /etc/dovecot/conf.d/10-mail.conf file.

open(/var/mail/[email protected]) failed: Permission denied (euid=2000(vmail) egid=2000(vmail) missing +w perm: /var/mail, we're not in group 8(mail), dir owned by 0:8 mode=0775

Cloudflare DNS

As I said in part 1, if you use Cloudflare DNS service, you should not enable the CDN (proxy) feature when creating DNS A record and AAAA record for the hostname of your mail server. Cloudflare doesn’t support SMTP or IMAP proxy.

Relay Access Denied

If you see the “relay access denied” error when trying to send emails from a mail client, it’s most likely that you use port 25 as the SMTP port in your mail client. As I said a while ago, you should use port 587 or 465 as the SMTP port in mail clients (Mozilla Thunberbird, Microsoft Outlook, etc) to submit outgoing emails. Port 25 should be used for SMTP server to SMTP server communications.

postfix dovecot relay access denied

iOS Mail App

If you use the iOS Mail app to log into your mail server and encounter the following error.

ios the mail server is not responding

You can try to fix it by enforcing SSL encryption, for both SMTP and IMAP.

ios mail enforce SSL encryption

Fun fact: It seems the iOS Mail app has difficulty in supporting STARTTLS on IMAP port 143, but it supports STARTTLS on the submission port 587.

Temporary Lookup Failure

If your mail server was working fine for some time, but suddenly you find the following error in the mail log,

Aug 25 20:25:24 mx postfix/trivial-rewrite[3313]: warning: virtual_alias_domains: proxy:mysql:/etc/postfix/sql/mysql_virtual_alias_maps.cf: table lookup problem
Aug 25 20:25:24 mx postfix/trivial-rewrite[3313]: warning: virtual_alias_domains lookup failure
Aug 25 20:25:24 mx postfix/submission/smtpd[3464]: NOQUEUE: reject: 451 4.3.0 <[email protected]>: Temporary lookup failure;  proto=ESMTP
Aug 25 20:25:24 mx postfix/submission/smtpd[3464]: Temporary lookup failure

It’s likely that your MariaDB/MySQL database stopped somehow. You can use the following command to check when your database server stopped.

sudo journalctl -eu mariadb

or

sudo journalctl -eu mysql

A common cause for this situation is that your server is out-of-memory. Check if your server has enough memory.

htop

or

free -m

Change User Password in PostfixAdmin

Users can log into PostfixAdmin at https://postfixadmin.example.com/users/login.php, then change their passwords.

Automatically Clean the Junk Folder and Trash Folder

To delete emails in Junk folder for all users, you can run

sudo doveadm expunge -u *@example.com mailbox Junk all

To delete emails in Trash folder for all users, run

sudo doveadm expunge -u *@example.com mailbox Trash all

I think it’s better to clean emails that have been in the Junk or Trash folder for more than 2 weeks, instead of cleaning all emails.

sudo doveadm expunge -u *@example.com mailbox Junk savedbefore 2w

Then add a cron job to automate the job.

sudo crontab -e

Add the following line to clean Junk and Trash folder every day

@daily doveadm expunge -u *@example.com mailbox Junk savedbefore 2w;doveadm expunge -u *@example.com mailbox Trash savedbefore 2w

Restricting Access to Sendmail

By default, any local user can use the sendmail binary to submit outgoing emails. Now that your mail server is using virtual mailboxes, you might want to restrict access to the sendmail binary to trusted local users only, so a malicious user can’t use it to send a large volume of emails to damage your mail server’s reputation. Edit the Postfix main configuration file.

sudo nano /etc/postfix/main.cf

Add the following line to the end of this file, so only the root and www-data user can submit emails via sendmail. You can also add other usernames.

authorized_submit_users = root,www-data

Save and close the file. Then restart Postfix.

sudo systemctl restart postfix

Next Step

I hope this tutorial helped you install and use PostfixAdmin on CentOS 8/RHEL 8 to create virtual mailboxes. In part 4, I will show you how to set up SPF and DKIM with Postfix to improve email deliverability and in a future tutorial, I’m going to show you how to host multiple domains with PostfixAdmin.

If you want to access emails from a web browser, then I recommend Roundcube, which is a very popular and featured-rich open-source webmail client. As always, if you found this post useful,  subscribe to our free newsletter to get more tips and tricks. Take care 🙂

Rate this tutorial
[Total: 14 Average: 5]

52 Responses to “Part 3: PostfixAdmin – Create Virtual Mailboxes on CentOS 8/RHEL 8 Mail Server

  • Thanks Xiao, but already checked twice =( everything seems fine, i can create users, send and receive mail from outlook to internal and external domains, its just as if postfixadmin are not queueing the mail or have its mail functions disabled

    • Hey, i’ve figured out what was wrong:
      I looked on all logs, it was the log “messages” what pointed me in the right direction:

      “Apr 12 23:20:34 localhost setroubleshoot[16985]: SELinux is preventing /usr/sbin/php-fpm from name_connect access on the tcp_socket port 25.”

      Enabing this SELinux setting
      did the magic:
      setsebool -P httpd_can_sendmail=on

      Then it seems to work but just for the internal domain, mails going to external domains werent working, so i changed the SMTP server setting
      $CONF[‘smtp_server’] = ‘localhost’;
      on /var/www/postfixadmin/config.local.php

      To
      $CONF[‘smtp_server’] = ‘127.0.0.1’;

      And now mails going to external domains are relaying correctly too

  • Dear Xiao,
    Thank you for such a wonderful guide. In the first part I was able to send and receive email for a system user. In the second part, I receive and read emails but cannot send. Somehow, user fails during postfix authentication. I went over your instructions a couple of time but couln’t find the problem. Below is an excerpt from maillog. Why does it say SASL PLAIN failed? Is it normal? It seems to me that postfix is still looking for a SASL authentication.

    Apr 19 16:08:07 winsvr postfix/submission/smtpd[12252]: connect from unknown[45.87.212.182]
    Apr 19 16:08:08 winsvr postfix/submission/smtpd[12252]: Anonymous TLS connection established from unknown[45.87.212.182]: TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits)
    Apr 19 16:08:08 winsvr dovecot[11987]: auth: Debug: Loading modules from directory: /usr/lib64/dovecot/auth
    Apr 19 16:08:08 winsvr dovecot[11987]: auth: Debug: Module loaded: /usr/lib64/dovecot/auth/lib20_auth_var_expand_crypt.so
    Apr 19 16:08:08 winsvr dovecot[11987]: auth: Debug: Module loaded: /usr/lib64/dovecot/auth/libdriver_mysql.so
    Apr 19 16:08:08 winsvr dovecot[11987]: auth: Debug: Module loaded: /usr/lib64/dovecot/auth/libdriver_sqlite.so
    Apr 19 16:08:08 winsvr dovecot[11987]: auth: Debug: Read auth token secret from /var/run/dovecot/auth-token-secret.dat
    Apr 19 16:08:08 winsvr dovecot[11987]: auth: Debug: auth client connected (pid=0)
    Apr 19 16:08:08 winsvr dovecot[11987]: auth: Debug: client in: AUTH#0111#011PLAIN#011service=smtp#011nologin#011lip=172.104.150.134#011rip=45.87.212.182#011secured#011resp=AGhtZQBEZW5pem95YTIw (previous base64 data may contain sensitive data)
    Apr 19 16:08:08 winsvr dovecot[11987]: auth-worker(12257): Debug: Loading modules from directory: /usr/lib64/dovecot/auth
    Apr 19 16:08:08 winsvr dovecot[11987]: auth-worker(12257): Debug: Module loaded: /usr/lib64/dovecot/auth/lib20_auth_var_expand_crypt.so
    Apr 19 16:08:08 winsvr dovecot[11987]: auth-worker(12257): Debug: Module loaded: /usr/lib64/dovecot/auth/libdriver_mysql.so
    Apr 19 16:08:08 winsvr dovecot[11987]: auth-worker(12257): Debug: Module loaded: /usr/lib64/dovecot/auth/libdriver_sqlite.so
    Apr 19 16:08:08 winsvr dovecot[11987]: auth-worker(12257): Debug: sql(hme,45.87.212.182): query: SELECT username AS user,password FROM mailbox WHERE username = 'hme' AND active='1'
    Apr 19 16:08:08 winsvr dovecot[11987]: auth-worker(12257): sql(hme,45.87.212.182): unknown user
    Apr 19 16:08:10 winsvr postfix/submission/smtpd[12252]: warning: unknown[45.87.212.182]: SASL PLAIN authentication failed:
    Apr 19 16:08:10 winsvr dovecot[11987]: auth: Debug: client passdb out: FAIL#0111#011user=hme
    Apr 19 16:08:10 winsvr dovecot[11987]: auth: Debug: client in: AUTH#0112#011LOGIN#011service=smtp#011nologin#011lip=172.104.150.134#011rip=45.87.212.182#011secured
    Apr 19 16:08:14 winsvr dovecot[11987]: auth: Debug: client passdb out: CONT#0112#011VXNlcm5hbWU6
    Apr 19 16:08:14 winsvr dovecot[11987]: auth: Debug: client in: CONT#0112#011aG1l (previous base64 data may contain sensitive data)
    Apr 19 16:08:14 winsvr dovecot[11987]: auth: Debug: client passdb out: CONT#0112#011UGFzc3dvcmQ6
    Apr 19 16:08:14 winsvr dovecot[11987]: auth: Debug: client in: CONT#0112#011RGVuaXpveWEyMA== (previous base64 data may contain sensitive data)
    Apr 19 16:08:14 winsvr dovecot[11987]: auth-worker(12257): Debug: sql(hme,45.87.212.182): query: SELECT username AS user,password FROM mailbox WHERE username = 'hme' AND active='1'
    Apr 19 16:08:14 winsvr dovecot[11987]: auth-worker(12257): sql(hme,45.87.212.182): unknown user
    Apr 19 16:08:16 winsvr dovecot[11987]: auth: Debug: client passdb out: FAIL#0112#011user=hme
    Apr 19 16:08:16 winsvr postfix/submission/smtpd[12252]: warning: unknown[45.87.212.182]: SASL LOGIN authentication failed: UGFzc3dvcmQ6
    Apr 19 16:08:19 winsvr postfix/submission/smtpd[12252]: disconnect from unknown[45.87.212.182] ehlo=2 starttls=1 auth=0/2 quit=1 commands=4/6
    
  • Don’t worry. I found the problem. In the screenshot of thunderbird, you used user1. But, actually, I guess it should be [email protected].

    So, I authenticated as [email protected], all is fine now.

    Thank you for such a great guide.

  • Hello. Can I change the ‘postfixadmin’ name so instead of that it becomes, for example, webmail [dot] example [dot] com?

  • Dear Xiao,
    Thank you for such a wonderful guide, I have a question can we install both Roundcube as a web client and postfix admin as admin panel in the same server.

    and how can we use them to connect remotely to mail server

  • Thanks for putting this together, it works perfectly 🙂

  • Cyberian
    4 years ago

    Hi LinuxBabe, Even after completing all steps – especially step 11 – the “catch-all” feature is not working. What I would like to achieve is the following:

    All incoming emails such as – [email protected], [email protected], [email protected], [email protected] etc which are not already defined as alias – should be delivered to [email protected] (which should be the catch-all address) or another address designated as catch-all address.

    Currently I get the error “550: 5.1.1 : Recipient address rejected: User unknown in virtual mailbox table”

  • I found that my SELinux was disabled. I enabled it to permissive mode, and now I can see the warnings via Cockpit.
    Lots of “SELinux is preventing php-fpm from read access on the file xxxxxxx”

    Can anyone point me to an SELinux guide suitable for my situation?
    – CentOS 8 on a cloud VPS
    – production WordPress website running

    Is there a recipe of SELinux commands to permit all usual Apache+Wordpress+MariaDB stuff to work?

  • Volgraft
    4 years ago

    Hello Xiao. Thank you for the great tutorial.
    I successfully finished 1 and 2 tutorial and LAMP.
    But in this tutorial a have a problem:
    In Step 6, on my site – mail.example.com/setup.php, I see a blank list with one row “File not found.”
    – if I replace mail.example.com with my site IP, nothing change.
    – if I replace setup.php with non existent setup.txt it’s show 2 rows:
    Not Found
    The requested URL /setup.txt was not found on this server.
    Can you suggest any debug trick for this problem?

    p.s. I doble-check LAMP and Steps 1-5, by have only idea to try bellow command:
    chown apache:apache /var/www/postfixadmin -R

    • Volgraft
      4 years ago

      That was dumb.
      I replace mail.example.com with postfix.example.com in a file /etc/nginx/conf.d/postfixadmin.conf and create additional A record, wait several minutes and it works now)

  • Hello Xiao!
    Under your leadership, I went through Part 1 and Part 2. Everything works and letters are sent and come. But in Part 3: PostfixAdmin, step 3 command
    sudo chcon -t httpd_sys_rw_content_t / var / www / postfixadmin / templates_c / -R
    displays “chcon: can’t apply partial context to unlabeled file ‘/ var / www / postfixadmin / templates_c /'”.
    The directory “templates_c” is visible in the FileZilla Client, the owner of “root:root”. SELinux status: disabled.
    Please tell me what to do next.

    Thank!

    • Any luck figuring this out? I need to know as well.

    • try this berofe:

      sudo chcon -h system_u:object_r:bin_t:s0 /usr/share/nginx/postfixadmin/templates_c/

      It worked for me

  • If i want to set up a second mail server as backup, which steps do i have to go through? Is setting up postfix and dovecot enough? Guess not, because of the needed domain/user lookup in the postfixadmin part.

    So how do I set up a backup mail server with postfixadmin?

    All the best,
    Frank

  • Richard
    4 years ago

    First, thank you! This is the best guide I’ve found in a long time. I am trying to set up a new server to replace one that I’ve had running for nearly 20 years with Postfix, Dovecot, Postfixadmin, and Maia. On my old server, I’m using the postfix virtual delivery agent, but I want to use the Dovecot agent as you guide so I can use the mail sorting functions for Junk, etc.

    So far, I’m running into a problem that when I add a new domain/user in postfixadmin, and having it send the welcome email, the mail directories are not getting created and the email is bouncing.

    I’ve gone back over your guide several times and don’t see what I’m missing.

    Thanks!

    • Xiao Guoan (Admin)
      4 years ago

      No problem on my mail server. When I add a new domain and a new user, the mail_location directory (/var/vmail/domain.com/user/Maildir/) is automatically created.

      Please copy the error message in /var/log/maillog file and paste it here.

      You can also check the Dovecot log.

      sudo journalctl -eu dovecot
      • Richard Robinson
        3 years ago

        Dear Xiao Guoan — I’ve been trying to debug dovecot’s behavior in my environment, but so far without success. I confirmed that dovecot will create /var/vmail/domain.com/user/Maildir if that directory doesn’t exist. If I delete it, it will create it again when Thunderbird tries to connect. Incoming mail gets lodged in …./Maildir/new. Also, Thunderbird can SEND mail from one of the hosts inside my network, But that same Thunderbird can’t read the incoming mail from the mail hot, complaining in a pop-up that the SERVER reports an internal error. Journalctl gave me the following after I quit thunderbird:

        Jan 27 18:11:30 host1.dom.com dovecot[47928]: lmtp(83750): Connect from local
        Jan 27 18:11:30 host1.dom.com dovecot[47928]: lmtp([email protected]): Debug: auth-master: userdb lookup([email protected]): Started userdb lookup
        Jan 27 18:11:30 host1.dom.com dovecot[47928]: lmtp([email protected]): Debug: auth-master: conn unix:/var/run/dovecot/auth-userdb: Connecting
        Jan 27 18:11:30 host1.dom.com dovecot[47928]: lmtp([email protected]): Debug: auth-master: conn unix:/var/run/dovecot/auth-userdb: Client connected (fd=16)
        Jan 27 18:11:30 host1.dom.com dovecot[47928]: lmtp([email protected]): Debug: auth-master: userdb lookup([email protected]): auth USER input: [email protected] maildir=dom.com/user/ >
        Jan 27 18:11:30 host1.dom.com dovecot[47928]: lmtp([email protected]): Debug: auth-master: userdb lookup([email protected]): Finished userdb lookup ([email protected] maildir=goamca>
        Jan 27 18:11:30 host1.dom.com dovecot[47928]: lmtp([email protected]): Debug: lmtp-server: conn unix:pid=83749,uid=89 [1]: rcpt [email protected]: Added userdb setting: plugin/maildir=goamcan.c>
        Jan 27 18:11:30 host1.dom.com dovecot[47928]: lmtp(83750, [email protected]): Debug: lmtp-server: conn unix:pid=83749,uid=89 [1]: rcpt [email protected]: Effective uid=2000, gid=2000, home=/var/vmail/dom.com/user
        Jan 27 18:11:30 host1.dom.com dovecot[47928]: lmtp(83750, [email protected]): Debug: lmtp-server: conn unix:pid=83749,uid=89 [1]: rcpt [email protected]: Namespace inbox: type=private, prefix=, sep=, inbox=yes, hidden=no, li>
        Jan 27 18:11:30 host1.dom.com dovecot[47928]: lmtp(83750, [email protected]): Debug: lmtp-server: conn unix:pid=83749,uid=89 [1]: rcpt [email protected]: maildir++: root=/var/vmail/dom.com/user/Maildir, index=, index>
        Jan 27 18:11:30 host1.dom.com dovecot[47928]: lmtp([email protected]): Debug: lmtp-server: conn unix:pid=83749,uid=89 [1]: rcpt [email protected]: Mailbox INBOX: Mailbox opened because: lib-lda>
        Jan 27 18:11:30 host1.dom.com dovecot[47928]: lmtp([email protected]): lmtp-server: conn unix:pid=83749,uid=89 [1]: rcpt [email protected]: msgid=
        Jan 27 18:11:30 host1.dom.com dovecot[47928]: lmtp(83750): Disconnect from local: Client has quit the connection (state=READY)
        Jan 27 18:11:44 host1.dom.com dovecot[47928]: imap-login: Login: user=, method=PLAIN, rip=10.180.48.2, lip=69.71.183.164, mpid=83766, TLS, session=
        Jan 27 18:11:44 host1.dom.com dovecot[47928]: imap(user): Debug: Added userdb setting: plugin/auth_mech=PLAIN
        Jan 27 18:11:44 host1.dom.com dovecot[47928]: imap(user): Debug: Effective uid=1000, gid=1000, home=/home/user
        Jan 27 18:11:44 host1.dom.com dovecot[47928]: imap(user): Debug: Namespace inbox: type=private, prefix=, sep=, inbox=yes, hidden=no, list=yes, subscriptions=yes location=maildir:/var/vmail/dom.com/>
        Jan 27 18:11:44 host1.dom.com dovecot[47928]: imap(user): Debug: maildir++: root=/var/vmail/dom.com/user/Maildir, index=, indexpvt=, control=, inbox=/var/vmail/dom.com/user/Maildir, alt=
        Jan 27 18:11:44 host1.dom.com dovecot[47928]: imap(user): Error: stat(/var/vmail/dom.com/user/Maildir/subscriptions) failed: Permission denied
        Jan 27 18:11:44 host1.dom.com dovecot[47928]: imap(user): Error: stat(/var/vmail/dom.com/user/Maildir) failed: Permission denied (euid=1000(user) egid=1000(user) missing +x perm: /var/v>
        Jan 27 18:11:44 host1.dom.com dovecot[47928]: imap(user): Debug: Namespace : Using permissions from /var/vmail/dom.com/user/Maildir: mode=0700 gid=default
        Jan 27 18:11:44 host1.dom.com dovecot[47928]: imap(user): Error: mkdir(/var/vmail/dom.com/user/Maildir) failed: Permission denied (euid=1000(user) egid=1000(user) missing +x perm: /var/>
        Jan 27 18:11:44 host1.dom.com dovecot[47928]: imap(user): Error: mkdir(/var/vmail/dom.com/user/Maildir) failed: Permission denied (euid=1000(user) egid=1000(user) missing +x perm: /var/>
        Jan 27 18:11:44 host1.dom.com dovecot[47928]: imap(user): Error: opendir(/var/vmail/dom.com/user/Maildir) failed: Permission denied (euid=1000(user) egid=1000(user) missing +x perm: /va>
        Jan 27 18:11:44 host1.dom.com dovecot[47928]: imap(user): Error: mkdir(/var/vmail/dom.com/user/Maildir) failed: Permission denied (euid=1000(user) egid=1000(user) missing +x perm: /var/>
        Jan 27 18:11:44 host1.dom.com dovecot[47928]: imap(user): Debug: Mailbox INBOX: Mailbox opened because: SELECT
        Jan 27 18:11:44 host1.dom.com dovecot[47928]: imap(user): Error: stat(/var/vmail/dom.com/user/Maildir/tmp) failed: Permission denied (euid=1000(user) egid=1000(user) missing +x perm: /v> 
        Jan 27 18:11:53 host1.dom.com dovecot[47928]: imap(user): Logged out in=194 out=1149 deleted=0 expunged=0 trashed=0 hdr_count=0 hdr_bytes=0 body_count=0 body_bytes=0
        

        It looks to me as though imap is running as me, my username, instead of as vmail or one of the mail group. That seems counterintuitive.

  • Thanks for this great guide. Almost everything works on my side, but I am not able to use the Forgot Password Function (for users, not for admins) in postfixadmin. I created a postfix mail account with “Other e-mail” attribute specified (described as “Used if the password is forgotten”. Then I go to ../postfixadmin/public/users/password-recover.php and enter the mail adress of the postfix mail account. After that, a form is shown where I could enter login mail adress, “code sent by email/sms” and a new password. But I never receive a mail with the code.

    In config.inc.php, forgotten_user_password_reset is set to true (it says “…ith a code sent by email/SMS” – how can I choose mail?). sms_send_function is empty, as I would like to use mail. There is no error message in maillog or httpd logs. Do you have an idea what I may have missed?

  • László
    3 years ago

    Dear Xiao,

    Thank you for this very useful tutorial. I can build my e-mail server with little admin experience.
    Until now I could use all commands as you wrote, but in this part, I could find a small difference:

    “Next, we need to create a user named vmail with ID 2000 and a group with ID 2000.”

    sudo adduser vmail --system --uid 2000 --user-group --no-create-home

    This command created the vmail user with 2000 uid, and the vmail group but not with 2000 gid. It was created with gid=983 or something like this.
    I had to issue this command to achieve the 2000 gid:

    sudo groupmod -g 2000 vmai

    Regards
    László

    • Thanks, this saved my day!

    • Chris Nguyen
      1 year ago

      Dear László

      Thank you very much!
      I can log in to email but I can’t send emails out until I find your comment.

  • Hi Xiao,

    That’s one of the best step-by-step guide I’ve ever seen. Many thanks.

    I created the superadmin accound with the setup password in the setup.php page.

    Now in the login.php it says “Your email address or password is not correct.”

    I checked many times and I’m sure there is no misspelling. Even I started from the beginning of the guide a couple of times.

    Do you know what I could have done wrong?

    Thanks,

    • I found out that the table postfixadmin->admin is empty.

      • I’ve commented
        //$CONF[‘encrypt’] = ‘dovecot:BLF-CRYPT’; in config.local.php so it is using the default encryption $CONF[‘encrypt’] = ‘md5crypt’;

        Now, I can loging and use Postfix Admin.

        Is there any drawback using this solution?

  • Michael
    3 years ago

    Hi, I just followed this guide and hit a bit of a block on step 10.
    When I was setting up my superadmin account on the setup page, the section with the textboxes would vanish after I clicked “add Admin”.
    On the next step on the login.php page I would then get an error, telling me that my credentials were incorrect, when trying to log in.
    After some digging, I found this post on the postfixadmin github page. (https://github.com/postfixadmin/postfixadmin/issues/444)
    It seems to be an issue with the encryption that is chosen here (dovecot:BLF-CRYPT). I followed the suggestion and changed it to md5, which worked perfectly.
    If you do this, just watch out in step 13 when creating the dovecot-sql.conf.ext file to change the line:
    default_pass_scheme = BLF-CRYPT to
    default_pass_scheme = md5

    This is working well for me.

    Thank you for the guides. I truly appreciate them.

  • Hello Xiao,

    Thank you for a great tutorial. I’m new to linux so your tutorials are helping me tremendously. I do have a question regarding set 6 and step 8, enabling https.
    I was able to get a ssl cert for postfixadmin.mydomain.com but is did not automatically install it. Searching the web, my understanding is that the
    the config file for the virtual host should have:

    SSLEngine on
    SSLCertificateFile
    SSLCertificateKeyFile 
    

    pointing to the the actual files. Is my assumption correct. Would I need to extend the conf file to include the
    Your reply would be much appreciated.

    Thanks

    • Xiao Guoan (Admin)
      3 years ago

      Certbot did install the certificate by creating another file /etc/httpd/conf.d/postfixadmin-le-ssl.conf, which contains the SSL configurations.

  • Hi Guoan Gor, I completed Step.9 and want to login postfixadmin using superadmin account but prompt the error in page as below, please give some advices, thanks.
    invalid token (session timeout; refresh the page and try again?)

  • Hi Xiao,

    i am having problems with client ( outlook, thunderbird)
    it is not reading the emails

    on the mailserver i can see the folder “new” and e-mails are in there for that user.
    /var/vmail/domain/user/new

    send emails to gmail from client goes fine.

    i must have some setting wrong but cant figure it out.. don’t see any errors
    client connect

    Apr 23 13:05:42 mail dovecot[1526]: imap-login: Login: user=, method=PLAIN, rip=remoteip, lip=listenip, mpid=3595, TLS, session=
    Apr 23 13:06:13 mail dovecot[1526]: imap([email protected]): Logged out in=358 out=2656 deleted=0 expunged=0 trashed=0 hdr_count=0 hdr_bytes=0 body_count=0 body_bytes=0

    • fixed it.. finally found I forgot 1 line

      virtual_transport = lmtp:unix:private/dovecot-lmtp
  • ct tang
    3 years ago

    hi,
    this is related to the command:
    sudo sepolicy generate –init /usr/sbin/nginx

    when i did ./nginx.sh, it produced the following error:

    Problems processing filecon rules
    Failed post db handling
    /usr/sbin/semodule: Failed!

    i did a semanage fcontext -l | grep /usr/sbin/nginx and it produces:

    /usr/sbin/nginx regular file system_u:object_r:httpd_exec_t:s0

    how to solve this problem? i think it should be nginx_exec_t but i don’t know how to do it. thanks

    • ct tang
      3 years ago

      i tried to solve my problem with the followings:

      1. sudo grep dove /var/log/audit/audit.log | audit2allow -M mynginx
      2. sudo semodule -i mynginx.pp
      3. sudo semanage permissive -a httpd_t

      and it allows me to continue with postfixadmin installation. (remember to reload php-fpm)

  • Dredzik
    3 years ago

    ./nginx.sh
    Building and Loading Policy
    + make -f /usr/share/selinux/devel/Makefile nginx.pp
    make: ‘nginx.pp’ jest aktualne.
    + /usr/sbin/semodule -i nginx.pp
    Problems processing filecon rules
    Failed post db handling
    /usr/sbin/semodule: Failed!
    Any idea where it’s problem?

  • Dear XIAO,

    I have installed the postfixadmin. It work well vi Roundcube after disable selinux. Once selinux work, I can not received Email. Could u give me a favor? Thks.

    I debug via [sudo journalctl -eu dovecot]
    Error show below:

    11月 23 03:16:18 mail.ghjt.co.uk dovecot[1024]: imap([email protected]): Error: open(/var/vmail/ghjt.co.uk/franky/Maildir/dovecot.index.log) failed: Permission denied
    11月 23 03:16:18 mail.ghjt.co.uk dovecot[1024]: imap([email protected]): Error: file_dotlock_create(/var/vmail/ghjt.co.uk/franky/Maildir/dovecot-uidlist) failed: Perm
    11月 23 03:16:18 mail.ghjt.co.uk dovecot[1024]: imap([email protected]): Error: open(/var/vmail/ghjt.co.uk/franky/Maildir/dovecot-uidlist) failed: Permission denied
    11月 23 03:16:18 mail.ghjt.co.uk dovecot[1024]: imap([email protected]): Error: open(/var/vmail/ghjt.co.uk/franky/Maildir/dovecot.index.log) failed: Permission denied
    11月 23 03:16:18 mail.ghjt.co.uk dovecot[1024]: imap([email protected]): Error: file_dotlock_create(/var/vmail/ghjt.co.uk/franky/Maildir/dovecot-uidlist) failed: Perm
    11月 23 03:16:18 mail.ghjt.co.uk dovecot[1024]: imap([email protected]): Error: open(/var/vmail/ghjt.co.uk/franky/Maildir/dovecot-uidlist) failed: Permission denied
    11月 23 03:16:18 mail.ghjt.co.uk dovecot[1024]: imap([email protected]): Error: open(/var/vmail/ghjt.co.uk/franky/Maildir/dovecot.index.log) failed: Permission denied
    11月 23 03:16:18 mail.ghjt.co.uk dovecot[1024]: imap([email protected]): Error: file_dotlock_create(/var/vmail/ghjt.co.uk/franky/Maildir/dovecot-uidlist) failed: Perm
    11月 23 03:16:18 mail.ghjt.co.uk dovecot[1024]: imap([email protected]): Error: open(/var/vmail/ghjt.co.uk/franky/Maildir/dovecot-uidlist) failed: Permission denied
    11月 23 03:16:18 mail.ghjt.co.uk dovecot[1024]: imap([email protected]): Logged out in=108 out=853
    11月 23 03:16:31 mail.ghjt.co.uk dovecot[1024]: lmtp(16268): Connect from local
    11月 23 03:16:31 mail.ghjt.co.uk dovecot[1024]: auth: Debug: master in: USER        1        [email protected]        service=lmtp
    11月 23 03:16:31 mail.ghjt.co.uk dovecot[1024]: auth-worker(16098): Debug: sql([email protected]): SELECT maildir, 2000 AS uid, 2000 AS gid FROM mailbox WHERE usernam
    11月 23 03:16:31 mail.ghjt.co.uk dovecot[1024]: auth: Debug: userdb out: USER        1        [email protected]        [email protected]/        uid=2000     
    11月 23 03:16:31 mail.ghjt.co.uk dovecot[1024]: lmtp([email protected]): Error: open(/var/vmail/ghjt.co.uk/franky/Maildir/dovecot.index.log) failed: Permission denied
    11月 23 03:16:31 mail.ghjt.co.uk dovecot[1024]: lmtp([email protected]): Error: open(/var/vmail/ghjt.co.uk/franky/Maildir/dovecot-uidlist) failed: Permission denied
    11月 23 03:16:31 mail.ghjt.co.uk dovecot[1024]: lmtp([email protected]): Error: open(/var/vmail/ghjt.co.uk/franky/Maildir/tmp/1637637391.M874360P16268.mail.ghjt.co.uk
    11月 23 03:16:31 mail.ghjt.co.uk dovecot[1024]: lmtp([email protected]): msgid=: save failed to INBOX: open(/v
    11月 23 03:16:31 mail.ghjt.co.uk dovecot[1024]: lmtp(16268): Disconnect from local: Successful quit
    
    • Xiao Guoan (Admin)
      3 years ago

      Try this to make /var/vmail/ writable.

      sudo chcon -t mail_spool_t /var/vmail/ -R
  • Xiauo, these articles are excellent! Clear, concise, complete. I’ve completed all the steps and my virtual mail boxes are up & running with no issues. Wonderful!

    Thanks a ton Xiauo!!

  • David Hieber
    2 years ago

    I have been following your tutorials and they are quite excellent, thank you! However, I have been looking everywhere for the “Vacation Message” area of PostfixAdmin but cannot find anything in the SuperAdmin section or the User section of the web interface.

    My mail server is running excellent, got a 10/10 from the “test my email” site, and working on setting up Roundcube next. I guess I won’t really need the vacation-ooo response here, but curious as to where it might be?

    I would have liked to have a tutorial on maintaining Virtual Mailboxes. Presently I use Dovecot with IMAP but have real Unix accounts for the users. Having a resource on how to manage the VM’s would be most excellent.

    Many thanks again!

    • Xiao Guoan (Admin)
      2 years ago

      It seems the “Vacation” function in PostfixAdmin is removed in recent versions. I always use the “Vacation” function in Roundcube Webmail

  • sudo ./httpd.sh gives me the following error on apache and almalinux9.
    Any idea how to solve this? Thank you.

    Building and Loading Policy
    + make -f /usr/share/selinux/devel/Makefile httpd.pp
    Compiling targeted httpd module
    Creating targeted httpd.pp policy package
    rm tmp/httpd.mod tmp/httpd.mod.fc
    + /usr/sbin/semodule -i httpd.pp
    Re-declaration of type httpd_t
    Previous declaration of type at /var/lib/selinux/targeted/tmp/modules/400/httpd/cil:1
    Bad type declaration at /var/lib/selinux/targeted/tmp/modules/400/httpd/cil:1
    Failed to build AST
    /usr/sbin/semodule:  Failed!
    
  • 7k3pr0xy
    1 year ago

    I’m getting a below mentioned error. please help me!!!!

    Mariadb : 2022-12-24 11:02:33 21 [Warning] Access denied for user 'postfixadmin'@'localhost' (using password: YES)
    Postfix Maillog: mail dovecot[2902]: auth-worker(119002): Error: mysql(127.0.0.1): Connect failed to database (postfixadmin): Access denied for user 'postfixadmin'@'localhost' (using password: YES) - waiting for 25 seconds before retry
    

    but i’m configured “skip-grant-table” in mariadb configuration file. that time dovecot is working fine.i want permanent solution please help me. Sorry for my english!!!!!

    • Xiao Guoan (Admin)
      1 year ago

      It’s very likely that you enterred a wrong password in one of the .cf files unders /etc/postfix/sql/ directory, or your MariaDB server isn’t running.

  • Ataur Rahaman
    1 year ago

    I wanna configure this postfixadmin in my local server for practice. in this case Let’s Encrypt can not be possible to configure as you know. I wanna complete ssl step using self-signed ssl in centos 9. Is there anyone available to help me to overcome this problem. everything is going fine except https.
    I can easily access from my web browser to postfixadmin.mydomain.local. but when I generated self-signed ssl using following command:

    sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/pki/tls/private/apache-selfsigned.key -out /etc/pki/tls/certs/apache-selfsigned.crt
    

    then I cannot access this from my web browser. I don’t get any error in maillog or journalctl or systemctl status httpd.
    now what can i do for this?
    even I cannot remove self-signed ssl

  • Hello Sir,

    Thanks for article.
    But I am getting error, when I try to open http://domainname.com/setup.php
    502 Bad Gateway

    Could u please help. I am new on CentOS 7.

    Regards
    Dhanu

  • Hi, awesome guide, helped me out alot. I have two questions.

    1) I use FreeIPA for my identity management and have it integrated with to use SSO login with Nextcloud, is it possible to do that for the postfix/dovecot server while using postfix admin?

    2) I was looking at the Dovecot documentation to setup up shared mailboxes, or sharing mailboxes between users, is this still possible to do with postfix admin running?

  • Chris Nguyen
    12 months ago

    I have changed the mailbox path to a new one (/var/dobucket). However, the new emails are still being delivered to the old one (/var/vmail). Please help me fix it.

    In this file: /etc/postfix/main.cf

    virtual_mailbox_base = /var/dobucket
    

    In this file: /etc/dovecot/conf.d/10-mail.conf

    mail_home = /var/dobucket/%d/%n
    
    • Chris Nguyen
      12 months ago

      I have restarted the postfix + dovecot service but no changes. The new emails still go to the old maildir.

      sudo systemctl restart postfix
      
  • Very good tutorial.
    However, have the following issue;

     imap([email protected]): Error: Namespace '': No home directory for system user. Can't expand ~Maildir for mail root dir in: ~Maildir
    
    Jul 16 22:15:10 tld dovecot[11822]: imap([email protected]): Disconnected: Namespace '': No home directory for system user. Can't expand ~Maildir for mail root dir in: ~Maildir in=0 out=418 deleted=0 expunged=0 trashed=0 hdr_count=0 hdr_bytes=0 body_count=0 body_bytes=0
    

    I’ve gone through every file – at least twice now ensuring all details are correct
    Even changed the group of vmail to 2000 manually as it want 967, and changed the group of /var/vmail to vmail instead of 967.

  • I have used your email configuration very successfully for a number of years using CentOS 8 Stream.

    CentOS 8, however, is now end of life so I am setting up a new Stream 9 server and there are some issues, the major one beginning with the custom SELinux configuration. As I figure it out I will provide information if you want it, or perhaps if you have a Stream 9 configuration working you could point me to it ?

    Thanks for the tutorials, they are very well presented.

Leave a Comment

  • Comments with links are moderated by admin before published.
  • Your email address will not be published.
  • Use <pre> ... </pre> HTML tag to quote the output from your terminal/console.
  • Please use the community (https://community.linuxbabe.com) for questions unrelated to this article.
  • I don't have time to answer every question. Making a donation would incentivize me to spend more time answering questions.

The maximum upload file size: 2 MB. You can upload: image. Links to YouTube, Facebook, Twitter and other services inserted in the comment text will be automatically embedded. Drop file here