Setup Email Server From Scratch On FreeBSD #2 - 07 RoundCube WebMail
06 SPF DMARC And DKIM <- Intro -> 09 Create Virtual Domains
We believe in data independence, and support others who want data independence.
This tutorial is partially complete 2025-08-07
This is version 2 and everthing works up to and including Roundcube.
######################################
# Setup RoundCube Webmail On FreeBSD #
######################################
# Getting Roundcube setup and installing the modules could be a tutorial by itself.
# Download Roundcube from https://roundcube.net/download/ and get the complete stable version
# roundcubemail-1.6.11-complete.tar.gz.
cd /tmp
wget https://github.com/roundcube/roundcubemail/releases/download/1.6.11/roundcubemail-1.6.11-complete.tar.gz
tar xfzv roundcubemail-1.6.11-complete.tar.gz
mv /tmp/roundcubemail-1.6.11 /usr/local/www/roundcube
chown -R root:wheel /usr/local/www/roundcube
mkdir -p /usr/local/www/roundcube/temp
mkdir -p /usr/local/www/roundcube/logs
chown -R www:www /usr/local/www/roundcube/temp
chown -R www:www /usr/local/www/roundcube/logs
# Add PHP Modules - some of these may not be neeeded, but with these everything works as expected
pkg install php83-pear-DB_ldap2 php83-pecl-imagick php83-pecl-redis php83-mysqli openssl zoneinfo-2025.a
pkg install php83-imap php83-curl php83-zip php83-mbstring php83-bz2 php83-intl php83-gmp php83-gd
pkg install php83-pear wget Crypt_GPG py311-lesscpy php83-composer gnupg
pkg install php83-gettext php83-phar php83-enchant sabredav sabre php83-xmlreader
pkg install gnupg
pear install Crypt_GPG
# We need lessc and lesscpy is not lessc, so p311-lesscpy may not be needed, to be cautious
# install it, or try without.
pkg install py311-lesscpy
# Add /proc filesystem - some python functions require /proc.
mount -t procfs proc /proc
echo 'proc /proc procfs rw 0 0' >> /etc/fstab
# Install less package with npm which has lessc.
pkg install node
pkg install npm
npm install -g less
# Get your timezone for adding to php.ini
ls /usr/share/zoneinfo/*
# Configure php.ini including the timezone - the values in php.ini affect roundcube max attachment size.
nano /usr/local/etc/php.ini
# ---
max_execution_time = 600
max_input_time = 300
max_input_vars = 3000
memory_limit = 256M
post_max_size = 200M
upload_max_filesize = 100M
...
date.timezone = US/Pacific
# ---
# Edit MySQL configuration
nano /usr/local/etc/mysql/my.cnf
# --- add to the bottom of the mysqld section
[mysqld]
...
max_connections = 500
tmp_table_size = 128M
max_heap_table_size = 128M
sort_buffer_size = 2M
slow_query_log = 1
# skip-name-resolve
lower_case_table_names=1
character-set-server = utf8mb4
# collation-server = utf8mb4_general_ci
collation_server = utf8mb4_unicode_520_ci
# ---
service mysql-server restart
# Create Roundcube Database
mysql -u root
# ---
> create database roundcubemail;
> CREATE USER 'roundcube'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'secretpasswd';
> grant all privileges on roundcubemail.* to 'roundcube'@'localhost';
> flush privileges;
> exit;
# ---
# Test the mysql login
mysql -u roundcube -psecretpasswd roundcubemail
> exit;
# Import Database Tables
mysql roundcubemail < /usr/local/www/roundcube/SQL/mysql.initial.sql
# Change Apache DocumentRoot and Directory to Roundcube
nano /usr/local/etc/apache24/Includes/ssl-mail.okbsd.conf
# ---
#DocumentRoot /usr/local/www/okbsd/html/
#<Directory /usr/local/www/okbsd/html/>
# Options FollowSymLinks
# AllowOverride All
# Require all granted
#</Directory>
DocumentRoot /usr/local/www/roundcube/
Alias /webmail "/usr/local/www/roundcube"
Alias /roundcube "/usr/local/www/roundcube"
<Directory /usr/local/www/roundcube/>
# Options FollowSymLinks MultiViews
Options FollowSymLinks
AllowOverride All
# Require all granted
Require all denied
Require ip 10.0.0.0/8 192.168.0.0/16 127.0.0.1 trusted_ip3 trusted_ipv6
</Directory>
# ---
# Don't use Alias /mail "/usr/share/roundcube" because /mail is used for autoconfig.
# MultiViews doesn't seem to be required. It is better to configure Roundcube with
# ssl/https only to force https password encryption.
service php_fpm restart
apachectl restart
# Configure Roundcube imap, smtp, and spell checking.
cd /usr/local/www/roundcube/config
cp /usr/local/www/roundcube/config/config.inc.php.sample /usr/local/www/roundcube/config/config.inc.php
# Please change the DES Key to a random 24 char string and make it exactly 24 chars and set your
# super_secret_password the same as you provided when creating the roundcube database. Please count the
# chars in your key, base 64 defaults to 32 so remove 8 chars.
openssl rand -base64 24
nano /usr/local/www/roundcube/config/config.inc.php
# ---
$config['db_dsnw'] = 'mysql://roundcube:secretpasswd@localhost/roundcubemail';
$config['imap_host'] = 'tls://imap.okbsd.com:143';
$config['smtp_host'] = 'tls://smtp.okbsd.com:587';
// Replace the default key with 24 random characters.
$config['des_key'] = '123456789012345678901234';
// Enable more plugins
$config['plugins'] = ['acl', 'additional_message_headers', 'archive', 'attachment_reminder',
'autologon', 'debug_logger', 'emoticons', 'enigma', 'filesystem_attachments', 'help',
'hide_blockquote', 'http_authentication', 'identicon', 'identity_select', 'jqueryui',
'krb_authentication', 'managesieve', 'markasjunk', 'new_user_dialog', 'new_user_identity',
'newmail_notifier', 'password', 'reconnect', 'redundant_attachments', 'show_additional_headers',
'squirrelmail_usercopy', 'subscriptions_option', 'userinfo', 'vcard_attachments', 'virtuser_file',
'virtuser_query', 'zipdownload','automatic_addressbook'];
// skin name: folder from skins/
$config['enable_spellcheck'] = true;
$config['spellcheck_engine'] = 'enchant';
// this will make max upload 75% so 100M => 75M
$config['max_message_size'] = '134M';
$config['create_default_folders'] = true;
?>
# ---
# Fix directories and permissions - any files can contain mysql passwords should not writable by user
# or group www and not be world readable.
chown root:www /usr/local/www/roundcube/config/config.inc.php
chmod 640 /usr/local/www/roundcube/config/config.inc.php
# Enable Enigma - needed for setting mail users signature and pgp options.
# Create enigma home
mkdir -p /usr/local/www/roundcube/plugins/enigma/home
chown www:www /usr/local/www/roundcube/plugins/enigma/home
chmod 750 /usr/local/www/roundcube/plugins/enigma/home
# Configure Enigma
cd /usr/local/www/roundcube/plugins/enigma
cp /usr/local/www/roundcube/plugins/enigma/config.inc.php.dist /usr/local/www/roundcube/plugins/enigma/config.inc.php
nano /usr/local/www/roundcube/plugins/enigma/config.inc.php
# ---
$config['enigma_pgp_homedir'] = "/usr/local/www/roundcube/plugins/enigma/home";
# ---
chown root:www /usr/local/www/roundcube/plugins/enigma/config.inc.php
chmod 640 /usr/local/www/roundcube/plugins/enigma/config.inc.php
# Automatic Addressbook
cd /tmp
git clone https://github.com/sblaisot/automatic_addressbook.git
mv /tmp/automatic_addressbook /usr/local/www/roundcube/plugins
cd /usr/local/www/roundcube/plugins/automatic_addressbook/SQL
mysql -u roundcube -psecretpasswd roundcubemail < mysql.initial.sql
cd /usr/local/www/roundcube/plugins/automatic_addressbook/config
cp /usr/local/www/roundcube/plugins/automatic_addressbook/config/config.inc.php.dist /usr/local/www/roundcube/plugins/automatic_addressbook/config/config.inc.php
chown root:www /usr/local/www/roundcube/plugins/automatic_addressbook/config/config.inc.php
chmod 640 /usr/local/www/roundcube/plugins/automatic_addressbook/config/config.inc.php
# Test Roundcube Login ...
https://mail.okbsd.com
# Check Settings -> Identities -> Click On User -> If fixed it shows Signature and Manage PGP Keys
# Sieve Install - seive is provided by dovecot-pideonhole and is needed for mail filtering in Roundcube.
echo 'DEFAULT_VERSIONS+=ssl=openssl' >> /etc/make.conf
cd /usr/ports/mail/dovecot-pigeonhole
make
X DOCS
X EXAMPLES
X LDAP
X MANAGESIEVE
X GSSAPI_NONE
make install
nano /usr/local/etc/dovecot/dovecot.conf
# ---
protocols = imap lmtp sieve
# ... append to bottom
service managesieve-login {
inet_listener sieve {
port = 4190
}
inet_listener sieve_deprecated {
port = 2000
}
service_count = 1
process_min_avail = 0
vsz_limit = 64M
}
service managesieve {
process_limit = 1024
}
protocol sieve {
managesieve_max_line_length = 65536
managesieve_implementation_string = dovecot
log_path = /var/log/dovecot-sieve-errors.log
info_log_path = /var/log/dovecot-sieve.log
}
# ---
nano /usr/local/etc/dovecot/conf.d/10-master.conf
# ---
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
}
# ---
nano /usr/local/etc/postfix/main.cf
# ---
mailbox_transport = lmtp:unix:private/dovecot-lmtp
smtputf8_enable = no
# ---
nano /usr/local/etc/dovecot/conf.d/15-lda.conf
# ---
protocol lda {
# Space separated list of plugins to load (default is global mail_plugins).
mail_plugins = $mail_plugins sieve
}
# ---
nano /usr/local/etc/dovecot/conf.d/20-lmtp.conf
# ---
protocol lmtp {
# Space separated list of plugins to load (default is global mail_plugins).
mail_plugins = $mail_plugins quota sieve
}
# ---
nano /usr/local/etc/dovecot/conf.d/10-mail.conf
# ---
mail_location = maildir:~/Maildir
mail_home = /var/vmail/%d/%n
# ---
mkdir -p /usr/local/etc/dovecot/sieve/global
nano /usr/local/etc/dovecot/sieve/before-global.sieve
# ---
require "fileinto";
if header :contains "X-Spam-Flag" "YES"
{
fileinto "Junk";
stop;
}
# ---
sievec /usr/local/etc/dovecot/sieve/before-global.sieve
nano /usr/local/etc/dovecot/conf.d/90-plugin.conf
# ---
plugin {
#setting_name = value
sieve = file:~/sieve;active=~/.dovecot.sieve
sieve_default = /usr/local/etc/dovecot/sieve/default.sieve
sieve_global = /usr/local/etc/dovecot/sieve/global/
sieve_before = /usr/local/etc/dovecot/sieve/before-global.sieve
}
# ---
service postfix restart
service dovecot restart
# Configure managesieve in Roundcube
cd /usr/local/www/roundcube/plugins/managesieve
cp /usr/local/www/roundcube/plugins/managesieve/config.inc.php.dist /usr/local/www/roundcube/plugins/managesieve/config.inc.php
chown root:www /usr/local/www/roundcube/plugins/managesieve/config.inc.php
chmod 640 /usr/local/www/roundcube/plugins/managesieve/config.inc.php
nano /usr/local/www/roundcube/plugins/managesieve/config.inc.php
# --- add or change
// $config['managesieve_host'] = 'localhost';
$config['managesieve_host'] = 'tls://smtp.okbsd.com';
$config['managesieve_usetls'] = true;
$config['managesieve_default'] = '/usr/local/etc/dovecot/sieve/global';
$config['managesieve_debug'] = true;
$config['managesieve_usetls'] = true;
# ---
apachectl restart
# Remove Roundcube Header
nano /usr/local/etc/postfix/smtp_header_checks
# --- create or add
/^User-Agent.*Roundcube Webmail/ IGNORE
# ---
nano /usr/local/etc/postfix/main.cf
# --- append to end of file
smtp_header_checks = regexp:/usr/local/etc/postfix/smtp_header_checks
# ---
postmap /usr/local/etc/postfix/smtp_header_checks
service postfix restart
# Configure RoundCube Password Plugin
cd /usr/local/www/roundcube/plugins/password
cp /usr/local/www/roundcube/plugins/password/config.inc.php.dist /usr/local/www/roundcube/plugins/password/config.inc.php
nano /usr/local/www/roundcube/plugins/password/config.inc.php
# ---
$config['password_algorithm'] = 'dovecot';
$config['password_dovecotpw'] = '/usr/local/bin/doveadm pw -r 5';
$config['password_dovecotpw_method'] = 'ARGON2I';
$config['password_dovecotpw_with_method'] = true;
$config['password_db_dsn'] = 'mysql://postfixadmin:secretpasswd@localhost/postfixadmin';
$config['password_query'] = 'UPDATE mailbox SET password=%P,modified=NOW() WHERE username=%u';
$config['password_strength_driver'] = null;
$config['password_minimum_length'] = 8;
$config['password_minimum_score'] = 0;
# ---
chown root:www /usr/local/www/roundcube/plugins/password/config.inc.php
chmod 640 /usr/local/www/roundcube/plugins/password/config.inc.php
# Enable plugins 'automatic_addressbook','password' in Roundcube remove redundant_attachments
nano /usr/local/www/roundcube/config/config.inc.php
# ---
$config['plugins'] = ['acl', 'additional_message_headers', 'archive', 'attachment_reminder',
'autologon', 'debug_logger', 'emoticons', 'enigma', 'filesystem_attachments', 'help',
'hide_blockquote', 'http_authentication', 'identicon', 'identity_select', 'jqueryui',
'krb_authentication', 'managesieve', 'markasjunk', 'new_user_dialog', 'new_user_identity',
'newmail_notifier', 'password', 'reconnect', 'show_additional_headers',
'squirrelmail_usercopy', 'subscriptions_option', 'userinfo', 'vcard_attachments', 'virtuser_file',
'virtuser_query', 'zipdownload','automatic_addressbook','password'];
//'redundant_attachments',
# ---
# If you have problems double check ARGON2I has been compiled into dovecot.
/usr/local/bin/doveadm pw -l
SHA1 SSHA512 SCRAM-SHA-256 BLF-CRYPT PLAIN HMAC-MD5 OTP SHA512 SHA DES-CRYPT CRYPT SSHA
MD5-CRYPT PLAIN-MD4 PLAIN-MD5 SCRAM-SHA-1 SHA512-CRYPT CLEAR CLEARTEXT ARGON2I ARGON2ID
SSHA256 MD5 PBKDF2 SHA256 CRAM-MD5 PLAIN-TRUNC SHA256-CRYPT SMD5 DIGEST-MD5 LDAP-MD5
# Check that changing password works in roundcube
# I was not able to change passwords because the password_db_dsn default is to use
# 127.0.0.1 and I had put skip-name-resolve in my.cnf.
cat /usr/local/www/roundcube/logs/errors.log
Host '127.0.0.1' is not allowed to connect to this MySQL server
mysql -u postfixadmin -psecretpasswd -h 127.0.0.1 postfixadmin
Host '127.0.0.1' is not allowed to connect to this MySQL server
mysql -u postfixadmin -psecretpasswd -h localhost postfixadmin
> exit
# Two solutions, get rid of skip-name-resolve in my.cnf (preferred) or change the password_db_dsn line.
nano /usr/local/etc/mysql/my.cnf
# ---
# skip-name-resolve
# ---
nano /usr/local/www/roundcube/plugins/password/config.inc.php
# ---
// $config['password_db_dsn'] = 'mysql://postfixadmin:secretpasswd@127.0.0.1/postfixadmin';
$config['password_db_dsn'] = 'mysql://postfixadmin:secretpasswd@localhost/postfixadmin';
# ---
# It is best to disable skip-name-resolve for flexibility but some sources say localhost uses the mysql.sock
# which is more efficient and secure than using the inet port 3306. When at all possible configure to unix
# sockets which have less overhead and are slightly faster.
# Install Calendar
cd /tmp
git clone https://git.kolab.org/diffusion/RPK/roundcubemail-plugins-kolab.git
cd /usr/local/www/roundcube/plugins
cp -r /tmp/roundcubemail-plugins-kolab/plugins/calendar .
cp -r /tmp/roundcubemail-plugins-kolab/plugins/libcalendaring .
cp -r /tmp/roundcubemail-plugins-kolab/plugins/libkolab .
# Install sabre/dav
cd /usr/local/www/roundcube
nano /usr/local/www/roundcube/composer.json
# ---
{
"name": "roundcube/roundcubemail",
"version": "1.6.11",
"description": "The Roundcube Webmail suite",
"license": "GPL-3.0-or-later",
"repositories": [
{
"type": "composer",
"url": "https://plugins.roundcube.net"
}
],
"require": {
"php": ">=7.3.0",
"pear/pear-core-minimal": "~1.10.1",
"pear/auth_sasl": "~1.1.0",
"pear/mail_mime": "~1.10.0",
"pear/net_smtp": "~1.10.0",
"pear/crypt_gpg": "~1.6.3",
"pear/net_sieve": "~1.4.5",
"roundcube/plugin-installer": "~0.3.1",
"roundcube/rtf-html-php": "~2.1",
"masterminds/html5": "~2.7.0",
"bacon/bacon-qr-code": "^2.0.0",
"guzzlehttp/guzzle": "^7.3.0",
"kolab/net_ldap3": "~1.1.4",
},
"suggest": {
"bjeavons/zxcvbn-php": "^1.0 required for Zxcvbn password strength driver"
},
"config": {
"allow-plugins": {
"roundcube/plugin-installer": true
}
}
}
# ---
# Run composer to install the dependencies. Initially I just ran composer require sabre/dav but
# composer said it could not find the version of roundcube/roundcube so defaulted to 1.0.0 which
# downgraded the install. Adding "version": "1.6.11", is the above file fixed this and it would
# be better to do this first.
composer require sabre/dav ~4.7.0 sabre/vobject ^4.5.7 --with-all-dependencies
# Test what versions were installed.
grep sabre composer.json
"sabre/dav": "~4.7.0",
"sabre/vobject": "^4.5.7"
# Copy calendar configuration and set permissions.
cd /usr/local/www/roundcube/plugins/calendar
cp /usr/local/www/roundcube/plugins/calendar/config.inc.php.dist /usr/local/www/roundcube/plugins/calendar/config.inc.php
chown root:www /usr/local/www/roundcube/plugins/calendar/config.inc.php
chmod 640 /usr/local/www/roundcube/plugins/calendar/config.inc.php
# Initialize the calendar database
cd /usr/local/www/roundcube/plugins/libkolab/SQL
mysql -u roundcube -psuper_secret_password roundcubemail < /usr/local/www/roundcube/plugins/libkolab/SQL/mysql.initial.sql
cd /usr/local/www/roundcube
bin/initdb.sh --dir=plugins/calendar/drivers/database/SQL
# Generate the compiled css files with lessc, this will generate a message of missing svg files, but calendar works.
lessc -x /usr/local/www/roundcube/plugins/libkolab/skins/elastic/libkolab.less > /usr/local/www/roundcube/plugins/libkolab/skins/elastic/libkolab.min.css
# Enable calendar in Roundcube main configuration file.
# To customize displayed product_name based on the virtual server used include the routine shown here.
# Creating default folders is a nice touch. If you do not use the default path in apache set url_base in the
# roundcube configuration. Roundcube max "attachments" size is 2M by default, use max_message_size to
# override. Also, default values in php.ini need to be modified to increase max attachment size.
nano /usr/local/www/roundcube/config/config.inc.php
# ---
...
$config['db_dsnw'] = 'mysql://roundcube:secretpasswd@localhost/roundcubemail';
$config['imap_host'] = 'tls://imap.okbsd.com:143';
$config['smtp_host'] = 'tls://smtp.okbsd.com:587';
$config['smtp_user'] = '%u';
$config['smtp_pass'] = '%p';
$config['support_url'] = '';
...
// Name your service. This is displayed on the login screen and in the window title
$sn = $_SERVER['SERVER_NAME'];
if (preg_match('/coragarden\.com/', $sn)) {
$config['product_name'] = 'Cora Garden Webmail';
} else if (preg_match('/okbsd\.com/', $sn)) {
$config['product_name'] = 'OkBSD Webmail';
} else if (preg_match('/okdeb\.com/', $sn)) {
$config['product_name'] = 'OkDeb Webmail';
} else {
$config['product_name'] = 'Roundcube Webmail';
}
$config['des_key'] = '123456789012345678901234';
$config['plugins'] = ['acl', 'additional_message_headers', 'archive', 'attachment_reminder',
'autologon', 'debug_logger', 'emoticons', 'enigma', 'filesystem_attachments', 'help',
'hide_blockquote', 'http_authentication', 'identicon', 'identity_select', 'jqueryui',
'krb_authentication', 'managesieve', 'markasjunk', 'new_user_dialog', 'new_user_identity',
'newmail_notifier', 'password', 'reconnect', 'show_additional_headers',
'squirrelmail_usercopy', 'subscriptions_option', 'userinfo', 'vcard_attachments', 'virtuser_file',
'virtuser_query', 'zipdownload', 'automatic_addressbook', 'calendar', 'password'];
// 'redundant_attachments'
// skin name: folder from skins/
$config['skin'] = 'elastic';
$config['max_pagesize'] = 1000;
$config['enable_spellcheck'] = true;
$config['spellcheck_engine'] = 'enchant';
// this will make max upload 75% so 100M => 75M or 134M => 100M
$config['max_message_size'] = '134M';
$config['create_default_folders'] = true;
// $config['url_base'] = '/mail-login/';
?>
# ---
# Test that everything works as expected
# Install the larry skin which are compatable with calendar.
cd /tmp
git clone https://github.com/roundcube/larry.git
cp -r larry /var/www/roundcube/skins/
cd /var/www/roundcube/config
# You can now select which skin you'd like.
Settings -> Preferences - > User Interface
# There are many more roundcube skins but most require payment.
# Free colorful larry skins https://github.com/roundcube/larry.git that
# DO NOT WORK WITH CALENDAR!
cd /tmp
git clone https://github.com/texxasrulez/roundcube_skins.git
cd /tmp/roundcube_skins/skins
mv * /var/www/roundcube/skins
# Try html5_notifier, I didn't see any notifications when I used it.
# html5_notifier doesn't seem to work under Debian with Firefox...
cd /tmp
git clone https://github.com/stremlau/html5_notifier.git
mv /tmp/html5_notifier /var/www/roundcube/plugins/
cd /var/www/roundcube/plugins/html5_notifier/config
cp /var/www/roundcube/plugins/html5_notifier/config/config.inc.php.dist \
/var/www/roundcube/plugins/html5_notifier/config/config.inc.php
nano /var/www/roundcube/config/config.inc.php
# --- append to the end of modules list
'virtuser_query', 'zipdownload', 'automatic_addressbook', 'calendar','password', 'html5_notifier'];
# ---
# A vexiadmin plugin allows users to manage spam filtering
# Roundcube has an extensive set of plugins which are beyond the scope of this setup
# tutorial. It would be interesting to set this up with caldav cardav and maybe webdav
# could be integrated for sharing larger files.
# Next - Multiple "Virtual" Mail Domains