3
-----------
PostfixAdmin- Centos 8
PostfixAdmin Features
manage mailboxes, virtual domains and aliases
vacation/out-of-office messages
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
display used quota
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.
PostfixAdmin is written in PHP and requires a database (MySQL/MariaDB, PostgreSQL or SQLite). This article will use MariaDB database. You also need to run Apache or Nginx web server. So basically we are going to need a LAMP or LEMP stack.
If you prefer to use Apache web server, then set up a LAMP stack.
If you prefer to use Nginx web server, then set up a LEMP stack.
Once the above requirements are met, let’s install and configure PostfixAdmin.
Step 1: 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.2.4 with the new version number.
dnf -y install wget
wget https://github.com/postfixadmin/postfixadmin/archive/postfixadmin-3.2.4.tar.gz
Once downloaded, extract the archive.
If you are using Apache, then extract it to /var/www/html directory and rename it to postfixadmin.
dnf install tar
tar xvf postfixadmin-3.2.4.tar.gz -C /var/www/html/
mv /var/www/html/postfixadmin-postfixadmin-3.2.4 /var/www/html/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.2.4.tar.gz -C /usr/share/nginx/
sudo mv /usr/share/nginx/postfixadmin-postfixadmin-3.2.4 /usr/share/nginx/postfixadmin
Step 2: 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
mkdir /var/www/html/postfixadmin/templates_c
setfacl -R -m u:apache:rwx /var/www/html/postfixadmin/templates_c/
chcon -t httpd_sys_rw_content_t /var/www/html/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
Step 3: 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 4: 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
cp /var/www/html/postfixadmin/config.inc.php /var/www/html/postfixadmin/config.inc.php.ORG
vi /var/www/html/postfixadmin/config.inc.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 3. Line: 101-105
<?php
$CONF['configured'] = true; # line 42: change
$CONF['database_type'] = 'mysqli'; # Line: 101-105
$CONF['database_host'] = 'localhost';
$CONF['database_port'] = '3306';
$CONF['database_user'] = 'postfixadmin';
$CONF['database_password'] = 'postfixadmin_password';
$CONF['database_name'] = 'postfixadmin';
Save and close the file.
Step 5: Create Apache Virtual Host or Nginx Config File for PostfixAdmin
Apache
If you use Apache web server, create a virtual host for PostfixAdmin.
# vi /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/html/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/html/postfixadmin/public/>
Options FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
allow from all
</Directory>
</VirtualHost>
[root@mail ~]# vi /etc/httpd/conf.d/postfixadmin.conf
Alias /postfixadmin /var/www/html/postfixadmin/public
<Directory /var/www/html/postfixadmin/public/>
Order Deny,Allow
Deny from all
# IP address you permit to access
Allow from all
</Directory>
---------------------------
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 6: 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-imap php-mbstring php-mysqlnd php-json php-curl php-zip php-xml php-bz2 php-intl php-gmp
Restart Apache or Nginx.
sudo systemctl restart httpd
sudo systemctl restart nginx
Step 7: 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 you@example.com -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 you@example.com -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.
Step 8: Finish the Installation in Web Browser
Go to postfixadmin.example.com/setup.php to run the web-based setup wizard. First, it will check if all dependencies are installed.
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.
Once all requirements are satisfied, you can create a setup password for PostfixAdmin.
After creating the password hash, PostfixAdmin will display a line like below.
# vi /var/www/html/postfixadmin/config.inc.php
Line 30: $CONF['setup_password'] = 'de177e35654bde7195edb0a8d907279e:5304910a9d847e525824101bfe3c799bef0f0aa8';
---OR---- Creat Admin Passwd-command:
bash /var/www/html/postfixadmin/scripts/postfixadmin-cli admin add admin@your_domain_name.com --password mysql_password --password2 mysql_password --superadmin 1 --active 1
Apache
vim /var/www/html/postfixadmin/config.inc.php
$CONF['setup_password'] = 'hO03pn9kxIo6ZBokLaiVpSddTRczYD35740aa:rk9luqxtr+s32lwqWIHd650acf3ada94e';
You need to open the config.inc.php file.
Nginx
sudo nano /usr/share/nginx/postfixadmin/config.local.php
Add the above line at the end of the file. After saving the file, you need to enter the setup password again and create the admin account.
After that, you can log into PostfixAdmin at postfixadmin.example.com/login.php.
Step 9: Checking Tables in the Database
The PostfixAdmin setup process populates the postfixadmin database with some default tables. It’s helpful for us to know the names and structure of the tables. Log in to MySQL/MariaDB console.
mysql -u root -p
Select the postfixadmin database.
USE postfixadmin;
List all tables in this database.
SHOW TABLES;
Output:
+------------------------+
| Tables_in_postfixadmin |
+------------------------+
| admin |
| alias |
| alias_domain |
| config |
| domain |
| domain_admins |
| fetchmail |
| log |
| mailbox |
| quota |
| quota2 |
| vacation |
| vacation_notification |
+------------------------+
13 rows in set (0.001 sec)
The 3 most important tables are:
domain: contains information on the domains that are using your mail server to send and receive email.
mailbox: contains information on every email address, including hashed password and the location of mail files.
alias: contains the alias of each email address.
If you are interested, you can check what columns each table contains. For example, the following command will show us the columns in the domain table.
DESCRIBE domain;
Output:
+-------------+--------------+------+-----+---------------------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------------------+-------+
| domain | varchar(255) | NO | PRI | NULL | |
| description | varchar(255) | NO | | NULL | |
| aliases | int(10) | NO | | 0 | |
| mailboxes | int(10) | NO | | 0 | |
| maxquota | bigint(20) | NO | | 0 | |
| quota | bigint(20) | NO | | 0 | |
| transport | varchar(255) | NO | | NULL | |
| backupmx | tinyint(1) | NO | | 0 | |
| created | datetime | NO | | 2000-01-01 00:00:00 | |
| modified | datetime | NO | | 2000-01-01 00:00:00 | |
| active | tinyint(1) | NO | | 1 | |
+-------------+--------------+------+-----+---------------------+-------+
Log out of MySQL/MariaDB console.
EXIT;
Step 10: 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
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 3.
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 11: 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, it’s a good practice to store emails under /var/vmail/example.com/username/, so change the mail_location to:
mail_location = maildir:/var/vmail/%d/%n
It’s recommended 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
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
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 3.
driver = mysql
connect = host=localhost dbname=postfixadmin user=postfixadmin password=postfixadmin_password
default_pass_scheme = MD5-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 MD5-CRYPT hash from the password entered by the user, then compare it with the password hash stored in the database.
Step 12: 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.
Then click Virtual List tab and select Add Mailbox to add a new email address for your domain.
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.
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.
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
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.example.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 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
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
Step 1: Download PostfixAdmin on CentOS/RHEL 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.2.3 with the new version number.
sudo dnf install wget wget https://github.com/postfixadmin/postfixadmin/archive/postfixadmin-3.2.3.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 tar xvf postfixadmin-3.2.3.tar.gz -C /var/www/ sudo mv /var/www/postfixadmin-postfixadmin-3.2.3 /var/www/postfixadmin
If you are using Nginx, extract it to /usr/share/nginx/ directory and rename it to postfixadmin.
sudo tar xvf postfixadmin-3.2.3.tar.gz -C /usr/share/nginx/ sudo mv /usr/share/nginx/postfixadmin-postfixadmin-3.2.3 /usr/share/nginx/postfixadmin
Step 2: 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.
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 3: Configure PostfixAdmin
The default PostfixAdmin configuration file is config.inc.php. We need to create a config.local.php file and add custom configurations.
sudo nano /var/www/postfixadmin/config.local.php
or
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 2.
<?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';
Save and close the file.
Step 4: 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; 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 5: 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 php-imap php-mbstring php-mysqlnd php-json php-curl php-zip php-xml php-bz2 php-intl php-gmp
Restart Apache or Nginx.
sudo systemctl restart httpd sudo systemctl restart nginx
Step 6: 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 /usr/local/bin/certbot --apache --agree-tos --redirect --hsts --staple-ocsp --email you@example.com -d postfixadmin.example.com
If you use Nginx, run the following command to obtain and install TLS certificate.
sudo /usr/local/bin/certbot --nginx --agree-tos --redirect --hsts --staple-ocsp --email you@example.com -d postfixadmin.example.com
Where
--nginx: Use the nginx plugin.
--apache: Use the Apache 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.
The certificate should now be obtained and automatically installed, which is indicated by the messages below.
Step 7: Setting Up Permissions
PostfixAdmin requires a templates_c directory, so create it.
sudo mkdir /var/www/postfixadmin/templates_c
or
sudo mkdir /usr/share/nginx/postfixadmin/templates_c
The web server needs to have read and write access to this directory.
sudo setfacl -R -m u:apache:rwx /var/www/postfixadmin/templates_c/
or
sudo setfacl -R -m u:nginx:rwx /usr/share/nginx/postfixadmin/templates_c/
We also need to change the SELinux context to make it writable.
sudo chcon -t httpd_sys_rw_content_t /var/www/postfixadmin/templates_c/ -R
or
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
Step 7: Finish the Installation in Web Browser
Go to postfixadmin.example.com/setup.php to run the web-based setup wizard. First, it will check if all dependencies are installed.
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. Once all requirements are satisfied, you can create a setup password for PostfixAdmin.
After creating the password hash, PostfixAdmin will display a line like below.
$CONF['setup_password'] = 'hO03pn9kxIo6ZBokLaiVpSddTRczYD35740aa:rk9luqxtr+s32lwqWIHd650acf3ada94e';
You need to open the config.local.php file and add this line at the end of the file. Next, create the admin account.
After that, you can log into PostfixAdmin at postfixadmin.example.com/login.php.
Step 8: Checking Tables in the Database
The PostfixAdmin setup process populates the postfixadmin database with some default tables. It’s helpful for us to know the names and structure of the tables. Log in to MySQL/MariaDB console.
mysql -u root -p
Select the postfixadmin database.
USE postfixadmin;
List all tables in this database.
SHOW TABLES;
Output:
+------------------------+ | Tables_in_postfixadmin | +------------------------+ | admin | | alias | | alias_domain | | config | | domain | | domain_admins | | fetchmail | | log | | mailbox | | quota | | quota2 | | vacation | | vacation_notification | +------------------------+ 13 rows in set (0.001 sec)
The 3 most important tables are:
domain: contains information on the domains that are using your mail server to send and receive email.
mailbox: contains information on every email address, including hashed password and the location of mail files.
alias: contains the alias of each email address.
If you are interested, you can check what columns each table contains. For example, the following command will show us the columns in the domain table.
DESCRIBE domain;
Output:
+-------------+--------------+------+-----+---------------------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------+--------------+------+-----+---------------------+-------+ | domain | varchar(255) | NO | PRI | NULL | | | description | varchar(255) | NO | | NULL | | | aliases | int(10) | NO | | 0 | | | mailboxes | int(10) | NO | | 0 | | | maxquota | bigint(20) | NO | | 0 | | | quota | bigint(20) | NO | | 0 | | | transport | varchar(255) | NO | | NULL | | | backupmx | tinyint(1) | NO | | 0 | | | created | datetime | NO | | 2000-01-01 00:00:00 | | | modified | datetime | NO | | 2000-01-01 00:00:00 | | | active | tinyint(1) | NO | | 1 | | +-------------+--------------+------+-----+---------------------+-------+
Log out of MySQL/MariaDB console.
EXIT;
Step 9: 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.
If you are going to set quotas for mailboxes, you need to also add the following lines to the file.
virtual_create_maildirsize = yes virtual_mailbox_extended = yes virtual_mailbox_limit_maps = mysql:/etc/postfix/sql/mysql_virtual_mailbox_limit_maps.cf virtual_mailbox_limit_override = yes virtual_maildir_limit_message = Sorry, the user's maildir has overdrawn his diskspace quota, please try again later. virtual_overquota_bounce = yes
We want to use dovecot to deliver incoming emails to the virtual users’ message store, so add the following line.
virtual_transport = lmtp:unix:private/dovecot-lmtp
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 2.
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'
If you added quota support, then create the mysql_virtual_mailbox_limit_maps.cf file.
sudo nano /etc/postfix/sql/mysql_virtual_mailbox_limit_maps.cf
Add the following content.
user = postfixadmin password = postfixadmin_password hosts = localhost dbname = postfixadmin query = SELECT quota FROM mailbox WHERE username='%s' AND 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/
Now let’s open the Postfix main configuration file again.
sudo nano /etc/postfix/main.cf
Find the mydestination parameter, which contains a list of domain names that will receive emails delivered to local Unix accounts. Since we are going to use virtual mailbox, so we need to remove the apex domain name from the list. My apex domain name is linuxbabe.com, so I removed it from the mydestination parameter.
mydestination = $myhostname, mail.linuxbabe.com, localhost.linuxbabe.com, localhost
Then 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 --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 10: 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
Change the mail_location to:
mail_location = maildir:/var/vmail/%d/%n
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
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
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 2.
driver = mysql connect = host=localhost dbname=postfixadmin user=postfixadmin password=postfixadmin_password default_pass_scheme = MD5-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
Restart Dovecot.
sudo systemctl restart dovecot
When a user tries to log in, Dovecot would generate an MD5-CRYPT hash from the password entered by the user, then compare it with the password hash stored in the database.
Step 11: 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.
Then click Virtual List tab and select Add Mailbox to add a new email address for your domain.
Next, you can open your desktop email client such as Mozilla Thunderbird and add a mail account.
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.
You should now be able to connect to your own email server and also send and receive emails with your desktop email client!
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
-----------