Setting Up Postfix with Virtual Mailboxes
August 16, 2026
Postfix is the MTA (Mail Transfer Agent) that handles sending and receiving email. It accepts incoming mail on port 25, delivers it to local mailboxes, and relays outgoing mail from authenticated users on port 587. This post covers installing Postfix on Gentoo with virtual mailbox domains backed by MySQL, TLS encryption, and SASL authentication through Dovecot.
Prerequisites
Before starting:
- DNS records and TLS certificates are configured
- MySQL is installed with root access
- Dovecot will be installed in the next post — Postfix depends on it for SASL authentication and local delivery
Setting USE Flags
Postfix needs MySQL support for virtual mailbox lookups and SASL support for authenticated submission:
cat > /etc/portage/package.use/postfix << 'EOF'
mail-mta/postfix mysql sasl ssl
EOF
Installing Postfix
emerge -av mail-mta/postfix
If another MTA (like ssmtp or sendmail) is installed, Portage will block. Remove it first:
emerge --deselect mail-mta/ssmtp
emerge -C mail-mta/ssmtp
Creating the Virtual Mail User
Virtual mailboxes are stored on disk under a single system user rather than individual Unix accounts. Create the vmail user and group:
groupadd -g 5000 vmail
useradd -g vmail -u 5000 -d /var/vmail -s /sbin/nologin -m vmail
Create the mail storage directory:
mkdir -p /var/vmail
chown vmail:vmail /var/vmail
chmod 770 /var/vmail
All virtual mailboxes will be stored under /var/vmail/domain/user/ in Maildir format.
Creating the Mail Database
Create the database and user that Postfix will query for virtual domains, mailboxes, and aliases:
mysql << 'EOF'
CREATE DATABASE IF NOT EXISTS mailserver CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS 'mailuser'@'localhost' IDENTIFIED BY 'mail-db-password';
GRANT SELECT ON mailserver.* TO 'mailuser'@'localhost';
FLUSH PRIVILEGES;
EOF
Postfix only needs SELECT access — it reads domain and mailbox information but never writes to the database. PostfixAdmin handles the write operations.
Configuring main.cf
The main Postfix configuration file is /etc/postfix/main.cf. This is the core of the mail server.
# Identity
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
mydestination = localhost
# Network
inet_interfaces = all
inet_protocols = ipv4
# TLS (incoming connections)
smtpd_tls_cert_file = /etc/lego/example.com/certificates/mail.example.com.crt
smtpd_tls_key_file = /etc/lego/example.com/certificates/mail.example.com.key
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
# TLS (outgoing connections)
smtp_tls_security_level = may
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
# SASL Authentication (via Dovecot)
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
# Restrictions
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination
smtpd_relay_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination
# Virtual mailbox configuration
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailboxes.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-aliases.cf
virtual_mailbox_base = /var/vmail
virtual_minimum_uid = 5000
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
# Delivery via Dovecot LMTP
virtual_transport = lmtp:unix:private/dovecot-lmtp
# Limits
message_size_limit = 52428800
mailbox_size_limit = 0
# Misc
smtpd_banner = $myhostname ESMTP
biff = no
append_dot_mydomain = no
Key Decisions
mydestination = localhost — Postfix doesn't deliver to local Unix accounts. All mail goes through the virtual mailbox system.
virtual_transport = lmtp:unix:private/dovecot-lmtp — Postfix hands incoming mail to Dovecot via LMTP (Local Mail Transfer Protocol). Dovecot handles final delivery, Sieve filtering, and quota enforcement.
smtpd_tls_auth_only = yes — SASL credentials are only accepted over TLS-encrypted connections. This prevents passwords from being sent in cleartext.
smtpd_tls_security_level = may — TLS is offered but not required for incoming mail on port 25. Requiring TLS on port 25 would reject mail from servers that don't support it. Port 587 (submission) requires TLS separately.
message_size_limit = 52428800 — 50MB maximum message size. Adjust based on your needs.
MySQL Lookup Files
Create the files Postfix uses to query the mail database.
/etc/postfix/mysql-virtual-domains.cf
user = mailuser
password = mail-db-password
hosts = 127.0.0.1
dbname = mailserver
query = SELECT domain FROM domain WHERE domain='%s' AND active = '1'
/etc/postfix/mysql-virtual-mailboxes.cf
user = mailuser
password = mail-db-password
hosts = 127.0.0.1
dbname = mailserver
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'
/etc/postfix/mysql-virtual-aliases.cf
user = mailuser
password = mail-db-password
hosts = 127.0.0.1
dbname = mailserver
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'
Set restrictive permissions — these files contain database credentials:
chmod 640 /etc/postfix/mysql-virtual-*.cf
chown root:postfix /etc/postfix/mysql-virtual-*.cf
Configuring master.cf
Edit /etc/postfix/master.cf to enable the submission port (587) with mandatory TLS and authentication:
# Standard SMTP (port 25)
smtp inet n - n - - smtpd
# Submission (port 587) - authenticated users only
submission inet n - n - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_tls_auth_only=yes
-o smtpd_reject_unlisted_recipient=no
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
The submission port requires TLS (smtpd_tls_security_level=encrypt) and only accepts authenticated users. This is the port mail clients use for sending.
Starting and Enabling Postfix
rc-update add postfix default
rc-service postfix start
Alias Domains
If you host mail for a secondary domain that should mirror the primary domain's mailboxes (e.g., mail sent to user@example.org delivers to the same mailbox as user@example.com), configure it as an alias domain in PostfixAdmin. Postfix handles this through its virtual alias maps — the MySQL query resolves the alias to the primary address, and delivery proceeds normally.
For domain-level aliases, add an entry in the alias_domain table mapping example.org to example.com. Postfix needs an additional lookup file for this.
/etc/postfix/mysql-virtual-alias-domains.cf
user = mailuser
password = mail-db-password
hosts = 127.0.0.1
dbname = mailserver
query = SELECT alias_domain FROM alias_domain WHERE alias_domain='%s' AND active = '1'
Add to main.cf:
virtual_alias_domains = mysql:/etc/postfix/mysql-virtual-alias-domains.cf
Set permissions:
chmod 640 /etc/postfix/mysql-virtual-alias-domains.cf
chown root:postfix /etc/postfix/mysql-virtual-alias-domains.cf
Postfix Logging
Postfix logs all mail transactions to the system mail log. On Gentoo, this is /var/log/mail.log or /var/log/messages depending on your syslog configuration.
Monitor deliveries and errors in real time:
tail -f /var/log/mail.log
Common log entries to understand:
status=sent— message delivered successfullystatus=deferred— temporary failure, Postfix will retrystatus=bounced— permanent failure, sender gets a bounce messageNOQUEUE: reject— message rejected before queuing (policy restriction)
Verifying the Installation
Check Postfix Is Listening
ss -lnt sport = :25
ss -lnt sport = :587
Both should show Postfix listening.
Test SMTP Connection
telnet localhost 25
You should see the Postfix banner:
220 mail.example.com ESMTP
Type EHLO test and verify TLS is offered:
250-STARTTLS
Type QUIT to exit.
Test TLS on Submission Port
openssl s_client -connect localhost:587 -starttls smtp
This should show the TLS handshake and certificate details.
Verify MySQL Lookups
Test that Postfix can query the database (after PostfixAdmin creates the tables):
postmap -q example.com mysql:/etc/postfix/mysql-virtual-domains.cf
This returns the domain name if it exists in the database.
Check the Mail Log
tail -f /var/log/mail.log
Send a test email and watch for delivery or error messages.
Summary
After completing these steps:
- Postfix accepts incoming mail on port 25 with opportunistic TLS
- Authenticated users send mail via port 587 with mandatory TLS
- Virtual domains and mailboxes are stored in MySQL
- Mail is delivered to Dovecot via LMTP for final mailbox storage
- The vmail user owns all mailbox files with Maildir format
- MySQL credentials are protected with restrictive file permissions
Postfix is now ready to receive mail, but delivery requires Dovecot for LMTP delivery and IMAP access.