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 complete 2025-08-14 except there is no page for setting up postscreen.

This is version 2 and everthing works.

######################################
# 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.

# 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

Prevent pkg from overwriting compiled ports version of dovecot

pkg lock dovecot

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

DOES 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 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 #
##########################################

06 SPF DMARC And DKIM <- Intro -> 09 Create Virtual Domains