Grommunio Mail Server
This material is work-in-progress ... This is a work in progress |
HOWTO: Install AlpineLinux Mail Server with Grommunio
This tutorial outlines the steps for setting up a mail server on Alpine Linux using Grommunio, a modern, open-source groupware solution that supports email and calendar services. The installation includes MariaDB, Nginx, PHP, Postfix, and other components necessary for a fully functioning mail server.
Prerequisites
Before proceeding with the installation, ensure you have a fresh Alpine Linux system setup. You'll need root privileges to execute these commands.
Procedure
- Install and configure MariaDB
- MariaDB performance tuning (optional)
- Install and configure Nginx
- Install and configure PHP
- Install and configure Postfix
- Install and configure Grommunio
- Configure Valkey (Redis replacement)
- Install and configure Rspamd
- Finalize and verify installation
1. Install and Configure MariaDB
Install MariaDB
To start, install MariaDB and necessary utilities
apk add mariadb mariadb-client mariadb-server-utils
Define the variables used later in the setup setup and configuration.
The default datapath is /var/lib/mysql and contains the entire database. If you prefer another location define it here and create a symlink. In any case, the grommunio database will be rather small as it only contains the configuration.
DB_DATA_PATH="/srv/mysql" DB_ROOT_PASS="Passw0rd1" DB_USER="admin" DB_PASS="Passw0rd2"
Setup the default system tables:
sudo mysql_install_db --user=mysql --datadir=${DB_DATA_PATH}
Set a symlink to the standard mysql data directory for compatibility reason:
ln -s /srv/mysql /var/lib/mysql
Restart service:
rc-service mariadb restart
Run the built-in security script:
Set the new root password to the one defined above and answer everything with 'y'
sudo mysql_secure_installation
Create MariaDB User for Grommunio
Create a new user for Grommunio and assign privileges:
echo "GRANT ALL ON *.* TO ${DB_USER}@'127.0.0.1' IDENTIFIED BY '${DB_PASS}' WITH GRANT OPTION;" > /tmp/sql echo "GRANT ALL ON *.* TO ${DB_USER}@'localhost' IDENTIFIED BY '${DB_PASS}' WITH GRANT OPTION;" >> /tmp/sql echo "GRANT ALL ON *.* TO ${DB_USER}@'::1' IDENTIFIED BY '${DB_PASS}' WITH GRANT OPTION;" >> /tmp/sql echo "DELETE FROM mysql.user WHERE User=;" >> /tmp/sql echo "FLUSH PRIVILEGES;" >> /tmp/sql cat /tmp/sql | mysql -u root --password="${DB_ROOT_PASS}"
Configure MariaDB for Grommunio
Edit the MariaDB configuration for better performance:
vi /etc/my.cnf.d/mariadb-server.cnf
Add the following configuration:
[mysqld] #skip-networking ## Configuration for grommunio (most values are default) innodb_log_buffer_size=16M innodb_log_file_size=32M innodb_read_io_threads=4 innodb_write_io_threads=4 join_buffer_size=512K query_cache_size=0 query_cache_type=0 query_cache_limit=2M # Activate performance schema performance_schema=ON # Bind to localhost only bind-address = 127.0.0.1 # Disable DNS lookups as we only use localhost connections skip-name-resolve=ON
Create a default charset configuration for MariaDB:
cat > /etc/my.cnf.d/mariadb-server-default-charset.cnf << EOF [client] default-character-set = utf8mb4 [mysqld] collation_server = utf8mb4_general_ci character_set_server = utf8mb4 [mysql] default-character-set = utf8mb4 EOF
Restart MariaDB and enable it to start on boot:
rc-update add mariadb default rc-service mariadb restart
Verify MariaDB Setup
Check if the MariaDB listener is running and bound to the correct interface (127.0.0.1):
ss -tulpn
Create Grommunio Database
Define the database parameters and create the Grommunio database:
MYSQL_HOST="localhost" MYSQL_USER="grommunio" MYSQL_PASS="Passw0rd3" MYSQL_DB="grommunio"
echo "create database $MYSQL_DB character set 'utf8mb4';" > /tmp/sql echo "grant select, insert, update, delete, create, drop, index, alter, create temporary tables, lock tables on $MYSQL_DB.* TO $MYSQL_USER@$MYSQL_HOST identified by '$MYSQL_PASS';" >> /tmp/sql echo "flush privileges;" >> /tmp/sql cat /tmp/sql | mysql -u admin --password="${DB_PASS}"
Test the database connection:
mysql -hlocalhost -u grommunio -p${MYSQL_PASS} grommunio
2. MariaDB Performance Tuning (Optional)
To assist us in checking and tuning the configuration as per our specific needs, we can install mysqltuner, a script that will provide suggestions to improve the performance of our database server and increase its stability
Download MySQLTuner:
wget -v https://raw.githubusercontent.com/major/MySQLTuner-perl/master/mysqltuner.pl -O /tmp/mysqltuner.pl
Move it to the correct directory and set permissions:
mv /tmp/mysqltuner.pl /usr/local/bin/mysqltuner.pl chmod 755 /usr/local/bin/mysqltuner.pl
Install perl:
NOTE: The perl-doc package is installed because it's the only one containing the perldiag.pod required by mysqltuner.pl
apk add perl perl-doc
Execute the script. You will be prompted to enter the credentials of your administrative MariaDB account:
/usr/local/bin/mysqltuner.pl --user admin --pass ${DB_PASS}
3. Install and Configure Nginx
Install Nginx
Install the necessary Nginx modules:
apk add nginx nginx-mod-http-headers-more nginx-mod-http-vts nginx-mod-http-brotli
Configure Nginx
Backup the original Nginx configuration and edit it for security headers and TLS settings:
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.orig vi /etc/nginx/nginx.conf
Add the following configuration:
#error_log /var/log/nginx/error.log warn; error_log syslog:server=unix:/dev/log,facility=local2,nohostname warn; ### Common headers for security # TODO: Set temporary to a lower value until we are sure it's working #more_set_headers "Strict-Transport-Security : max-age=15768000; includeSubDomains; preload"; more_set_headers "Strict-Transport-Security : max-age=2592000; includeSubDomains;"; more_set_headers "X-Frame-Options : SAMEORIGIN"; more_set_headers "Content-Security-Policy : default-src https: data: 'unsafe-inline' 'unsafe-eval' always"; more_set_headers "X-Xss-Protection : 1; mode=block"; more_set_headers "X-Content-Type-Options : nosniff"; more_set_headers "Referrer-Policy : strict-origin-when-cross-origin"; more_set_headers "Server : Follow the white rabbit."; ### TLS settings ssl_protocols TLSv1.2 TLSv1.3; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; log_format main_ssl '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' 'client_ciphers="$ssl_ciphers" client_curves="$ssl_curves"'; #access_log /var/log/nginx/access.log main_ssl; # Disabled for performance #access_log syslog:server=unix:/dev/log,facility=local2,nohostname main; # Disabled for performance access_log off;
Disable the default page by renaming it:
mv /etc/nginx/http.d/default.conf /etc/nginx/http.d/default.orig
Restart Nginx and enable it to start on boot:
rc-update add nginx rc-service nginx restart
4. Install and Configure PHP
Install PHP
Install the basic php packages. The modules are installed as grommunio dependencies:
apk add php83 php83-fpm
Disable default fpm conf:
mv /etc/php83/php-fpm.d/www.conf /etc/php83/php-fpm.d/www.conf.default
Harden PHP Configuration
Backup config file /etc/php83/php.ini
cp /etc/php83/php.ini /etc/php83/php-fpm.d/php.ini.orig
Disable remote PHP code execution:
sed 's/^;\?\(allow_url_fopen\).*/\1 = Off/' -i /etc/php83/php.ini sed 's/^;\?\(allow_url_include\).*/\1 = Off/' -i /etc/php83/php.ini
Disable information leakage:
sed 's/^;\?\(expose_php\).*/\1 = Off/' -i /etc/php83/php.ini
Configure Error handling:
sed 's/^;\?\(display_errors\).*/\1 = Off/' -i /etc/php83/php.ini sed 's/^;\?\(display_startup_errors\).*/\1 = Off/' -i /etc/php83/php.ini sed 's/^;\?\(log_errors\).*/\1 = On/' -i /etc/php83/php.ini sed 's/^;\?\(error_log = syslog\).*/\1/' -i /etc/php83/php.ini
PHP Resource Control (Optional):
#sed 's/^\(max_execution_time\).*/\1 = 25/' -i /etc/php83/php.ini #sed 's/^\(max_input_time\).*/\1 = 25/' -i /etc/php83/php.ini #sed 's/^\(memory_limit\).*/\1 = 30M/' -i /etc/php83/php.ini #sed 's/^\(post_max_size\).*/\1 = 1M/' -i /etc/php83/php.ini #sed 's/^;\?\(max_input_vars\).*/\1 = 1000/' -i /etc/php83/php.ini
Disable vulnerable functions:
sed 's/^\(disable_functions\).*/\1 = apache_note, apache_setenv, chgrp, curl_multi_exec, define_sys, define_syslog_variables, debugger_off, debugger_on, diskfreespace, _getppid, escapeshellarg, escapeshellcmd, exec, getmyuid, ini_restore, leak, listen, parse_ini_file, passthru, pcntl_alarm, pcntl_async_signals, pcntl_exec, pcntl_fork, pcntl_get_last_error, pcntl_getpriority, pcntl_setpriority, pcntl_signal, pcntl_signal_dispatch, pcntl_signal_get_handler, pcntl_sigprocmask, pcntl_sigtimedwait, pcntl_sigwaitinfo, pcntl_strerror, pcntl_unshare, pcntl_wait, pcntl_waitpid, pcntl_wexitstatus, pcntl_wifcontinued, pcntl_wifexited, pcntl_wifsignaled, pcntl_wifstopped, pcntl_wstopsig, pcntl_wtermsig, posix, posix_ctermid, posix_getcwd, posix_getegid, posix_geteuid, posix_getgid, posix_getgrgid, posix_getgrnam, posix_getgroups, posix_getlogin, posix_getpgid, posix_getpgrp, posix_getpid, posix_getpwnam, posix_getpwuid, posix_getrlimit, posix_getsid, posix_isatty, posix_kill, posix_mkfifo, posix_setegid, posix_seteuid, posix_setgid, posix_setpgid, posix_setsid, posix_setuid, posix_times, posix_ttyname, posix_uname, popen, proc_close, proc_get_status, proc_nice, proc_open, proc_terminate, shell_exec, show_source, system, url_exec/' -i /etc/php83/php.ini
Harden Session Security:
sed 's/^;\?\(session.use_strict_mode\).*/\1 = 1/' -i /etc/php83/php.ini sed 's/^;\?\(session.use_cookies\).*/\1 = 1/' -i /etc/php83/php.ini sed 's/^;\?\(session.cookie_secure\).*/\1 = 1/' -i /etc/php83/php.ini sed 's/^;\?\(session.use_only_cookies\).*/\1 = 1/' -i /etc/php83/php.ini sed 's/^;\?\(session.cookie_httponly\).*/\1 = 1/' -i /etc/php83/php.ini
5. Install and Configure Postfix
Install Postfix
Install the required packages:
apk add postfix postfix-mysql postfix-pcre
Configure Postfix
First backup the original files:
mv /etc/postfix/main.cf /etc/postfix/main.cf.orig mv /etc/postfix/master.cf /etc/postfix/master.cf.orig mv /etc/postfix/header_checks /etc/postfix/header_checks.orig
Download the configuration files
TODO
Copy the prepared configuration files. Ensure you adapt at least the following values:
main.cf: myhostname, mynetworks, smtp_tls_chain_files, smtpd_tls_chain_files, local_recipient_maps, mydestination, virtual_*, relayhost, mua_sender_restrictions, smtpd_recipient_restrictions master.cf: smtpd_tls_chain_files=/etc/ssl/private/internal-server-key-and-cert.pem
Create the required postmap files:
newaliases postmap /etc/postfix/transport
Run Postfix setup:
newaliases postmap /etc/postfix/transport
Enable the postfix service and restart:
rc-update add postfix rc-service postfix restart
Verify Postfix Logs
Check the Postfix logs for any errors:
tail -f /var/log/maillog
6. Install and Configure Grommunio
Enable IPv6
First of all, turn on ipv6 which is mandatory for grommunio daemons
Edit `/etc/hosts` to include IPv6 localhost:
vi /etc/hosts ----- ::1 localhost ipv6-localhost ipv6-loopback
Ensure IPv6 is enabled in `/etc/sysctl.conf`:
sed -i 's/^net\.ipv6\.conf\..*\.disable_ipv6\s=\s1/#&/' /etc/sysctl.conf sysctl -p ping ::1 # Test if IPv6 is working
Specify Database Parameters
Specify DB parameters if necessary (already specified above in step 1)
MYSQL_HOST="localhost" MYSQL_USER="grommunio" MYSQL_PASS="Passw0rd3" MYSQL_DB="grommunio"
Specify Internal FQDN, Mail Domain, and Relayhost
The FQDN is for example used by Outlook clients to connect. This name will have to be present in the used certificate.
The default Mail Domain is for example used for Non-Delivery Reports and for generation of some simple TLS certificates. Specify ONLY ONE domain here.
Adjust the following for your specific setup:
FQDN="mail.example.local" MAILDOMAIN="example.com" RELAYHOST="123.123.123.1" ADMIN_PASS="Passw0rd4"
Install Dependencies and Grommunio Packages
Install necessary dependencies (util-linux-login is required to get pam.d working):
apk add valkey valkey-cli cyrus-sasl cyrus-sasl-login util-linux-login
Install grommunio packages:
apk add grommunio-gromox grommunio-web grommunio-admin-api grommunio-admin-web grommunio-index grommunio-error-pages
Optionally, install deprecated ActiveSync if needed:
apk add grommunio-dav grommunio-sync
Move Mail Storage to Another Disk
The directory `/var/lib/gromox` will be the largest directory containing all the mails and attachments. We therefore relocate it to another disk and create a symlink:
mv /var/lib/gromox /srv/gromox ln -s /srv/gromox /var/lib/gromox
Enable Required Services
Enable all required services:
rc-update add grommunio-admin-api rc-update add gromox-delivery rc-update add gromox-delivery-queue rc-update add gromox-event rc-update add gromox-http rc-update add gromox-imap rc-update add gromox-midb rc-update add gromox-pop3 rc-update add gromox-timer rc-update add gromox-zcore rc-update add valkey@grommunio rc-update add php-fpm83 rc-update add saslauthd
Configure Grommunio
Modify the configuration files with the custom parameters specified above.
Configure gromox *.cfg:
sed -i "s/mail.example.local/${FQDN}/g" /etc/gromox/*.cfg sed -i "s/example.com/${MAILDOMAIN}/g" /etc/gromox/*.cfg
Configure gromox adaptor:
sed -i "s/<password>/${MYSQL_PASS}/g" /etc/gromox/mysql_adaptor.cfg cat /etc/gromox/mysql_adaptor.cfg
Configure autodiscovery:
sed -i "s/mail.example.local/${FQDN}/g" /etc/gromox/autodiscover.ini sed -i "s/<password>/${MYSQL_PASS}/g" /etc/gromox/autodiscover.ini cat /etc/gromox/autodiscover.ini
Prepare additional postfix file for sender maps:
cp -p /etc/postfix/grommunio-virtual-mailbox-maps.cf /etc/postfix/grommunio-virtual-mailbox-sender-maps.cf sed -i '/^query =/d' /etc/postfix/grommunio-virtual-mailbox-sender-maps.cf echo "query = SELECT username FROM users WHERE username='%s' UNION SELECT aliasname FROM aliases WHERE mainname='%s'" >> /etc/postfix/grommunio-virtual-mailbox-sender-maps.cf
Configure postfix files:
sed -i "s/<password>/${MYSQL_PASS}/g" /etc/postfix/grommunio*.cf
Configure TLS certificates:
sed -i "s/mail.example.local/${FQDN}/g" /etc/grommunio-common/nginx/ssl_certificate.conf sed -i "s/mail.example.local/${FQDN}/g" /etc/grommunio-admin-common/nginx-ssl.conf
Link TLS certificate configuration:
Since both ssl files are identical it might make more sense to link them
ln -s /etc/grommunio-common/nginx/ssl_certificate.conf /etc/grommunio-admin-common/nginx-ssl.conf
Configure grommunio admin:
sed -i "s/mail.example.local/${FQDN}/g" /etc/grommunio-admin-common/config.json sed -i "s/<password>/${MYSQL_PASS}/g" /etc/grommunio-admin-api/conf.d/database.yaml cat /etc/grommunio-admin-api/conf.d/database.yaml
Prepare external or self-signed certificate and put them to:
/etc/ssl/certs/${FQDN}.cert.pem /etc/ssl/private/${FQDN}.key.pem
Concatenate key and cert as needed by postfix:
cat /etc/ssl/private/${FQDN}.key.pem \ /etc/ssl/certs/${FQDN}.cert.pem \ > /etc/ssl/private/${FQDN}.key_cert.pem chmod 640 /etc/ssl/private/*.key_cert.pem chown root:ssl-cert /etc/ssl/private/*.key_cert.pem ls -al /etc/ssl/private/
Add gromox to ssl-cert (or another) group allowed to read the private keys:
addgroup gromox ssl-cert
Retrieve the certificate fingerprint:
NOTE: This is only required if you work with a relay server authenticating the client cert.
Add the output to the relay_clientcerts file on the relay server and hash it with the postmap command.
openssl x509 -noout -fingerprint -sha384 -in /etc/ssl/certs/${FQDN}.cert.pem
Configure PAM for SMTP:
mv /etc/pam.d/smtp /etc/pam.d/smtp.orig cat > /etc/pam.d/smtp <<EOF #%PAM-1.0 # config for grommunio auth services auth required pam_gromox.so service=smtp account required pam_permit.so EOF
Configure SASL authentication:
cat > /etc/conf.d/saslauthd <<EOF # Configuration for /etc/init.d/saslauthd SASLAUTHD_OPTS="-a pam -r" EOF
SASL configuration for SMTP service:
mkdir /etc/sasl2 cat > /etc/sasl2/smtpd.conf <<EOF pwcheck_method: saslauthd mech_list: plain login EOF chmod 600 /etc/sasl2/smtpd.conf
In case you migrate from another server replace X500-org-name with the original one:
X500_ORG_NAME_OLD=$(grep 'x500_org_name=' /etc/gromox/zcore.cfg | cut -d= -f2) X500_ORG_NAME_NEW=<original-key> sed -i "s/$X500_ORG_NAME_OLD/$X500_ORG_NAME_NEW/g" /etc/gromox/*
Initialize the Database and Set Admin Password
Initialize the database:
gromox-dbop -C
Set the Grommunio admin password:
grommunio-admin passwd --password "${ADMIN_PASS}"
Add grommunio to the adm group to enable admin UI to read `/var/log/maillog`.
NOTE: Additional custom patch is needed to make the log monitoring work
addgroup grommunio adm
Configure Firewall Ports
Open the necessary firewall ports:
25/tcp 80/tcp 110/tcp 143/tcp 443/tcp 587/tcp 993/tcp 995/tcp 8080/tcp 8443/tcp
7. Configure Valkey (Redis Replacement)
Enable Syslog
vi /etc/valkey/grommunio.conf ----- syslog-enabled yes syslog-ident valkey syslog-facility local0 -----
Enable Memory Overcommit
vi /etc/sysctl.conf ----- vm.overcommit_memory = 1 ----- sysctl -p
Start Valkey and Test
rcctl restart valkey@grommunio valkey-cli ping # Expected result: 'PONG'
8. Install and Configure Rspamd
Install Rspamd
apk add rspamd rspamd-client
Configure Rspamd
Modify Rspamd configuration files:
cat > /etc/rspamd/local.d/options.inc <<EOF dns { enable_dnssec = true; timeout = 4s; retransmits = 5; } EOF
cat > /etc/rspamd/local.d/redis.conf <<EOF read_servers = "127.0.0.1"; write_servers = "127.0.0.1"; EOF
cat > /etc/rspamd/local.d/worker-proxy.inc <<EOF milter = yes; bind_socket = "/var/run/rspamd/worker-proxy.sock mode=0660 owner=rspamd"; timeout = 120s; upstream "local" { default = yes; self_scan = yes; } count = 4; EOF
Add Postfix to Rspamd Group
addgroup postfix rspamd
Configure DKIM Signing
cat > /etc/rspamd/local.d/dkim_signing.conf <<EOF enabled = true; path = "/var/lib/rspamd/dkim/\$domain-\$selector.key"; selector = "dkim"; sign_authenticated = true; sign_local = false; domain { example.com { selector = "202406"; } } EOF
Generate DKIM Key Pair
mkdir -p /var/lib/rspamd/dkim rspamadm dkim_keygen -s 202406 -t ED25519 -d example.com -k /var/lib/rspamd/dkim/example.com-202406.key > /var/lib/rspamd/dkim/example.com-202406.pub
Start Rspamd
rc-update add rspamd rcctl start rspamd
9. Finalize and Verify Installation
Restart Services
Restart all services:
rcctl restart postfix saslauthd rspamd valkey@grommunio nginx php-fpm83 gromox-delivery gromox-event \ gromox-http gromox-imap gromox-midb gromox-pop3 gromox-delivery-queue gromox-timer gromox-zcore \ grommunio-admin-api
Verify Service Status
Check the status of all services:
rcctl status
Ensure that all services (Postfix, MariaDB, Nginx, PHP, Grommunio) are running correctly:
ss -tulpn
Verify Mail Functionality
Test sending and receiving emails using a mail client and verifying server logs for any errors.
Check Logs
Inspect logs for any errors or issues:
find /var/log -type f | xargs tail -n50 | grep -iE '==>|fail|crit|error|alert|corrupt|warning'
Web UI Access
Admin UI: [1](https://mail.example.local:8443)
10. End User Configuration
Admin UI
Log into the Admin UI with the username `admin` and the previously created `ADMIN_PASS`.
License Configuration
If you have a license, you can configure it under Grommunio settings in the Admin UI.