Grommunio Mail Server

This tutorial covers the steps to set up a mail server on Alpine Linux using grommunio, an open-source groupware solution that supports email, calendar, and task management. Grommunio stands out with its unique MAPI support, enabling seamless integration with Microsoft Outlook and other MAPI clients, making it an ideal choice for open-source alternatives to proprietary systems. The setup includes essential components like MariaDB, Nginx, PHP, and Postfix to create a fully functional mail server. Follow along to build a secure, scalable communication platform using grommunio's powerful features.
== 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
# 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 ===
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 | mariadb -u root --password="${DB_ROOT_PASS}"
=== Configure MariaDB ===
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 | mariadb -u admin --password="${DB_PASS}"
Test the database connection:
 mariadb -hlocalhost -u grommunio -p${MYSQL_PASS} grommunio
== 2. 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
== 3. 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:
NOTE: In case you need grommunio-sync (ActiveSync) set allow_url_fopen=On
 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:
NOTE: In case you need grommunio-sync (ActiveSync) remove escapeshellarg & exec
 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
== 4. 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 prepared configuration files:
 wget https://alpinelinuxsupport.com/downloads/postfix-configuration-files.zip
Copy the prepared configuration files to `/etc/postfix/` and adapt the configuration to your environment. Ensure you change 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
== 5. Install and Configure Grommunio ==
=== Enable IPv6 ===
First of all, turn on ipv6 which is mandatory for grommunio daemons
Edit `/etc/hosts` to make sure that IPv6 localhost in configured:
 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 Domain Parameters ===
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 Packages ===
Install necessary dependencies (util-linux-login is required to get pam.d working):
 apk add valkey valkey-cli cyrus-sasl cyrus-sasl-login linux-pam 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
=== Relocate Mail Storage ===
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:
NOTE: 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
Create default login.defs (missing in util-linux-login):
 cat > /etc/login.defs <<EOF
 # By default, create a group with the name of the user
 USERGROUPS_ENAB yes
 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 Database ===
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 (provided by the alpinelinuxsupport.com team) is needed to make the log monitoring work
 addgroup grommunio adm
=== Configure Firewall ===
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
== 6. Configure Valkey ==
=== Enable Syslog ===
The syslog facility is set to 'local0'.
NOTE: It is recommended to redirect local0 to the maillog in syslog.conf as all other grommunio processes write to maillog
 vi /etc/valkey/grommunio.conf
 -----
 #pidfile /var/run/valkey/default.pid
 #logfile /var/log/valkey/default.log
 syslog-enabled yes
 syslog-ident valkey
 syslog-facility local0
=== Disable jemalloc ===
Disable jemalloc background threads as we use libc malloc
 sed -i "s/^jemalloc-bg-thread yes/jemalloc-bg-thread no/" /etc/valkey/grommunio.conf
=== Reduce maxclients ===
Reduce the maxclients to avoid the error message regarding maximum open files
 echo "maxclients 4064" >> /etc/valkey/grommunio.conf
=== Enable Memory Overcommit ===
Valkey insists in enabling memory overcommitment in sysctl. If not set, a startup warning is shown
 cat >> /etc/sysctl.conf <<EOF
 # Enable memory overcommit for valkey
 vm.overcommit_memory = 1
 EOF
Update the system configuration
 
 sysctl -p
=== Start Valkey and Test ===
 rcctl restart valkey@grommunio
 valkey-cli ping # Expected result: 'PONG'
== 7. Install and Configure Rspamd ==
=== Install Rspamd ===
 apk add rspamd rspamd-client
=== Configure Rspamd ===
Set general options:
 cat > /etc/rspamd/local.d/options.inc <<EOF
 dns {
 enable_dnssec = true;
 timeout = 4s;
 retransmits = 5;
 }
 EOF
Configure redis/valkey connection:
 cat > /etc/rspamd/local.d/redis.conf <<EOF
 read_servers = "127.0.0.1";
 write_servers = "127.0.0.1";
 EOF
Configure normal worker:
 cat > /etc/rspamd/local.d/worker-normal.inc << EOF
 # Normal worker is intended to scan messages for spam
 # You might disable normal worker to free up system resources as we use the proxy worker in self-scan mode
 enabled = false;
 # If the mailer is running on the same host use a unix socket
 #bind_socket = "127.0.0.1:11333";
 #bind_socket = "/var/run/rspamd/worker-normal.sock mode=0660 owner=rspamd";
 EOF
Configure proxy worker:
 cat > /etc/rspamd/local.d/worker-proxy.inc << EOF
 # Proxy worker is used as postfix milter
 # If the mailer is running on the same host use a unix socket
 milter = yes;
 #bind_socket = "127.0.0.1:11332";
 bind_socket = "/var/run/rspamd/worker-proxy.sock mode=0660 owner=rspamd";
 timeout = 120s;
 upstream "local" {
 default = yes; # Self-scan upstreams are always default
 self_scan = yes; # Enable self-scan
 }
 count = 4; # Spawn more processes in self-scan mode
 EOF
Add postfix to rspamd group to access the socket:
 addgroup postfix rspamd
Configure controller worker:
 cat > /etc/rspamd/local.d/worker-controller.inc << EOF
 # Controller worker is used to manage rspamd stats, to learn rspamd and to serve WebUI
 # If the mailer is running on the same host use a unix socket
 # NOTE: grommunio connects over http
 bind_socket = "127.0.0.1:11334";
 #bind_socket = "/var/run/rspamd/worker-controller.sock mode=0660 owner=rspamd";
 # password for read-only commands
 password = "<encrypted_password_string>";
 # password for write commands
 enable_password = "<encrypted_password_string>"
 secure_ip = "127.0.0.1";
 EOF
Create the user password and replace it in worker-controller.inc:
Command: rspamadm pw --encrypt -p P4ssword
 sed -i s/\<encrypted_password_string\>/$(rspamadm pw --encrypt -p "${ADMIN_PASS}")/ /etc/rspamd/local.d/worker-controller.inc
 cat /etc/rspamd/local.d/worker-controller.inc
Redirect rspamd logs to syslog:
 cat > /etc/rspamd/local.d/logging.inc << EOF
 # Redirect rspamd logs to the mail log
 type = "syslog";
 facility = "mail";
 level = "notice";
 EOF
Configure external relay:
 cat > /etc/rspamd/local.d/external_relay.conf << EOF
 enabled = true;
 rules {
 FETCHMAIL {
 strategy = "local";
 }
 }
 EOF
Configure actions:
 cat > /etc/rspamd/local.d/actions.conf << EOF
 reject = 15; # Reject when reaching this score
 add_header = 4; # Add spam header when reaching this score
 greylist = 3; # Apply greylisting when reaching this score (will emit 'soft reject action')
 
 #rewrite_subject = 6; # Rewrite subject to indicate spam. Cannot be combined with add_header
 #unknown_weight = 1.0; # Enable if need to set score for all symbols implicitly
 #subject = "***SPAM*** %s"; # Set rewrite subject to this value (%s is replaced by the original subject)
 EOF
Configure milter_headers:
 cat > /etc/rspamd/local.d/milter_headers.conf << EOF
 # Enables x-spamd-result, x-rspamd-server and x-rspamd-queue-id headers (default false)
 extended_spam_headers = true;
 
 # Set false to always add headers for local IPs (default true)
 skip_local = false;
 
 # Set false to always add headers for authenticated users (default true)
 skip_authenticated = false;
 
 # Set false to keep pre-existing spam flag added by an upstream spam filter (default true)
 remove_upstream_spam_flag = true;
 EOF
Configure dkim signing:
 cat > /etc/rspamd/local.d/dkim_signing.conf << EOF
 # Enable DKIM signing
 enabled = true;
 
 # If false, messages with empty envelope from are not signed
 allow_envfrom_empty = true;
 
 # If true, envelope/header domain mismatch is ignored
 allow_hdrfrom_mismatch = true;
 
 # If true, multiple from headers are allowed (but only first is used)
 allow_hdrfrom_multiple = false;
 
 # If true, username does not need to contain matching domain
 allow_username_mismatch = true;
 
 # Default path to key, can include '$domain' and '$selector' variables
 path = "/var/lib/rspamd/dkim/\$domain-\$selector.key";
 
 # Default selector to use
 selector = "dkim";
 
 # If false, messages from authenticated users are not selected for signing
 sign_authenticated = true;
 
 # If false, messages from local networks are not selected for signing
 sign_local = false;
 
 # Map file of IP addresses/subnets to consider for signing
 #sign_networks = "/some/file"; # or url
 
 # Symbol to add when message is signed
 symbol = "DKIM_SIGNED";
 
 # If false, messages from domains not defined here will not be signed
 try_fallback = false;
 
 # Domain to use for DKIM signing: can be "header" (MIME From), "envelope" (SMTP From), 
 # "recipient" (SMTP To), "auth" (SMTP username) or directly specified domain name
 use_domain = "header";
 
 # Domain to use for DKIM signing when sender is in sign_networks ("header"/"envelope"/"auth")
 #use_domain_sign_networks = "header";
 
 # Domain to use for DKIM signing when sender is a local IP ("header"/"envelope"/"auth")
 #use_domain_sign_local = "header";
 
 # Whether to normalise domains to eSLD
 use_esld = true;
 
 # Whether to get keys from Redis
 use_redis = false;
 
 # Hash for DKIM keys in Redis
 key_prefix = "DKIM_KEYS";
 
 # map of domains -> names of selectors (since rspamd 1.5.3)
 #selector_map = "/etc/rspamd/dkim_selectors.map";
 
 # map of domains -> paths to keys (since rspamd 1.5.3)
 #path_map = "/etc/rspamd/dkim_paths.map";
 
 # If `true` get pubkey from DNS record and check if it matches private key
 check_pubkey = false;
 
 # Set to `false` if you want to skip signing if public and private keys mismatch
 allow_pubkey_mismatch = true;
 
 # Domain specific settings
 domain {
 <maildomain> { selector = "<selector>"; }
 }
 EOF
Specify parameters (MAILDOMAIN already defined above):
 MAILDOMAIN="example.com"
 SELECTOR="202406"
Replace maildomain and selector in configuration:
 sed -i "s/<maildomain>/${MAILDOMAIN}/g" /etc/rspamd/local.d/dkim_signing.conf
 sed -i "s/<selector>/${SELECTOR}/g" /etc/rspamd/local.d/dkim_signing.conf
Create DKIM Key Pair:
NOTE: The 'pub' file contains the public key and the configuration to create the corresponding dns record
 mkdir -p /var/lib/rspamd/dkim
 rspamadm dkim_keygen -s ${SELECTOR} -t ED25519 -d ${MAILDOMAIN} -k /var/lib/rspamd/dkim/${MAILDOMAIN}-${SELECTOR}.key > /var/lib/rspamd/dkim/${MAILDOMAIN}-${SELECTOR}.pub
 chown -R rspamd:grommunio /var/lib/rspamd/dkim
 chmod 640 /var/lib/rspamd/dkim/${MAILDOMAIN}-${SELECTOR}.key
 cat /var/lib/rspamd/dkim/${MAILDOMAIN}-${SELECTOR}.pub
=== Start Rspamd and Test ===
 rc-update add rspamd
 rcctl start rspamd
Test rspam client connection getting statistics (you need to wait some minutes until stats are available):
 #rspamc -h /run/rspamd/worker-controller.sock stat
 rspamc stat
== 8. Finalize and Verify ==
=== Restart Services ===
Restart all services:
 rcctl restart postfix saslauthd rspamd valkey@grommunio nginx php-fpm83 \
 gromox-delivery gromox-delivery-queue gromox-event gromox-http gromox-imap \
 gromox-midb gromox-pop3 gromox-timer gromox-zcore grommunio-admin-api
=== Verify Service Status ===
Check the status of all services:
 rcctl status
=== 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: https://mail.example.local:8443
Web UI: https://mail.example.local
=== Admin UI - First steps ===
Log in with user 'admin' and the previously created ADMIN_PASS
Create and change the following entities:
# Defaults: Set the overall defaults to be applied to all domains and users. If you use PAM, ensure that the flag 'Allow SMTP sending (used by POP3/IMAP clients)' is activated
# Create an Organization
# Create a Domain belonging to this organization. Select as well "Create domain admin role"
# Create Users belonging to the domain
NOTE: For some reason, the first user is not created properly. Permissions are wrong and the sqlite database is not created. It is suggested to create a second user, ensure that the permissions are set correctly and then delete and recreate the first user
 
Correct user permissions are like follows:
 ls -l /var/lib/gromox/user/example.com
 drwxrwx--- 8 grommuni gromox 4.0K Jun 21 14:05 user1
 
 ls -l /var/lib/gromox/user/example.com/user1
 drwxrwx--- 8 grommuni gromox 4.0K Jun 21 14:05 .
 drwxr-xr-x 4 grommuni gromox 4.0K Jun 21 14:05 ..
 drwxrwx--- 2 grommuni gromox 4.0K Jun 21 14:05 cid
 drwxrwx--- 2 grommuni gromox 4.0K Jun 21 14:05 config
 drwxrwx--- 2 grommuni gromox 4.0K Jun 21 14:05 eml
 drwxrwx--- 2 grommuni gromox 4.0K Jun 21 14:05 exmdb
 drwxrwx--- 2 grommuni gromox 4.0K Jun 21 14:05 ext
 -rw-r--r-- 1 gromox gromox 0 Jun 21 14:05 tables.sqlite3
 drwxrwx--- 4 grommuni gromox 4.0K Jun 21 14:05 tmp
=== Web UI Testing ===
Verify if you can access the web UI with your new user
=== Test saslauthd authentication ===
Finally, test saslauthd authentication with PAM
 testsaslauthd -u username -r domain -p password -s smtp
 testsaslauthd -u username@domain -p password -s smtp
=== Add/verify the license ===
In case you have a license you can configure it in the admin UI under grommunio settings:
 https://mail.example.local:8443/license
The default (fallback) community license is defined in the following file
 vi /usr/share/grommunio-admin-api/tools/license.py
== See Also ==
Category:Server
Category:Mail