Configuration, Database Upgrades, PHP-FPM, Dovecot and SSL Troubleshooting
Migrating a production mail server is often easier when the migration is divided into layers. Postfix, Dovecot, the database, certificates and mail storage can be moved first, while administrative components such as PostfixAdmin are deployed later.
This approach works well because PostfixAdmin is primarily a web-based administration interface for managing virtual domains, mailboxes, aliases and related data stored in the mail server database. It can therefore be installed after the underlying mail infrastructure has already been migrated.
However, adding PostfixAdmin to an existing migrated environment can expose several configuration issues that were previously invisible. Typical examples include PHP-FPM permissions, database schema mismatches, password hashing compatibility and Dovecot being unable to access TLS certificates.
This article presents a practical approach to deploying PostfixAdmin on an existing Linux mail server and troubleshooting these issues without rebuilding the mail infrastructure.
Architecture
A typical installation may look like this:
Internet
|
Nginx
|
PHP-FPM
|
PostfixAdmin
|
MySQL / MariaDB
|
+----------------+
| |
Postfix Dovecot
| |
SMTP IMAP / POP3
PostfixAdmin does not replace Postfix or Dovecot. Instead, it provides an administrative interface to the database used by the mail environment.
PostfixAdmin supports MySQL/MariaDB, PostgreSQL and SQLite database backends and integrates with Postfix and IMAP/POP3 servers such as Dovecot.
This separation means that a server can already be successfully delivering and receiving mail before the PostfixAdmin web interface is installed.
1. Install the Web Stack
On an Enterprise Linux 9 compatible distribution, an Nginx/PHP installation can typically be prepared with packages such as:
dnf install -y \
nginx \
php \
php-fpm \
php-mbstring \
php-mysqlnd \
php-intl \
php-xml \
php-opcache \
php-gd \
php-ldap
Depending on the distribution repositories, php-imap might not be available. Its absence does not necessarily prevent the basic PostfixAdmin interface from operating.
Enable the services:
systemctl enable --now nginx
systemctl enable --now php-fpm
Verify them:
systemctl status nginx
systemctl status php-fpm
2. Deploy PostfixAdmin
Install PostfixAdmin under a dedicated directory such as:
/var/www/postfixadmin
The application should not normally be configured by modifying its main config.inc.php.
Instead, use:
/var/www/postfixadmin/config.local.php
PostfixAdmin’s own configuration documentation explicitly recommends overriding settings through config.local.php, which makes future upgrades considerably easier.
A minimal database configuration could resemble:
<?php
$CONF['configured'] = true;
$CONF['database_type'] = 'mysqli';
$CONF['database_host'] = 'localhost';
$CONF['database_user'] = 'postfix';
$CONF['database_password'] = 'REPLACE_WITH_PASSWORD';
$CONF['database_name'] = 'postfix';
$CONF['encrypt'] = 'dovecot:SHA512-CRYPT';
Never publish the actual database password in documentation, source control or public repositories.
PostfixAdmin supports mysqli for modern MySQL and MariaDB installations.
3. Configure Nginx
A simple Nginx virtual host can use PostfixAdmin’s public directory as its document root:
server {
listen 80;
server_name mailadmin.example.net;
root /var/www/postfixadmin/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_param SCRIPT_FILENAME
$document_root$fastcgi_script_name;
}
}
Validate the configuration:
nginx -t
Then reload Nginx:
systemctl reload nginx
In production, the administrative interface should normally be exposed over HTTPS rather than plain HTTP.
4. The templates_c Permission Problem
One common error during deployment is:
ERROR: the templates_c directory doesn't exist
or isn't writeable for the webserver
The important detail is that Nginx is not necessarily the process that needs write access.
Nginx forwards PHP requests to PHP-FPM, and PHP-FPM may execute them using a completely different Unix account.
Check the PHP-FPM worker identity:
grep -E '^user|^group' /etc/php-fpm.d/www.conf
For example:
user = apache
group = apache
In this situation, giving write permission only to the nginx user does not solve the problem.
Create the required directory:
mkdir -p /var/www/postfixadmin/templates_c
Then assign it to the PHP-FPM account:
chown -R apache:apache /var/www/postfixadmin/templates_c
chmod 755 /var/www/postfixadmin/templates_c
Test the permission using the same identity:
sudo -u apache touch \
/var/www/postfixadmin/templates_c/write-test
If the command succeeds, Unix permissions are sufficient.
5. Account for SELinux
On RHEL-family systems, correct Unix permissions do not necessarily mean that PHP is allowed to write to a directory.
SELinux can independently deny access.
Check its state:
getenforce
If SELinux is enforcing, assign an appropriate writable web-content context:
semanage fcontext -a \
-t httpd_sys_rw_content_t \
"/var/www/postfixadmin/templates_c(/.*)?"
Apply it:
restorecon -Rv /var/www/postfixadmin/templates_c
Avoid solving SELinux problems by permanently disabling SELinux or using overly permissive directory modes such as 777.
6. Test Database Connectivity Separately
Another common failure is:
SQLSTATE[HY000] [1045]
Access denied for user
This indicates that PHP and PostfixAdmin have progressed far enough to attempt a database connection.
Test the same credentials independently:
mysql -u postfix -p -h localhost
Then select the database:
USE postfix;
If manual authentication fails, fix the MySQL account or password rather than troubleshooting PostfixAdmin.
You can inspect the relevant local configuration without displaying the password unnecessarily:
grep -E \
"database_type|database_host|database_user|database_name" \
/var/www/postfixadmin/config.local.php
7. Upgrade a Migrated PostfixAdmin Database
A database copied from an older server may contain an older PostfixAdmin schema.
The new application can therefore report an error similar to:
The PostfixAdmin database layout is outdated.
Please run setup.php to upgrade the database.
This is expected when the application version on the new server is newer than the version that originally managed the database.
Before upgrading, create a database backup:
mysqldump postfix > postfix-before-postfixadmin-upgrade.sql
Preferably compress it:
mysqldump postfix | gzip \
> postfix-before-postfixadmin-upgrade.sql.gz
The PostfixAdmin setup process can then perform the required database migrations.
After successful configuration, make sure:
$CONF['configured'] = true;
PostfixAdmin explicitly requires this flag after the required local settings have been configured.
8. Migrated Administrator Passwords
A particularly interesting issue can occur with administrator accounts migrated from an older installation.
For example, an old admin table may contain hashes resembling:
$1$xxxxxxxx$xxxxxxxxxxxxxxxxxxxxxx
while the new installation is configured with:
$CONF['encrypt'] = 'dovecot:SHA512-CRYPT';
A SHA512-CRYPT password generated by Dovecot typically resembles:
{SHA512-CRYPT}$6$SALT$HASH
Dovecot documents SHA512-CRYPT as one of its supported password schemes, with the salt included in the resulting crypt string.
A new hash can be generated with:
doveadm pw -s SHA512-CRYPT
Dovecot then prompts for the new password and returns the encoded value.
The database record can subsequently be updated.
For example:
UPDATE admin
SET password = '{SHA512-CRYPT}$6$REDACTED'
WHERE username = 'administrator@example.net';
The administrator should also be active:
SELECT username, superadmin, active
FROM admin;
Never include real password hashes from production systems in public troubleshooting articles.
9. When a Correct Password Still Does Not Work
A password reset can appear completely correct and still result in failed authentication.
At this point, application logs become much more useful than repeatedly changing passwords.
For PHP-FPM:
tail -f /var/log/php-fpm/www-error.log
A particularly revealing error is:
Failed to read password from /usr/bin/doveadm pw
doveconf: Fatal:
ssl_cert: Can't open file ...
Permission denied
This changes the diagnosis completely.
The password itself is not necessarily wrong.
PostfixAdmin’s dovecot:* password method executes doveadm to generate or verify the password. PostfixAdmin’s current configuration documentation explicitly notes that Dovecot-based hashing depends on the doveadm binary and suitable permissions for Dovecot’s configuration.
Therefore the real dependency chain becomes:
PostfixAdmin
|
v
PHP-FPM
|
v
doveadm pw
|
v
Dovecot configuration
|
v
TLS certificate configuration
A failure at the bottom of this chain can manifest itself as a PostfixAdmin login failure.
10. The Let’s Encrypt Symlink Trap
Let’s Encrypt normally stores active certificates under:
/etc/letsencrypt/live/example.net/
but these are usually symbolic links.
For example:
fullchain.pem
-> ../../archive/example.net/fullchain16.pem
privkey.pem
-> ../../archive/example.net/privkey16.pem
The actual files are stored under:
/etc/letsencrypt/archive/example.net/
This matters enormously when diagnosing permission problems.
Inspect both:
ls -l /etc/letsencrypt/live/example.net
and:
ls -l /etc/letsencrypt/archive/example.net
Also inspect the complete path:
namei -l /etc/letsencrypt/live/example.net/fullchain.pem
namei is particularly useful because it shows every directory and symbolic-link component involved in reaching the real file.
11. Don’t Make the TLS Private Key Broadly Readable
A tempting response to a certificate permission problem is:
chmod 644 privkey.pem
Do not do this.
Private keys should remain tightly protected.
Dovecot’s documentation recommends restrictive permissions for the private key and notes that the normal Dovecot server reads its TLS certificate/key while it still has root privileges.
Consequently, a permission failure specifically from:
/usr/bin/doveadm pw
executed by a web-service account is a different situation from the normal Dovecot daemon reading its key at startup.
That distinction is important.
12. A Better Solution: Prevent doveadm pw From Reading Dovecot Configuration
For newer Dovecot versions, there is an especially useful solution.
Dovecot supports:
doveadm -O pw
The -O option tells doveadm not to read the Dovecot configuration files. Dovecot specifically documents this as useful when generating or verifying passwords without requiring the complete Dovecot configuration.
Current PostfixAdmin configuration documentation also calls this out:
$CONF['dovecotpw'] = "/usr/bin/doveadm -O pw";
and notes that this avoids a common configuration-permission problem on compatible Dovecot versions.
This can be significantly better than granting the PHP-FPM account access to Let’s Encrypt private keys merely so that it can calculate password hashes.
First determine the installed Dovecot version:
dovecot --version
Then test:
sudo -u apache /usr/bin/doveadm -O pw -s SHA512-CRYPT
If supported and successful, configure PostfixAdmin:
$CONF['encrypt'] = 'dovecot:SHA512-CRYPT';
$CONF['dovecotpw'] = '/usr/bin/doveadm -O pw';
Restart PHP-FPM:
systemctl restart php-fpm
This architecture is preferable because password hashing does not inherently require access to the mail server’s TLS private key.
13. Test Each Layer Independently
One of the most useful lessons from this type of migration is to avoid testing everything through the browser.
Test individual components instead.
PHP-FPM identity:
grep -E '^user|^group' /etc/php-fpm.d/www.conf
Database:
mysql -u postfix -p -h localhost postfix
Dovecot configuration:
doveconf -n
Password generation:
doveadm pw -s SHA512-CRYPT
Or, where supported:
doveadm -O pw -s SHA512-CRYPT
Test as the PHP-FPM user as well:
sudo -u apache \
/usr/bin/doveadm -O pw -s SHA512-CRYPT
Nginx:
nginx -t
SELinux denials:
ausearch -m AVC -ts recent
Services:
systemctl status nginx
systemctl status php-fpm
systemctl status postfix
systemctl status dovecot
This layered approach makes troubleshooting much faster.
14. Security Considerations
An administrative interface for a mail system deserves additional protection.
Use HTTPS for PostfixAdmin and avoid exposing an administrative interface over unencrypted HTTP.
Restrict access where practical using a firewall, VPN, reverse proxy or trusted administrative network.
Keep config.local.php protected because it normally contains database credentials.
Do not give the PHP-FPM account unnecessary access to mail storage, Dovecot private keys or Let’s Encrypt private keys.
Use strong administrator passwords and modern password hashing schemes.
PostfixAdmin currently supports several hashing approaches, including modern options and Dovecot-backed password generation. Its own configuration guidance recommends using config.local.php for overrides rather than changing the distributed configuration file.
Always back up the mail database before allowing a newer PostfixAdmin release to upgrade its schema.
15. Troubleshooting Flow
A useful diagnostic sequence is:
Does Nginx respond?
|
v
Does PHP execute?
|
v
Can PHP-FPM write templates_c?
|
v
Can PostfixAdmin connect to MySQL?
|
v
Is the PostfixAdmin DB schema current?
|
v
Can the admin account be retrieved?
|
v
Can the configured password mechanism run?
|
v
Can doveadm execute as the PHP-FPM user?
|
v
Does doveadm unnecessarily load Dovecot TLS config?
|
v
Login succeeds
Following this sequence avoids changing unrelated parts of a working mail server.
Conclusion
Deploying PostfixAdmin after migrating a Postfix/Dovecot mail server is entirely practical because the web interface is largely independent of the initial mail-server migration.
The more difficult part is understanding the dependencies that become visible when the web application is introduced.
A templates_c error may actually be caused by the PHP-FPM worker identity rather than Nginx. A database error may indicate credentials or an outdated schema. A failed administrator login may not be a password problem at all: with Dovecot-backed hashing, PostfixAdmin may invoke doveadm, which in turn may attempt to parse the complete Dovecot configuration and encounter inaccessible TLS certificates.
The most important troubleshooting principle is therefore to work from the bottom up and test each component independently.
For compatible Dovecot releases, using:
$CONF['dovecotpw'] = '/usr/bin/doveadm -O pw';
is particularly worth considering because it allows password operations without loading unrelated Dovecot configuration, avoiding the dangerous alternative of granting a web-service account access to TLS private keys.
A successful migration is not simply one where the PostfixAdmin login page loads. It is one where the administrative interface, database, password hashing, Postfix, Dovecot, PHP-FPM, TLS configuration and operating-system security controls work together without unnecessarily weakening the security of the mail server.

