Setup Email Server From Scratch On FreeBSD #2 - 05 PostfixAdmin

04 Dovecot IMAP <- Intro -> 06 SPF DMARC And DKIM

We believe in data independence, and support others who want data independence.
This tutorial is complete 2025-08-14 except there is no page for setting up postscreen.

This is version 2 and everthing works.

#################
# Postfix Admin #
#################

cd /usr/local/www
git clone https://github.com/postfixadmin/postfixadmin.git
cd /usr/local/www/postfixadmin
git checkout postfixadmin-3.3.10

mysql
> create database postfixadmin; > CREATE USER 'postfixadmin'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'secretpasswd'; > GRANT ALL PRIVILEGES ON `postfixadmin` . * TO 'postfixadmin'@'localhost'; > FLUSH PRIVILEGES; exit;

Install php and php modules dependencies - some of these won't actually be used but the postfixadmin setup page will warn you if they're missing.

pkg install php83 mod_php83 php83-bcmath php83-bz2 php83-curl php83-gd php83-gmp php83-imap php83-intl
pkg install php83-ldap php83-mbstring php83-mysqli php83-pdo php83-pdo_pgsql php83-pdo_sqlite php83-pecl-imagick
pkg install php83-pecl-redis php83-pgsql php83-session php83-tokenizer php83-xml php83-zip php83-zlib php83-pdo_mysql

Once fixing dependencies restart php_fpm and apache or postfixadmin may throw errors.

service php_fpm restart
apachectl restart

##########################
# Configure Postfixadmin #
##########################

nano /usr/local/www/postfixadmin/config.local.php
$CONF['configured'] = true; $CONF['database_type'] = 'mysqli'; $CONF['database_host'] = 'localhost'; $CONF['database_socket'] = '/tmp/mysql.sock'; $CONF['database_port'] = '3306'; $CONF['database_user'] = 'postfixadmin'; $CONF['database_password'] = 'secretpasswd'; $CONF['database_name'] = 'postfixadmin'; $CONF['encrypt'] = 'dovecot:ARGON2I'; $CONF['dovecotpw'] = "/usr/local/bin/doveadm pw -r 5"; $CONF['base_url'] = '/admin-login/'; // $CONF['setup_password'] = '';

mkdir /usr/local/www/postfixadmin/templates_c
chown -R www:www /usr/local/www/postfixadmin/templates_c

####################################
# Configure Postfixadmin in Apache #
####################################

Add the directory to the virtual host and secure it so only allowed ip's have access. Check apache docs on how to setup user authentication if you wish. This doesn't provide total security but reduces the attack surface. Login is still protected by https encrypted username and password. It is also a good idea to configure this with a different alias or as a different virtual host separate from the website and roundcube login path. You may also consider having apache listen on another port and configure postfixadmin only for that virtual host port combination.

nano /usr/local/etc/apache24/Includes/ssl-mail.okbsd.conf
Alias /admin-login /usr/share/postfixadmin/public <Directory /usr/share/postfixadmin/> Options FollowSymLinks MultiViews AllowOverride All Require all denied Require ip allowed_ip1 allowed_net2/cidr allowed_ipv6 allowed_ipv6/cidr </Directory>

Go to the setup page and create a setup password.

https://mail.okbsd.com/admin-login/setup.php

Copy the password to conf.local.php

nano /usr/local/www/postfixadmin/config.local.php
$CONF['setup_password'] = '$2y$10$fds_SAMPLE_HASH_ONLY_ddlfdfudedEFDFGdsnjalksd';

Reload the setup page and wait a few seconds.

Navigate to the setup.php page again and enter the setup password you created.

https://mail.okbsd.com/admin-login/setup.php

Setup Administrator password

Admin: jack@okbsd.com
Password: mysecret
Password (again) : mysecret

The following 'super-admin' accounts have already been added to the database.
jack@okbsd.com

Click the link at the bottom 'login to PostfixAdmin'.

https://mail.okbsd.com/admin-login

Login: jack@okbsd.com
Password: mysecret

Setup statistics in Dovecot

nano /usr/local/etc/dovecot/conf.d/10-master.conf
--- add to end of file ---
service stats { unix_listener stats-reader { user = www group = www mode = 0660 } unix_listener stats-writer { user = www group = www mode = 0660 } }

Add www to group dovecot and set permissions

pw groupmod dovecot -m www
service dovecot restart
chown www:www /var/run/dovecot/stats-reader /var/run/dovecot/stats-writer
chmod 660 /var/run/dovecot/stats-reader /var/run/dovecot/stats-writer

Configure Postfix to use MySQL

nano /usr/local/etc/postfix/main.cf
--- add to end of file ---
mailbox_transport = lmtp:unix:private/dovecot-lmtp smtputf8_enable = no virtual_mailbox_domains = proxy:mysql:/usr/local/etc/postfix/sql/mysql_virtual_domains_maps.cf virtual_mailbox_maps = proxy:mysql:/usr/local/etc/postfix/sql/mysql_virtual_mailbox_maps.cf, proxy:mysql:/usr/local/etc/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf virtual_alias_maps = proxy:mysql:/usr/local/etc/postfix/sql/mysql_virtual_alias_maps.cf, proxy:mysql:/usr/local/etc/postfix/sql/mysql_virtual_alias_domain_maps.cf, proxy:mysql:/usr/local/etc/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf virtual_transport = lmtp:unix:private/dovecot-lmtp

mkdir -p /usr/local/etc/postfix/sql
nano /usr/local/etc/postfix/sql/mysql_virtual_domains_maps.cf
user = postfixadmin password = secretpasswd hosts = localhost dbname = postfixadmin query = SELECT domain FROM domain WHERE domain='%s' AND active = '1'

nano /usr/local/etc/postfix/sql/mysql_virtual_mailbox_maps.cf
user = postfixadmin password = secretpasswd hosts = localhost dbname = postfixadmin query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'

nano /etc/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf
user = postfixadmin password = secertpasswd 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'

nano /etc/postfix/sql/mysql_virtual_alias_maps.cf
user = postfixadmin password = secretpasswd hosts = localhost dbname = postfixadmin query = SELECT goto FROM alias WHERE address='%s' AND active = '1'

nano /etc/postfix/sql/mysql_virtual_alias_domain_maps.cf
user = postfixadmin password = secretpasswd 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'

nano /etc/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf
user = postfixadmin password = secretpasswd 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'

Set restrictive file permissions since the secretpasswd is in the files.

chown -R root:postfix /usr/local/etc/postfix/sql
chmod 0640 /usr/local/etc/postfix/sql/*
chmod 0750 /usr/local/etc/postfix/sql

Change postfix destination

postconf mydestination
mydestination = $mydomain, $myhostname, localhost.$mydomain, localhost

postconf -e "mydestination = \$myhostname, localhost.\$mydomain, localhost"

nano /usr/local/etc/postfix/main.cf
--- add to end of file - use <control> - W <control> - V to go to end of file. ---
virtual_mailbox_base = /var/vmail virtual_minimum_uid = 2000 virtual_uid_maps = static:2000 virtual_gid_maps = static:2000

service postfix restart

check if vmail user and group have already been added

grep vmail /etc/group /etc/passwd
/etc/group:vmail:*:2000:
/etc/passwd:vmail:*:2000:2000::/nonexistent:/usr/sbin/nologin

If not add them.

pw useradd -c "" -n vmail -s /usr/bin/nologin -d /nonexistent -u 2000

Create virtual mailbox directories.

mkdir /var/vmail/
chown -R vmail:vmail /var/vmail

Configure Dovecot

Add mail_home to 10-mail.conf

nano /usr/local/etc/dovecot/conf.d/10-mail.conf
mail_location = maildir:~/Maildir mail_home = /var/vmail/%d/%n

Change username_format to %u and enable auth-sql.conf.ext and disable auth-system.conf.ext

nano /usr/local/etc/dovecot/conf.d/10-auth.conf
auth_username_format = %u auth_default_realm = okbsd.com #!include auth-system.conf.ext !include auth-sql.conf.ext auth_debug = yes auth_debug_passwords = yes

Change the following

nano /usr/local/etc/dovecot/dovecot-sql.conf.ext
driver = mysql connect = host=localhost dbname=postfixadmin user=postfixadmin password=secretpasswd default_pass_scheme = ARGON2I password_query = SELECT username AS user,password FROM mailbox WHERE username = '%u' AND active='1' user_query = SELECT CONCAT('/var/vmail/', maildir) AS home, 2000 AS uid, 2000 AS gid, CONCAT('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND active='1' iterate_query = SELECT username AS user FROM mailbox

service dovecot restart

At this point Thunderbird user@okbsd.com may get disconnected because Dovecot will be using the new virtual mailbox database and jack@okbsd.com doesn't exist there.

Add domain and mailboxes
Add Domain
Domain: okbsd.com
Description: my first mail domain
Aliases: 0
Mailboxes: 0
Active: x
Add default mail aliaes: UNCHECK THE BOX
Pass expires: 3650

RFC requires that the domain has a postmaster account, and it's a good idea to set a catch all to alias unknown accounts and send to postmaster.

Virtual List -> Add Mailbox
Add Mail Account postmaster@okbsd.com
Username: postmaster
Domain: okbsd.com
Password: ****
Password: ****
Name: OkBSD Postmaster
Quota: 0
Active: x
Send Welcome email: no
Add Mailbox

Virtual List -> Add Alias
Alias: *
Domain: okbsd.com
To: postmaster@okbsd.com

Virtual List -> Add Mailbox
Add Mail Account jack@okbsd.com
Username: jack
Domain: okbsd.com
Password: ****
Password: ****
Name: Jack Pumpkin
Quota: 0
Active: x
Send Welcome email: no
Add Mailbox

Add jack@okbsd.com to Thunderbird and test send and recieve to the account created entirely by PostfixAdmin

Forgot PostfixAdmin Password!

If you ever lost access to PostfixAdmin - I did because when I went through setup I hadn't enabled ARGON2I, when I enabled later my password was wrong. I checked the database and the password field had something like {1}$ which isn't ARGON2I.

mysql
use postfixadmin;
select * from admin;

Disabled the setup_password, enabled ARGGON2I and went to setup.php again generated a new setup password, added a temporary superadmin, logged in, updated the old admin password, logged out and back in, deleted the temporary superadmin. Upon checking the database the password field now starts with {ARGON2I}$argon2i$ which is correct.

########################################
# SUGGEST BACKUP WORKING CONFIGURATION #
########################################

If everything works as expected it is a good idea to backup the configurations now. We've done a lot of changes and made a lot of progress so let's make sure if we have problems later we can roll back to a working configuration.

cd /usr/local/etc
tar cfzv dovecot_backup.tgz dovecot
tar cfzv postfix_backup.tgz postfix
tar cfzv apache24_backup.tgz apache24

cd /usr/local/www
tar cfzv postfixadmin_backup.tgz postfixadmin
tar cfzv okbsd_backup.tgz okbsd

If you have other users on your system make these tgz files unreadable and/or move them to a safe directory.

chmod 600 <filename.tgz>

#######################################################################################
# Next: Setup spam filtering and configure SPF, DKIM, and DMARC email authentication. #
#######################################################################################

04 Dovecot IMAP <- Intro -> 06 SPF DMARC And DKIM