milosev.com
  • Home
    • List all categories
    • Sitemap
  • Downloads
    • WebSphere
    • Hitachi902
    • Hospital
    • Kryptonite
    • OCR
    • APK
  • About me
    • Gallery
      • Italy2022
      • Côte d'Azur 2024
    • Curriculum vitae
      • Resume
      • Lebenslauf
    • Social networks
      • Facebook
      • Twitter
      • LinkedIn
      • Xing
      • GitHub
      • Google Maps
      • Sports tracker
    • Adventures planning
  1. You are here:  
  2. Home

How to Create a Local Mail Server for Testing Without Public DNS

Details
Written by: Stanko Milosev
Category: Ubuntu
Published: 19 July 2026
Last Updated: 08 August 2026
Hits: 96

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.

Important: This configuration is intended for local testing. Sending and receiving email over the public Internet requires additional DNS records, TLS certificates, reverse DNS, firewall configuration, and an Internet provider that permits SMTP traffic.

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;
Important: If you are going to install PostfixAdmin then dont install these tables

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
);
Important: Also here if you are going to install PostfixAdmin then dont Create a Restricted Database User

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';
Replace 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
  • useradd — create a new Linux user account.
  • -g vmail — set the user's primary group to the existing vmail group. If you previously created it with groupadd -g 5000 vmail, its GID is 5000.
  • -u 5000 — explicitly give the new user UID 5000.
  • -d /var/mail — set /var/mail as the user's home directory.
  • -m — create the home directory if it does not already exist.
  • -s /usr/sbin/nologin — set the user's login shell to nologin. This prevents vmail from being used as a normal interactive login account.
  • vmail — the name of the user being created.

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
Important: Skip step "7. Generate the First Mailbox Password" if you're using PostfixAdmin

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.

Important: Skip step "8. Insert the First Mailbox" if you're using PostfixAdmin

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
Important: Skip step "11. Configure Dovecot's SQL Connection" if you're using PostfixAdmin

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.

Important: This is without PostfixAdmin

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
Do not restart Dovecot if 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
Important: For PostfixAdmin We will test this later

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.

Important: Skip step "17. Create the Postfix MariaDB Query Files" if you're using PostfixAdmin

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.

Important: Skip step "18. Secure the Postfix Query Files" if you're using PostfixAdmin since "chown: cannot dereference '/etc/postfix/mysql-virtual-*.cf': No such file or directory"

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 ...
Important: Skip step "19. Test the Database Queries Before Configuring Postfix" if you're using PostfixAdmin

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.

Important: Skip step "20. Configure Postfix for Virtual Mailboxes" if you're using PostfixAdmin

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

We will use postconf command to set mydestination

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

Unzip

Details
Written by: Stanko Milosev
Category: Ubuntu
Published: 17 July 2026
Last Updated: 31 July 2026
Hits: 35
sudo apt update
sudo apt install unzip
sudo unzip yourfile.zip
unzip /path/to/archive.zip -d /path/to/destination/

Display avaliable memory with colors

Details
Written by: Stanko Milosev
Category: Ubuntu
Published: 17 July 2026
Last Updated: 17 July 2026
Hits: 29
To display avaliable memory with colors
free -h | awk 'NR==2{printf "\033[36mRAM\033[0m | \033[32m%s\033[0m Total | \033[31m%s\033[0m Used | \033[32m%s\033[0m Free | \033[33m%s\033[0m Avail\n",$2,$3,$4,$7}'

Change pass add user

Details
Written by: Stanko Milosev
Category: Ubuntu
Published: 17 July 2026
Last Updated: 18 July 2026
Hits: 31
sudo passwd root
sudo adduser username
If you want the user to be able to run administrative commands:
sudo usermod -aG sudo username
Delete user
sudo deluser username
sudo deluser --remove-home username
  1. WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
  2. Installing and Running an ASP.NET Core 10 Application on Ubuntu 24.04 with Nginx
  3. Installing Nginx, MariaDB, and PHP 8.5 on a Clean Ubuntu Server for Joomla!
  4. Installing Mailcow on a Clean Ubuntu Installation

Subcategories

C#

Azure

ASP.NET

JavaScript

Software Development Philosophy

MS SQL

IBM WebSphere MQ

MySQL

Joomla

Delphi

PHP

Windows

Life

Lazarus

Downloads

Android

CSS

Chrome

HTML

Linux

Eclipse

Page 171 of 174

  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174