- Details
- Written by: Stanko Milosev
- Category: Ubuntu
- Hits: 22
This guide explains how to install Roundcube manually on Ubuntu 26.04 with Nginx, PHP 8.5, MariaDB, Postfix, Dovecot, and PostfixAdmin.
Because the packaged Roundcube version may not work correctly with PHP 8.5, this guide installs Roundcube manually from the official release archive.
1. Download and Extract Roundcube
Change to the web directory:
cd /var/www
Download Roundcube 1.6.12:
sudo wget https://github.com/roundcube/roundcubemail/releases/download/1.6.12/roundcubemail-1.6.12-complete.tar.gz
Extract the archive:
sudo tar -xzf roundcubemail-1.6.12-complete.tar.gz
Rename the extracted directory:
sudo mv roundcubemail-1.6.12 roundcube
2. Install Composer
Check whether Composer is already installed:
composer --version
If Composer is not installed, install it:
sudo apt update sudo apt install -y composer
3. Install Roundcube Dependencies
Change to the Roundcube directory:
cd /var/www/roundcube
Install the required Composer dependencies:
composer install --no-dev
4. Set File Permissions
Set the Roundcube files to be owned by the Nginx and PHP user:
sudo chown -R www-data:www-data /var/www/roundcube
Set directory permissions:
sudo find /var/www/roundcube -type d -exec chmod 755 {} \;
Set file permissions:
sudo find /var/www/roundcube -type f -exec chmod 644 {} \;
5. Create the Roundcube Web Link
First, check whether the Roundcube installation contains a public_html directory:
ls -l /var/www/roundcube
If the public_html directory exists, create this symbolic link:
sudo ln -s /var/www/roundcube/public_html /var/www/html/roundcube
If public_html does not exist, link the complete Roundcube directory instead:
sudo rm -f /var/www/html/roundcube sudo ln -s /var/www/roundcube /var/www/html/roundcube
Restart Nginx and PHP-FPM:
sudo systemctl restart nginx sudo systemctl restart php8.5-fpm
6. Create the Roundcube Configuration File
Check whether the configuration file already exists:
ls -l /var/www/roundcube/config/config.inc.php
If the file does not exist, create it from the sample configuration:
sudo cp /var/www/roundcube/config/config.inc.php.sample \ /var/www/roundcube/config/config.inc.php
If an older Roundcube configuration still exists under /etc/roundcube, you can copy it:
sudo cp /etc/roundcube/config.inc.php /var/www/roundcube/config/
After copying an older configuration, review it carefully because some settings may not match the manually installed Roundcube version.
7. Configure the Roundcube Database
First, check whether the Roundcube database already exists:
sudo mariadb -e "SHOW DATABASES LIKE 'roundcubemail';"
If the command returns roundcubemail, the database already exists.
Set a New Database Password
Open MariaDB:
sudo mariadb
Set a new password for the Roundcube database user:
ALTER USER 'roundcube'@'localhost' IDENTIFIED BY 'YOUR_STRONG_ROUNDCUBE_DB_PASSWORD'; FLUSH PRIVILEGES; EXIT;
Replace YOUR_STRONG_ROUNDCUBE_DB_PASSWORD with a strong password.
Update the Roundcube Database Connection
Use the same password that you assigned to the MariaDB user.
9. Create the Roundcube Database if It Does Not Exist
If this command returns nothing:
sudo mariadb -e "SHOW DATABASES LIKE 'roundcubemail';"
create the database manually.
Open MariaDB:
sudo mariadb
Create the database and database user:
CREATE DATABASE roundcubemail CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER IF NOT EXISTS 'roundcube'@'localhost' IDENTIFIED BY 'YOUR_STRONG_ROUNDCUBE_DB_PASSWORD'; ALTER USER 'roundcube'@'localhost' IDENTIFIED BY 'YOUR_STRONG_ROUNDCUBE_DB_PASSWORD'; GRANT ALL PRIVILEGES ON roundcubemail.* TO 'roundcube'@'localhost'; FLUSH PRIVILEGES; EXIT;
Import the Roundcube Database Schema
sudo mariadb roundcubemail \ < /var/www/roundcube/SQL/mysql.initial.sql
Restart PHP-FPM:
sudo systemctl restart php8.5-fpm
8. Enable the Roundcube Installer Temporarily
Edit the Roundcube configuration:
sudo nano /var/www/roundcube/config/config.inc.php
Find:
$config['enable_installer'] = false;
Change it to:
$config['enable_installer'] = true;
If the setting does not exist, add this line at the end of the file:
$config['enable_installer'] = true;
You can check whether the setting exists with:
grep enable_installer /var/www/roundcube/config/config.inc.php
Find a line similar to:
$config['db_dsnw'] = 'mysql://roundcube:pass@localhost/roundcubemail';
Change it to:
$config['db_dsnw'] = 'mysql://roundcube:YOUR_STRONG_ROUNDCUBE_DB_PASSWORD@localhost/roundcubemail';
Configure Roundcube IMAP and SMTP
For SSL add or update the following settings, be carefull, they could already exist:
$config['imap_host'] = 'ssl://mail.myDomain.com'; $config['default_port'] = 993; $config['smtp_host'] = 'tls://mail.myDomain.com'; $config['smtp_port'] = 587; $config['smtp_user'] = '%u'; $config['smtp_pass'] = '%p'; $config['product_name'] = 'My Webmail';
Without SSL first check if returns nothing:
sudo doveconf -n | grep disable_plaintext_auth
$config['imap_host'] = 'localhost:143'; $config['smtp_host'] = 'localhost:587'; $config['smtp_user'] = '%u'; $config['smtp_pass'] = '%p'; $config['product_name'] = 'My Webmail';
To disable certificate add:
$config['imap_conn_options'] = [
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true,
],
];
Replace mail.myDomain.com with your mail server hostname.
Save the file in Nano:
Ctrl+O Enter Ctrl+X
Restart PHP-FPM and Nginx:
sudo systemctl restart php8.5-fpm sudo systemctl restart nginx
Temporarily point the symlink to the complete Roundcube directory:
Remove the existing symlink:
sudo rm /var/www/html/roundcube
Create a temporary installation symlink:
sudo ln -s /var/www/roundcube /var/www/html/roundcube
Open the Roundcube installer in your browser:
http://mail.myDomain.com/roundcube/installer/
If you cant Test IMAP config then execute
sudo journalctl -u dovecot -f
Check, password should be without hash and user "user" should have access to database mailserver
sudo nano /etc/dovecot/dovecot-sql.conf.ext
For dovecot --version: 2.4.2 (0962ed2104) looks like:
sql_driver = mysql
mysql localhost {
user = postfixadmin
password = YOUR_POSTFIXADMIN_PASSWORD
dbname = mailserver
}
passdb sql {
default_password_scheme = ARGON2ID
query = \
SELECT password \
FROM mailbox \
WHERE username = '%{user}' AND active = 1
}
userdb static {
fields {
uid = vmail
gid = vmail
home = /var/mail/vhosts/%{user|domain}/%{user|username}
mail_driver = maildir
mail_path = ~/Maildir
}
}
mariadb -u user -p mailserver
sudo mariadb -e "SHOW GRANTS FOR 'user'@'localhost';"
sudo doveadm auth test email@myDomain.com YOUR_MAILBOX_PASSWORD
php -m | grep -i imap
php -r '$fp = stream_socket_client("ssl://mail.testmail.milosev.com:993", $errno, $errstr, 10); var_dump($fp, $errno, $errstr);'
Replace myDomain.com with your own domain name.
11. Restore Dovecot SQL Authentication if Required
This section is only required if Dovecot SQL authentication was lost during a reinstall or package replacement.
Ubuntu 26.04 ships Dovecot 2.4. Dovecot 2.4 configuration syntax differs from Dovecot 2.3, so old Dovecot 2.3 examples should not be copied directly.
Configure Dovecot 2.4 SQL Authentication
Edit the SQL authentication configuration:
sudo nano /etc/dovecot/conf.d/auth-sql.conf.ext
Replace its contents with:
sql_driver = mysql
mysql 127.0.0.1 {
user = postfixadmin
password = YOUR_POSTFIXADMIN_DATABASE_PASSWORD
dbname = mailserver
}
passdb sql {
default_password_scheme = ARGON2ID
query = SELECT username AS "user", password \
FROM mailbox \
WHERE username='%{user}' AND active='1'
}
userdb sql {
query = SELECT CONCAT('/var/mail/vhosts/', SUBSTRING_INDEX(username,'@',-1), '/', SUBSTRING_INDEX(username,'@',1)) AS home, 5000 AS uid, 5000 AS gid FROM mailbox WHERE username='%{user}' AND active='1'
iterate_query = SELECT username AS "user" FROM mailbox WHERE active='1'
}
Replace YOUR_POSTFIXADMIN_DATABASE_PASSWORD with the MariaDB password used by the postfixadmin database user.
Validate the Dovecot Configuration
sudo doveconf -n
If no errors are displayed, restart Dovecot:
sudo systemctl restart dovecot
Check its status:
sudo systemctl status dovecot --no-pager
Test Mailbox Authentication
sudo doveadm auth test username@myDomain.com
Dovecot will prompt you for the mailbox password.
12. Reset a Mailbox Password for Testing
If authentication fails, reset the mailbox password to a password you know.
Change to the PostfixAdmin installation directory:
cd /var/www/postfixadmin
Reset the mailbox password:
php scripts/postfixadmin-cli.php mailbox update username@myDomain.com \ --password "Test123456!" \ --password2 "Test123456!"
Test authentication again:
sudo doveadm auth test username@myDomain.com
Enter:
Test123456!
13. Configure Postfix MariaDB Maps
Postfix needs MariaDB map files to look up virtual domains, mailboxes, and aliases stored by PostfixAdmin.
Create the Virtual Domain Map
sudo nano /etc/postfix/mysql-virtual-mailbox-domains.cf
Paste:
user = postfixadmin password = YOUR_POSTFIXADMIN_DATABASE_PASSWORD hosts = 127.0.0.1 dbname = mailserver query = SELECT domain FROM domain WHERE domain='%s' AND active='1'
To be sure that password is correct execute:
mysql -h 127.0.0.1 -u postfixadmin -p mailserver
And enter YOUR_POSTFIXADMIN_DATABASE_PASSWORD
If you forgot password:
ALTER USER 'postfixadmin'@'localhost' IDENTIFIED BY 'YOUR_POSTFIXADMIN_DATABASE_PASSWORD'; FLUSH PRIVILEGES; EXIT;
Create the Virtual Mailbox Map
sudo nano /etc/postfix/mysql-virtual-mailbox-maps.cf
Paste:
user = postfixadmin password = YOUR_POSTFIXADMIN_DATABASE_PASSWORD hosts = 127.0.0.1 dbname = mailserver query = SELECT CONCAT(maildir,'Maildir/') FROM mailbox WHERE username='%s' AND active='1'
Create the Virtual Alias Map
sudo nano /etc/postfix/mysql-virtual-alias-maps.cf
Paste:
user = postfixadmin password = YOUR_POSTFIXADMIN_DATABASE_PASSWORD hosts = 127.0.0.1 dbname = mailserver query = SELECT goto FROM alias WHERE address='%s' AND active='1'
Replace YOUR_POSTFIXADMIN_DATABASE_PASSWORD in all three files with the actual database password.
Secure the Postfix Database Files
sudo chmod 640 /etc/postfix/mysql-virtual-*.cf sudo chown root:postfix /etc/postfix/mysql-virtual-*.cf
14. Update the Postfix Main Configuration
Edit the Postfix configuration:
sudo nano /etc/postfix/main.cf
Add or update these settings:
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf virtual_transport = virtual virtual_mailbox_base = /var/mail/vhosts virtual_minimum_uid = 5000 virtual_uid_maps = static:5000 virtual_gid_maps = static:5000
Restart Postfix:
sudo systemctl restart postfix
Verify the virtual mailbox configuration:
postconf -n | grep virtual
15. Verify the Postfix Database Queries
Test whether Postfix can find a virtual domain:
postmap -q myDomain.com \ mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
Test whether Postfix can find a mailbox:
postmap -q username@myDomain.com \ mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
Test whether Postfix can find an alias:
postmap -q username@myDomain.com \ mysql:/etc/postfix/mysql-virtual-alias-maps.cf
16. Complete the Roundcube Installer
Return to the Roundcube installer:
http://mail.myDomain.com/roundcube/installer/
Check that all required PHP extensions and database connections are working, then complete the installation.
17. Disable and Remove the Installer
For security reasons, disable the installer after completing the installation.
Edit the Roundcube configuration:
sudo nano /var/www/roundcube/config/config.inc.php
Change:
$config['enable_installer'] = true;
to:
$config['enable_installer'] = false;
Remove the installer directory:
sudo rm -rf /var/www/roundcube/installer
Restart PHP-FPM and Nginx:
sudo systemctl restart php8.5-fpm sudo systemctl restart nginx
18. Log In to Roundcube
You can now open Roundcube at:
http://mail.myDomain.com/roundcube/
Log in with the complete mailbox address and mailbox password, for example:
username@myDomain.com
19. Check Services and Logs
Check the status of Nginx, PHP-FPM, MariaDB, Dovecot, and Postfix:
sudo systemctl status nginx --no-pager sudo systemctl status php8.5-fpm --no-pager sudo systemctl status mariadb --no-pager sudo systemctl status dovecot --no-pager sudo systemctl status postfix --no-pager
Check the Nginx error log:
sudo tail -n 100 /var/log/nginx/error.log
Check the mail log:
sudo tail -n 100 /var/log/mail.log
Follow the mail log in real time:
sudo tail -f /var/log/mail.log
Check recent Dovecot messages:
sudo journalctl -u dovecot -n 100 --no-pager
Check recent PHP-FPM messages:
sudo journalctl -u php8.5-fpm -n 100 --no-pager
- Details
- Written by: Stanko Milosev
- Category: Ubuntu
- Hits: 30
This guide explains how to install PostfixAdmin 4.0.5 on Ubuntu with Nginx, PHP 8.5, and MariaDB.
Install Required Packages
sudo apt update
sudo apt install -y wget unzip composer git
cd /tmp
wget https://github.com/postfixadmin/postfixadmin/archive/refs/tags/v4.0.5.tar.gz
tar -xzf v4.0.5.tar.gz
sudo mv /tmp/postfixadmin-4.0.5 /var/www/postfixadmin
cd /var/www/postfixadmin
composer install --no-dev
sudo rm -f /var/www/html/postfixadmin
sudo ln -s /var/www/postfixadmin/public /var/www/html/postfixadmin
sudo chown -R www-data:www-data /var/www/postfixadmin
sudo find /var/www/postfixadmin -type d -exec chmod 755 {} \;
sudo find /var/www/postfixadmin -type f -exec chmod 644 {} \;
sudo nginx -t
sudo systemctl reload nginx
dbconfig-common
During the installation you will be asked:
Configure database for postfixadmin with dbconfig-common?
Select No. The database will be configured manually.
Create the Database User
Log in to MariaDB:
sudo mysql
Create a dedicated database user for PostfixAdmin:
CREATE USER 'postfixadmin'@'localhost' IDENTIFIED BY 'YOUR_STRONG_PASSWORD'; GRANT ALL PRIVILEGES ON mailserver.* TO 'postfixadmin'@'localhost'; FLUSH PRIVILEGES;
Configure Nginx
Edit the default Nginx site:
sudo nano /etc/nginx/sites-available/default
Uncomment the PHP block and use your PHP 8.5 socket:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.5-fpm.sock;
}
Make sure the index directive contains index.php. For example:
index index.php index.html index.htm;
Test the configuration and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Configure PostfixAdmin
Create the configuration file:
sudo nano /var/www/postfixadmin/config.local.php
Paste the following configuration:
<?php $CONF['configured'] = true; $CONF['database_type'] = 'mysqli'; $CONF['database_host'] = 'localhost'; $CONF['database_user'] = 'postfixadmin'; $CONF['database_password'] = 'YOUR_POSTFIXADMIN_PASSWORD'; $CONF['database_name'] = 'mailserver'; $CONF['encrypt'] = 'dovecot:ARGON2ID'; $CONF['domain_path'] = 'YES'; $CONF['domain_in_mailbox'] = 'NO';
Open the PostfixAdmin setup page (http://mail.myDomain.com/postfixadmin/setup.php) and enter your initial setup password. After submitting it, PostfixAdmin will generate a hashed setup password and display a configuration line similar to this:
$CONF['setup_password'] = 'YOUR_GENERATED_SETUP_PASSWORD';
Copy the generated line and add it to:
sudo nano /var/www/postfixadmin/config.local.php
Create the Web Symlink
Create the symbolic link for the PostfixAdmin web interface:
sudo ln -s /usr/share/postfixadmin/public /var/www/html/postfixadmin
Note: If you already created a symbolic link to /var/www/postfixadmin/public during the installation, you do not need to execute this command again.
Verify the Configuration
If it is not already present, ensure that index.php is included in the Nginx configuration:
sudo nano /etc/nginx/sites-available/default
Finally, verify that the PostfixAdmin configuration file exists and contains the correct database credentials:
sudo nano /var/www/postfixadmin/config.local.php
Now first start setup: http://mail.myDomain.com/postfixadmin/setup.php
Here I received the error:
⛔Database connection string : mysql:host=localhost;dbname=mailserver⛔Problem connecting to database, check database configuration ($CONF['database_*'] entries in config.local.php)
⛔SQLSTATE[HY000] [1045] Access denied for user 'postfixadmin'@'localhost' (using password: YES)
So, I executed:
grep -E "database_(host|user|password|name)" /var/www/postfixadmin/config.local.php
And saw that password was wrong, thats why I change it again:
sudo nano /var/www/postfixadmin/config.local.php
The I had the error:
⛔Password Hashing - attempted to use configured encrypt backend (dovecot:ARGON2ID) triggered an error: /usr/bin/doveadm pw failed, see error log for details⛔You will have problems logging into PostfixAdmin.
⛔Check out our Dovecot documentation at
So, I executed:
sudo -u www-data /usr/bin/doveadm pw -s ARGON2ID
Since I received permission denied I executed:
sudo usermod -aG dovecot www-data sudo usermod -aG dovecot www-data sudo systemctl restart php8.5-fpm sudo systemctl restart nginx
And again, I executed:
sudo -u www-data /usr/bin/doveadm pw -s ARGON2ID
This time without permission denied
You can now log in to PostfixAdmin as an administrator at http://mail.myDomain.com/postfixadmin/login.php, or as a mailbox user at http://mail.myDomain.com/postfixadmin/users/login.php. Replace myDomain.com with your own domain name.
If you receive an error then run these commands immediately after opening
sudo tail -50 /var/log/nginx/error.log sudo journalctl -u php8.5-fpm -n 50 --no-pager
Reset the Administrator Password
Check whether the mailserver database exists:
sudo mariadb -e "SHOW DATABASES LIKE 'mailserver';"
Check whether a user has access to mailserver:
sudo mariadb -e "SHOW GRANTS FOR 'postfixadmin'@'localhost';"
If you forgot the PostfixAdmin administrator password, first list the existing administrator accounts:
sudo mariadb mailserver -e "SELECT username FROM admin;"
If the previous command returns an administrator account such as adminmail@myDomain.com, reset its password with:
cd /var/www/postfixadmin php scripts/postfixadmin-cli.php admin update adminmail@myDomain.com \ --password "MyNewAdminPassword123!" \ --password2 "MyNewAdminPassword123!"
Reset a Mailbox Password
If you forgot a mailbox password, first list the existing mailbox accounts:
sudo mariadb mailserver -e "SELECT username FROM mailbox;"
If the previous command returns a mailbox account such as myUser@myDomain.com, reset its password with:
cd /var/www/postfixadmin php scripts/postfixadmin-cli.php mailbox update myUser@myDomain.com \ --password "MyNewMailboxPassword123!" \ --password2 "MyNewMailboxPassword123!"
- Details
- Written by: Stanko Milosev
- Category: Ubuntu
- Hits: 54
This tutorial explains how to create a complete local mail server using Postfix, Dovecot, and MariaDB. The server can be tested locally without configuring public DNS records or receiving email from the Internet.
What We Are Building
The mail server consists of three main components:
Postfix
Postfix is the Mail Transfer Agent, or MTA. It:
- receives email;
- sends email;
- determines where messages should be delivered;
- delivers messages to local virtual mailboxes.
Postfix does not provide IMAP access and does not let users read their mail directly.
Dovecot
Dovecot:
- authenticates mailbox users;
- provides access through IMAP;
- reads and manages messages stored in Maildir format.
Dovecot does not normally receive email directly from other Internet mail servers.
MariaDB
Instead of creating one Linux account for every mailbox, we will store domains, mailbox users, password hashes, and aliases in a MariaDB database.
For example:
| Email address | Password |
|---|---|
| email@myDomain.com | Secure password hash |
1. Install MariaDB
sudo apt update sudo apt install mariadb-server mariadb-client -y sudo reboot
After the server restarts, verify the MariaDB installation:
mariadb --version sudo systemctl status mariadb
2. Create the Mail Database
Open the MariaDB administration console:
sudo mysql
Create the database:
CREATE DATABASE mailserver CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; SHOW DATABASES; USE mailserver;
Create the virtual_domains table
CREATE TABLE virtual_domains (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE
);
Create the virtual_users table
CREATE TABLE virtual_users (
id INT AUTO_INCREMENT PRIMARY KEY,
domain_id INT NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
maildir VARCHAR(255) NOT NULL,
FOREIGN KEY (domain_id)
REFERENCES virtual_domains(id)
ON DELETE CASCADE
);
Create the virtual_aliases table
CREATE TABLE virtual_aliases (
id INT AUTO_INCREMENT PRIMARY KEY,
domain_id INT NOT NULL,
source VARCHAR(255) NOT NULL,
destination VARCHAR(255) NOT NULL,
FOREIGN KEY (domain_id)
REFERENCES virtual_domains(id)
ON DELETE CASCADE
);
3. Create a Restricted Database User
Postfix and Dovecot should not connect to MariaDB using its root account. Create a dedicated account that has read-only access to the mail database.
CREATE USER 'mailuser'@'localhost' IDENTIFIED BY 'YOUR_STRONG_PASSWORD'; GRANT SELECT ON mailserver.* TO 'mailuser'@'localhost'; FLUSH PRIVILEGES;If you made a mistake use:
ALTER USER 'mailuser'@'localhost' IDENTIFIED BY 'YOUR_STRONG_PASSWORD';
YOUR_STRONG_PASSWORD with a strong password.
Avoid characters that may cause parsing problems in configuration files unless you know how they must be escaped.
4. Verify the Database
USE mailserver; SHOW TABLES;
The result should contain the following tables:
+----------------------+ | Tables_in_mailserver | +----------------------+ | virtual_aliases | | virtual_domains | | virtual_users | +----------------------+
Inspect the table structures:
DESCRIBE virtual_domains; DESCRIBE virtual_users; DESCRIBE virtual_aliases;
Verify that the restricted database user exists:
SELECT User, Host FROM mysql.user WHERE User = 'mailuser';
Verify its permissions:
SHOW GRANTS FOR 'mailuser'@'localhost';
The account should have SELECT permission on the mailserver database.
5. Add the First Virtual Domain
Open MariaDB if you previously closed it:
sudo mysql
Add the domain:
USE mailserver;
INSERT INTO virtual_domains (name)
VALUES ('myDomain.com');
SELECT * FROM virtual_domains;
Public DNS is not required for local mailbox delivery. The domain is being used as a virtual mail domain inside Postfix and Dovecot.
6. Create the Virtual Mail User
All virtual mailboxes will be owned by one dedicated Linux user named
vmail.
sudo groupadd -g 5000 vmail
sudo useradd \
-g vmail \
-u 5000 \
-d /var/mail \
-m \
-s /usr/sbin/nologin \
vmail
Create the root directory for virtual mailboxes:
sudo mkdir -p /var/mail/vhosts sudo chown -R vmail:vmail /var/mail/vhosts sudo chmod 770 /var/mail/vhosts
Verify the directory and user:
ls -ld /var/mail/vhosts id vmail
7. Generate the First Mailbox Password
Install the Dovecot core utilities:
sudo apt install -y dovecot-core
Generate an Argon2id password hash:
doveadm pw -s ARGON2ID
Dovecot will ask for the password twice and return a value similar to:
{ARGON2ID}$argon2id$v=19$m=65536,t=3,p=1$...
Copy the complete hash, including the {ARGON2ID} prefix.
The database must contain the hash, not the plain-text mailbox password.
8. Insert the First Mailbox
Open MariaDB:
sudo mysql
Insert the mailbox:
USE mailserver;
INSERT INTO virtual_users
(
domain_id,
email,
password,
maildir
)
VALUES
(
1,
'email@myDomain.com',
'<PASTE_HASH_HERE>',
'myDomain.com/email/Maildir/'
);
Verify the mailbox:
SELECT id, email, maildir FROM virtual_users;
Expected result:
+----+---------------------+-------------------------------+ | id | email | maildir | +----+---------------------+-------------------------------+ | 1 | email@myDomain.com | myDomain.com/email/Maildir/ | +----+---------------------+-------------------------------+
9. Install Dovecot
sudo apt install -y \
dovecot-core \
dovecot-imapd \
dovecot-mysql
Verify the installed version:
dovecot --version
2.3.21 (47349e2482)
Check the service:
sudo systemctl status dovecot
10. Enable SQL Authentication in Dovecot
Open Dovecot's authentication configuration:
sudo nano /etc/dovecot/conf.d/10-auth.conf
Find this line and comment it out:
#!include auth-system.conf.ext
Enable SQL authentication:
!include auth-sql.conf.ext
11. Configure Dovecot's SQL Connection
Open the SQL authentication file:
sudo nano /etc/dovecot/conf.d/auth-sql.conf.ext
Replace its contents with:
sql_driver = mysql
mysql localhost {
user = mailuser
password = YOUR_STRONG_PASSWORD
dbname = mailserver
}
passdb sql {
default_password_scheme = ARGON2ID
query = SELECT email AS user, password \
FROM virtual_users \
WHERE email = '%{user}'
}
userdb sql {
query = SELECT \
5000 AS uid, \
5000 AS gid, \
CONCAT(
'/var/mail/vhosts/',
SUBSTRING_INDEX(maildir, '/Maildir/', 1)
) AS home \
FROM virtual_users \
WHERE email = '%{user}'
}
Replace YOUR_STRONG_PASSWORD with the password belonging to the
MariaDB account mailuser.
12. Configure Dovecot to Use Maildir - without PostfixAdmin
Open the mail configuration:
sudo nano /etc/dovecot/conf.d/10-mail.conf
Set the following values:
mail_driver = maildir
mail_home = /var/mail/vhosts/%{user | domain}/%{user | username}
mail_path = ~/Maildir
Comment out or remove incompatible mbox settings:
# mail_inbox_path = /var/mail/%{user}
# mail_path = %{home}/mail
The resulting mailbox paths will follow this structure:
home = /var/mail/vhosts/myDomain.com/email mail_path = /var/mail/vhosts/myDomain.com/email/Maildir
Verify the active mail settings:
sudo doveconf -n | grep -E \ 'mail_driver|mail_home|mail_path|mail_inbox_path'
12. Configure Dovecot to Use Maildir - with PostfixAdmin
Open the mail configuration:
sudo nano /etc/dovecot/conf.d/10-mail.conf
Set the following values:
mail_driver = maildir mail_path = ~/Maildir
mail_driver was two times
Comment out any mbox settings
Comment out lines such as:
# mail_driver = mbox
# mail_home = /home/%{user | username}
# mail_path = %{home}/mail
# mail_inbox_path = /var/mail/%{user}
if they exist.
If dovecot --version = 2.3.21 (47349e2482) then replace existing with:
mail_location = maildir:/var/mail/vhosts/%d/%n
Verify:
sudo doveconf -n | grep -E 'mail_driver|mail_home|mail_path'
Should see:
mail_driver = maildir mail_path = ~/Maildir
If dovecot --version = 2.3.21 (47349e2482) then verify:
sudo doveconf -n | grep mail_location
Should see:
mail_location = maildir:/var/mail/vhosts/%d/%n
13. Validate and Restart Dovecot
Always validate the configuration before restarting Dovecot:
sudo doveconf -n
doveconf -n reports a configuration error.
Correct the error first.
When validation completes successfully, restart the service:
sudo systemctl restart dovecot sudo systemctl status dovecot --no-pager
Test the password database
sudo doveadm auth test email@myDomain.com
Dovecot will ask for the mailbox password. A successful result should report that authentication succeeded.
Test the user database
sudo doveadm user email@myDomain.com
The result should contain values similar to:
uid=5000 gid=5000 home=/var/mail/vhosts/myDomain.com/email mail=/var/mail/vhosts/myDomain.com/email/Maildir
14. Install Postfix
sudo apt install -y postfix postfix-mysql
During installation, select:
- General type of mail configuration: Internet Site
- System mail name: myDomain.com
Selecting Internet Site does not automatically expose the server to the Internet. Firewall rules, provider restrictions, port forwarding, and DNS still determine whether external systems can reach it.
15. Verify the Postfix Installation
Check the Postfix version:
postconf mail_version
Should see:
mail_version = 3.8.6
Check the service:
sudo systemctl status postfix --no-pager
The service should report:
Active: active (running)
Verify MySQL support
postconf -m
The output should include:
mysql
If mysql is missing, verify that the
postfix-mysql package was installed successfully.
16. Configure the Local Mail Hostname
Check the current hostname:
hostnamectl hostname -f
The fully qualified hostname should be:
mail.myDomain.com
If necessary, set it:
sudo hostnamectl set-hostname mail.myDomain.com
Update /etc/hosts
Open the hosts file:
sudo nano /etc/hosts
Use a local entry similar to:
127.0.0.1 localhost 127.0.1.1 mail.myDomain.com mail # The following lines are desirable for IPv6-capable hosts ::1 localhost ip6-localhost ip6-loopback
Verify the hostname again:
hostname hostname -f
Both commands should identify the server as mail.myDomain.com.
17. Create the Postfix MariaDB Query Files
Postfix uses separate query files to look up virtual domains, mailboxes, and aliases. Each file has one specific responsibility.
Virtual domain lookup
sudo nano /etc/postfix/mysql-virtual-mailbox-domains.cf
user = mailuser password = YOUR_MAILUSER_PASSWORD hosts = localhost dbname = mailserver query = SELECT 1 FROM virtual_domains WHERE name='%s'
Virtual mailbox lookup
sudo nano /etc/postfix/mysql-virtual-mailbox-maps.cf
user = mailuser password = YOUR_MAILUSER_PASSWORD hosts = localhost dbname = mailserver query = SELECT maildir FROM virtual_users WHERE email='%s'
Virtual alias lookup
sudo nano /etc/postfix/mysql-virtual-alias-maps.cf
user = mailuser password = YOUR_MAILUSER_PASSWORD hosts = localhost dbname = mailserver query = SELECT destination FROM virtual_aliases WHERE source='%s'
Replace YOUR_MAILUSER_PASSWORD in all three files with the
password of the MariaDB account mailuser.
18. Secure the Postfix Query Files
These files contain a database password and must not be readable by every user.
sudo chown root:postfix /etc/postfix/mysql-virtual-*.cf sudo chmod 640 /etc/postfix/mysql-virtual-*.cf
Verify the permissions:
ls -l /etc/postfix/mysql-virtual-*.cf
Expected permissions:
-rw-r----- 1 root postfix ...
19. Test the Database Queries Before Configuring Postfix
Testing the individual lookup files before changing Postfix makes database, password, permission, and SQL errors much easier to identify.
Test the virtual domain
sudo postmap -q myDomain.com \ mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
Expected result:
1
Test the mailbox
sudo postmap -q email@myDomain.com \ mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
Expected result:
myDomain.com/email/Maildir/
Test the alias
sudo postmap -q email@myDomain.com \ mysql:/etc/postfix/mysql-virtual-alias-maps.cf
No output is expected when no alias exists for the address.
20. Configure Postfix for Virtual Mailboxes
First, inspect and save the current non-default settings:
postconf -n
Use postconf -e to change the configuration. This avoids duplicate
parameters and reduces the possibility of syntax errors in
/etc/postfix/main.cf.
sudo postconf -e \ "virtual_mailbox_domains=mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf" sudo postconf -e \ "virtual_mailbox_maps=mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf" sudo postconf -e \ "virtual_alias_maps=mysql:/etc/postfix/mysql-virtual-alias-maps.cf" sudo postconf -e "virtual_mailbox_base=/var/mail/vhosts" sudo postconf -e "virtual_uid_maps=static:5000" sudo postconf -e "virtual_gid_maps=static:5000" sudo postconf -e "virtual_transport=virtual"
21. Correct the mydestination Setting
Check the current value:
postconf mydestination
The virtual domain must not also be configured as a normal local destination. For example, this value would be incorrect:
mydestination = $myhostname, myDomain.com, ubuntu, localhost.localdomain, localhost
Set the appropriate value:
sudo postconf -e \ "mydestination=\$myhostname, localhost.\$mydomain, localhost"
Verify it:
postconf mydestination
Expected result:
mydestination = $myhostname, localhost.$mydomain, localhost
22. Validate and Restart Postfix
Validate the Postfix configuration:
sudo postfix check
My result: updating /etc/hosts => /var/spool/postfix//etc/hosts postfix/postfix-script: warning: not owned by root: /var/spool/postfix/etc/resolv.conf
Correct any errors before restarting the service. Some warnings may refer to files copied into Postfix's chroot environment. Review each warning carefully.
Restart Postfix:
sudo systemctl restart postfix sudo systemctl status postfix --no-pager
23. Send a Local Test Message
Install the command-line mail client if it is not already available:
sudo apt install -y bsd-mailx
Send a message to the virtual mailbox:
printf "Hello from my first Postfix test.\n" \ | mail -s "First Mail" email@myDomain.com
Immediately inspect the Postfix log:
sudo journalctl -u postfix -n 50 --no-pager
Look for a successful delivery status. If delivery fails, the log normally indicates whether the problem involves permissions, a database lookup, an unknown mailbox, or an invalid path.
24. Verify That the Message Was Delivered
Search for files under the virtual mailbox directory:
sudo find /var/mail/vhosts -type f
You should see a message file similar to:
/var/mail/vhosts/myDomain.com/email/Maildir/new/1752910270.Vfd...
List the user's Dovecot mailboxes:
sudo doveadm mailbox list -u email@myDomain.com
A basic result should include:
INBOX
Finally, retrieve the subject from the inbox:
sudo doveadm fetch \ -u email@myDomain.com \ 'hdr.subject' \ mailbox INBOX
Expected result:
hdr.subject: First Mail
25. Install certificate
First install certbot:
sudo apt update sudo apt install certbot
Request the certificate:
sudo certbot certonly --standalone -d mail.myDomain.com
or for testing
sudo certbot --test-cert --nginx -d mail.myDomain.com
If you already installed Nginx and is listening on port 80, then most probably you will receive error like:
Could not bind TCP port 80 because it is already in use by another process on this system (such as a web server). Please stop the program in question and then try again.
In that case use the Nginx plugin:
sudo apt install python3-certbot-nginx
Then request the certificate:
sudo certbot --nginx -d mail.myDomain.com
or for testing
sudo certbot --test-cert --nginx -d mail.myDomain.com
After you install a Let's Encrypt certificate, the files will be created here:
/etc/letsencrypt/live/mail.myDomain.com/
Find where are certificates located:
sudo find /etc -name "*.pem" | grep -E "ssl|dovecot|letsencrypt|snakeoil"
In my case they were in:
/etc/letsencrypt/live/mail.myDomain.com/fullchain.pem
Point Dovecot to the Let's Encrypt certificate:
sudo nano /etc/dovecot/conf.d/10-ssl.conf
Look for lines similar to:
ssl_server_cert_file = ... ssl_server_key_file= ...and change them to:
ssl_server_cert_file = /etc/letsencrypt/live/mail.myDomain.com/fullchain.pem ssl_server_key_file = /etc/letsencrypt/live/mail.myDomain.com/privkey.pem
Then restart Dovecot:
sudo systemctl restart dovecot
Check certificate:
openssl s_client -connect mail.myDomain.com:993 -servername mail.myDomain.com
Testing Without Public DNS
The local test works because Postfix recognizes myDomain.com as a
virtual domain and delivers the message directly to the local Maildir.
No public MX record is consulted during this local delivery.
When connecting from another computer on the same private network, add a temporary hosts-file entry on that computer:
192.168.1.50 mail.myDomain.com
Replace 192.168.1.50 with the private IP address of the mail server.
On Windows, edit this file as an administrator:
C:\Windows\System32\drivers\etc\hosts
On Linux, edit:
/etc/hosts
You can then configure an email client to connect to
mail.myDomain.com without creating a public DNS record.
Useful Diagnostic Commands
Check mail
sudo journalctl -u postfix | grep emailFromWhichIsSent@someDomain.com sudo journalctl -u postfix --since "today" | grep emailFromWhichIsSent find /var/mail/vhosts -type f | xargs grep -l "emailFromWhichIsSent@someDomain.com" 2>/dev/null find /var/mail/vhosts -type f | xargs grep -l "Subject: test" 2>/dev/null find /var/mail/vhosts -type f -printf "%TY-%Tm-%Td %TT %p\n" | sort
Test if anything is conected on port 25
sudo tcpdump -n -i any port 25
Add to DNS
Add record A like:
Name: mail.testmail
Data: your IP adress
TTL: 600 seconds
And MX record like:
Name: testmail
Data: mail.testmail.myDomain.com (Priority: 10)
TTL 1 Hour
Test if DNS MX record is visible
dig @8.8.8.8 testmail.myDomain.com MX +short
Check services
sudo systemctl status mariadb --no-pager sudo systemctl status dovecot --no-pager sudo systemctl status postfix --no-pager
Validate configurations
sudo doveconf -n sudo postfix check postconf -n
Test the database lookups
sudo postmap -q myDomain.com \ mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf sudo postmap -q email@myDomain.com \ mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
Test Dovecot authentication
sudo doveadm auth test email@myDomain.com sudo doveadm user email@myDomain.com
Inspect logs
sudo journalctl -u postfix -n 100 --no-pager sudo journalctl -u dovecot -n 100 --no-pager sudo journalctl -u mariadb -n 100 --no-pager
Inspect Maildir
sudo find /var/mail/vhosts -type f sudo doveadm mailbox list -u email@myDomain.com sudo doveadm fetch \ -u email@myDomain.com \ 'hdr.subject' \ mailbox INBOX