Grommunio Mail Server: Difference between revisions

From Alpine Linux
No edit summary
 
(99 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{Draft|This is a work in progress}}
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




= HOWTO: Install AlpineLinux Mail Server with Grommunio =
Define the variables used later in the setup setup and configuration.


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


== Prerequisites ==


Before proceeding with the installation, ensure you have a fresh Alpine Linux system setup. You'll need root privileges to execute these commands.
Setup the default system tables:


== Steps: ==
sudo mysql_install_db --user=mysql --datadir=${DB_DATA_PATH}


1. Install and configure MariaDB
2. MariaDB performance tuning (optional)
3. Install and configure Nginx
4. Install and configure PHP
5. Install and configure Postfix
6. Install and configure Grommunio
7. Configure Valkey (Redis replacement)
8. Install and configure Rspamd
9. Finalize and verify installation


---
Set a symlink to the standard mysql data directory for compatibility reason:


== 1. Install and Configure MariaDB ==
ln -s /srv/mysql /var/lib/mysql


=== Step 1: Install MariaDB ===
To start, install MariaDB and necessary client utilities:


```sh
Restart service:
apk add mariadb mariadb-client mariadb-server-utils
```


=== Step 2: Set up MariaDB Database Variables ===
rc-service mariadb restart
Define the variables used in the setup and create a symlink to the MariaDB data directory.


```sh
DB_DATA_PATH="/srv/mysql"
DB_ROOT_PASS="Passw0rd1"
DB_USER="admin"
DB_PASS="Passw0rd2"
```


Setup system tables and configure the symlink for MariaDB:
Run the built-in security script:


```sh
Set the new root password to the one defined above and answer everything with 'y'
sudo mysql_install_db --user=mysql --datadir=${DB_DATA_PATH}
ln -s /srv/mysql /var/lib/mysql
rc-service mariadb restart
```


=== Step 3: Secure MariaDB
sudo mysql_secure_installation
Run the built-in security script to set a root password and configure MariaDB security settings.


```sh
sudo mysql_secure_installation
```


=== Step 4: Create MariaDB User for Grommunio ===
=== Create MariaDB User ===
Create a new user for Grommunio and assign privileges:
Create a new user for Grommunio and assign privileges:


```sh
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}@'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}@'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 "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 "DELETE FROM mysql.user WHERE User='';" >> /tmp/sql
echo "FLUSH PRIVILEGES;" >> /tmp/sql
echo "FLUSH PRIVILEGES;" >> /tmp/sql
cat /tmp/sql | mariadb -u root --password="${DB_ROOT_PASS}"
cat /tmp/sql | mysql -u root --password="${DB_ROOT_PASS}"
```


=== Step 5: Configure MariaDB for Grommunio ===
=== Configure MariaDB ===
Edit the MariaDB configuration for better performance:
Edit the MariaDB configuration for better performance:


```sh
vi /etc/my.cnf.d/mariadb-server.cnf
vi /etc/my.cnf.d/mariadb-server.cnf
 
```


Add the following configuration:
Add the following configuration:


```ini
[mysqld]
[mysqld]
#skip-networking
innodb_log_buffer_size=16M
innodb_log_file_size=32M
## Configuration for grommunio (most values are default)
innodb_read_io_threads=4
innodb_log_buffer_size=16M
innodb_write_io_threads=4
innodb_log_file_size=32M
join_buffer_size=512K
innodb_read_io_threads=4
query_cache_size=0
innodb_write_io_threads=4
query_cache_type=0
query_cache_limit=2M
join_buffer_size=512K
performance_schema=ON
query_cache_size=0
bind-address = 127.0.0.1
query_cache_type=0
skip-name-resolve=ON
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:
Create a default charset configuration for MariaDB:


```sh
cat > /etc/my.cnf.d/mariadb-server-default-charset.cnf << EOF
cat > /etc/my.cnf.d/mariadb-server-default-charset.cnf << EOF
[client]
[client]
default-character-set = utf8mb4
default-character-set = utf8mb4
[mysqld]
collation_server = utf8mb4_general_ci
character_set_server = utf8mb4
[mysql]
default-character-set = utf8mb4
EOF


[mysqld]
collation_server = utf8mb4_general_ci
character_set_server = utf8mb4


[mysql]
Restart MariaDB and enable it to start on boot:
default-character-set = utf8mb4
EOF
```


Restart MariaDB and enable it to start on boot:
rc-update add mariadb default
rc-service mariadb restart


```sh
rc-update add mariadb default
service mariadb restart
```


=== Step 6: Verify MariaDB Setup
=== Verify MariaDB Setup ===
Check if the MariaDB listener is running and bound to the correct address:
Check if the MariaDB listener is running and bound to the correct interface (127.0.0.1):


```sh
ss -tulpn
ss -tulpn
```


=== Step 7: Create Grommunio Database ===
=== Create Grommunio Database ===
Define the database parameters and create the Grommunio database:
Define the database parameters and create the Grommunio database:


```sh
MYSQL_HOST="localhost"
MYSQL_HOST="localhost"
MYSQL_USER="grommunio"
MYSQL_USER="grommunio"
MYSQL_PASS="Passw0rd3"
MYSQL_PASS="Passw0rd3"
MYSQL_DB="grommunio"
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}"


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:
Test the database connection:


```sh
mariadb -hlocalhost -u grommunio -p${MYSQL_PASS} grommunio
mysql -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


== 2. MariaDB Performance Tuning (Optional) ==


Install and configure MySQLTuner to help with database performance:
Restart Nginx and enable it to start on boot:


```sh
rc-update add nginx
wget -v --no-check-certificate https://raw.githubusercontent.com/major/MySQLTuner-perl/master/mysqltuner.pl -O /tmp/mysqltuner.pl
rc-service nginx restart
mv /tmp/mysqltuner.pl /usr/local/bin/mysqltuner.pl
chmod 755 /usr/local/bin/mysqltuner.pl
apk add perl perl-doc
/usr/local/bin/mysqltuner.pl --user admin --pass ${DB_PASS}
```


---
== 3. Install and Configure PHP ==


== 3. Install and Configure Nginx ==
=== Install PHP ===
Install the basic php packages. The modules are installed as grommunio dependencies:


=== Step 1: Install Nginx ===
apk add php83 php83-fpm
Install the necessary Nginx modules:


```sh
Disable default fpm conf:
apk add nginx nginx-mod-http-headers-more nginx-mod-http-vts nginx-mod-http-brotli
```


=== Step 2: Configure Nginx ===
mv /etc/php83/php-fpm.d/www.conf /etc/php83/php-fpm.d/www.conf.default
Backup the original Nginx configuration and edit it for security headers and TLS settings:


```sh
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.orig
vi /etc/nginx/nginx.conf
```


Add the following configuration:
=== Harden PHP Configuration ===


```nginx
error_log syslog:server=unix:/dev/log,facility=local2,nohostname warn;
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.";


ssl_protocols TLSv1.2 TLSv1.3;
Backup config file /etc/php83/php.ini
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;


log_format main_ssl '$remote_addr - $remote_user [$time_local] "$request" '
cp /etc/php83/php.ini /etc/php83/php-fpm.d/php.ini.orig
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'client_ciphers="$ssl_ciphers" client_curves="$ssl_curves"';


access_log off;
```


Restart Nginx and enable it to start on boot:
Disable remote PHP code execution:<br>
'''NOTE:''' In case you need ''grommunio-sync'' (ActiveSync) set allow_url_fopen=On


```sh
sed 's/^;\?\(allow_url_fopen\).*/\1 = Off/' -i /etc/php83/php.ini
rc-update add nginx
sed 's/^;\?\(allow_url_include\).*/\1 = Off/' -i /etc/php83/php.ini
service nginx restart
```


---


== 4. Install and Configure PHP ==
Disable information leakage:
 
sed 's/^;\?\(expose_php\).*/\1 = Off/' -i /etc/php83/php.ini


=== Step 1: Install PHP ===
Install the required PHP packages for Grommunio:


```sh
Configure Error handling:
apk add php83 php83-fpm
```


=== Step 2: Harden PHP Configuration ===
sed 's/^;\?\(display_errors\).*/\1 = Off/' -i /etc/php83/php.ini
Disable insecure PHP settings and adjust PHP limits:
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


```sh
sed 's/^;\?\(allow_url_fopen\).*/\1 = Off/' -i /etc/php83/php.ini
sed 's/^;\?\(expose_php\).*/\1 = Off/' -i /etc/php83/php.ini
sed 's/^;\?\(display_errors\).*/\1 = Off/' -i /etc/php83/php.ini
sed 's/^;\?\(log_errors\).*/\1 = On/' -i /etc/php83/php.ini
```


=== Step 3: Configure Session Security ===
PHP Resource Control (Optional):
Configure PHP session security:


```sh
#sed 's/^\(max_execution_time\).*/\1 = 25/' -i /etc/php83/php.ini
sed 's/^;\?\(session.use_strict_mode\).*/\1 = 1/' -i /etc/php83/php.ini
#sed 's/^\(max_input_time\).*/\1 = 25/' -i /etc/php83/php.ini
sed 's/^;\?\(session.cookie_secure\).*/\1 = 1/' -i /etc/php83/php.ini
#sed 's/^\(memory_limit\).*/\1 = 30M/' -i /etc/php83/php.ini  
sed 's/^;\?\(session.cookie_httponly\).*/\1 = 1/' -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  


---


== 5. Install and Configure Postfix ==
Disable vulnerable functions:<br>
'''NOTE:''' In case you need ''grommunio-sync'' (ActiveSync) remove escapeshellarg & exec


=== Step 1: Install Postfix ===
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
Install Postfix and related modules:


```sh
apk add postfix postfix-mysql postfix-pcre
```


=== Step 2: Configure Postfix ===
Harden Session Security:
Backup and configure the Postfix settings. Adapt the values as necessary, such as `myhostname`, `mynetworks`, and `smtp_tls_chain_files`:


```sh
sed 's/^;\?\(session.use_strict_mode\).*/\1 = 1/' -i /etc/php83/php.ini
mv /etc/postfix/main.cf /etc/postfix/main.cf.orig
sed 's/^;\?\(session.use_cookies\).*/\1 = 1/' -i /etc/php83/php.ini
mv /etc/postfix/master.cf /etc/postfix/master.cf.orig
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


Run Postfix setup:
== 4. Install and Configure Postfix ==


```sh
=== Install Postfix ===
newaliases
Install the required packages:
postmap /etc/postfix/transport
```


Enable Postfix service:
apk add postfix postfix-mysql postfix-pcre


```sh
rc-update add postfix
service postfix restart
```


=== Step 3: Verify Postfix Logs ===
=== Configure Postfix ===
Check the Postfix logs for any errors:


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




```sh
Download the prepared configuration files:
tail -f /var/log/maillog
```


---
wget https://alpinelinuxsupport.com/downloads/postfix-configuration-files.zip


== 6. Install and Configure Grommunio ==


Install and configure Grommunio to provide email and calendar functionality. Follow the detailed installation steps outlined in the official Grommunio documentation.
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


== 7. Configure Valkey (Redis Replacement) ==
Configure Valkey for optimal caching and session handling, replacing Redis if required.


---
Create the required postmap files:
newaliases
postmap /etc/postfix/transport


== 8. Install and Configure Rspamd ==


Rspamd provides spam filtering for your mail server. Follow the official documentation to install and configure Rspamd to work with Postfix and Nginx.
Run Postfix setup:


---
newaliases
postmap /etc/postfix/transport


== 9. Finalize and Verify Installation ==


=== Step 1: Test Server Components ===
Enable the postfix service and restart:
Ensure that all services (Postfix, MariaDB, Nginx, PHP, Grommunio) are running correctly:


```sh
rc-update add postfix
ss -tulpn
rc-service postfix restart
```


=== Step 2: Verify Mail Functionality ===
=== Verify Postfix Logs ===
Test sending and receiving emails using a mail client and verifying server logs for any errors.
Check the Postfix logs for any errors:


tail -f /var/log/maillog


== 5. Install and Configure Grommunio ==
== 5. Install and Configure Grommunio ==


=== 1. Enable IPv6 ===
=== Enable IPv6 ===
Since Grommunio requires IPv6 for its daemons:
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"


1. **Edit `/etc/hosts` to include IPv6 localhost:**
  ```bash
  vi /etc/hosts
  -----
  ::1 localhost ipv6-localhost ipv6-loopback
  -----
  ```


2. **Ensure IPv6 is enabled in `/etc/sysctl.conf`:**
=== Specify Domain Parameters ===
  ```bash
The '''FQDN''' is for example used by Outlook clients to connect. This name will have to be present in the used certificate.
  sed -i 's/^net\.ipv6\.conf\..*\.disable_ipv6\s=\s1/#&/' /etc/sysctl.conf
  sysctl -p
  ping ::1  # Test if IPv6 is working
  ```


=== 2. Configure Database Parameters ===
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.
Set up your MySQL database connection details:
```bash
MYSQL_HOST="localhost"
MYSQL_USER="grommunio"
MYSQL_PASS="Passw0rd3"
MYSQL_DB="grommunio"
```


=== 3. Specify Internal FQDN, Mail Domain, and Relayhost ===
Adjust the following for your specific setup:
Adjust the following for your specific setup:
```bash
FQDN="mail.example.local"
MAILDOMAIN="example.com"
RELAYHOST="123.123.123.1"
ADMIN_PASS="Passw0rd4"
```


=== 4. Install Dependencies and Grommunio Packages ===
FQDN="mail.example.local"
Install necessary dependencies:
MAILDOMAIN="example.com"
```bash
RELAYHOST="123.123.123.1"
apk add valkey valkey-cli cyrus-sasl cyrus-sasl-login util-linux-login
ADMIN_PASS="Passw0rd4"
apk add grommunio-gromox grommunio-web grommunio-admin-api grommunio-admin-web grommunio-index grommunio-error-pages
 
```
 
=== 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:
Optionally, install deprecated ActiveSync if needed:
```bash
# apk add grommunio-dav grommunio-sync
```


=== 5. Move Mail Storage to Another Disk ===
apk add grommunio-dav grommunio-sync
Move the largest directory `/var/lib/gromox` to another disk and create a symlink:
 
```bash
 
mv /var/lib/gromox /srv/gromox
=== Relocate Mail Storage ===
ln -s /srv/gromox /var/lib/gromox
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:<br>
'''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
 


=== 6. Enable Required Services ===
Configure PAM for SMTP:
Enable all necessary Grommunio services:
```bash
rc-update add grommunio-admin-api
rc-update add gromox-delivery
rc-update add gromox-delivery-queue
# Add all the other grommunio services
```


=== 7. Configure Grommunio Files ===
mv /etc/pam.d/smtp /etc/pam.d/smtp.orig
Modify the configuration files to match your environment:
cat > /etc/pam.d/smtp <<EOF
```bash
#%PAM-1.0
sed -i "s/mail.example.local/${FQDN}/g" /etc/gromox/*.cfg
# config for grommunio auth services
sed -i "s/example.com/${MAILDOMAIN}/g" /etc/gromox/*.cfg
auth    required pam_gromox.so service=smtp
# Continue modifying other configuration files (mysql_adaptor.cfg, autodiscover.ini, etc.)
account required pam_permit.so
```
EOF


=== 8. Configure Postfix ===
Prepare Postfix for integration with Grommunio:
```bash
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
```


=== 9. Configure TLS Certificates ===
Create default login.defs (missing in util-linux-login):
Link and configure your SSL certificates:
```bash
ln -s /etc/grommunio-common/nginx/ssl_certificate.conf /etc/grommunio-admin-common/nginx-ssl.conf
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
addgroup gromox ssl-cert
```


=== 10. Configure PAM and SASL ===
cat > /etc/login.defs <<EOF
Set up authentication services:
# By default, create a group with the name of the user
```bash
USERGROUPS_ENAB yes
# Configure PAM for SMTP
EOF
cat > /etc/pam.d/smtp <<EOF
#%PAM-1.0
auth required pam_gromox.so service=smtp
account required pam_permit.so
EOF
# Configure SASL authentication
cat > /etc/conf.d/saslauthd <<EOF
SASLAUTHD_OPTS="-a pam -r"
EOF
```


=== 11. Initialize the Database and Set Admin Password ===
 
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:
Initialize the database:
```bash
 
gromox-dbop -C
gromox-dbop -C
```
 
 
Set the Grommunio admin password:
Set the Grommunio admin password:
```bash
grommunio-admin passwd --password "${ADMIN_PASS}"
```


=== 12. Configure Firewall Ports ===
grommunio-admin passwd --password "${ADMIN_PASS}"
 
 
Add grommunio to the adm group to enable admin UI to read `/var/log/maillog`.<br>
'''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:
Open the necessary firewall ports:
```bash
# Required ports: 25, 80, 443, etc.
```


---
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'.<br>
'''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 ===


== 6. Configure Valkey (Redis Replacement) ==
Valkey insists in enabling memory overcommitment in sysctl. If not set, a startup warning is shown


=== 1. Enable Syslog: ===
cat >> /etc/sysctl.conf <<EOF
  ```bash
# Enable memory overcommit for valkey
  vi /etc/valkey/grommunio.conf
vm.overcommit_memory = 1
  -----
EOF
  syslog-enabled yes
  syslog-ident valkey
  syslog-facility local0
  -----
  ```


=== 2. Enable Memory Overcommit: ===
  ```bash
  vi /etc/sysctl.conf
  -----
  vm.overcommit_memory = 1
  -----
  sysctl -p
  ```


=== 3. Start Valkey and Test: ===
Update the system configuration
  ```bash
 
  rcctl restart valkey@grommunio
sysctl -p
  valkey-cli ping  # Expected result: 'PONG'
  ```


---
=== Start Valkey and Test ===
 
rcctl restart valkey@grommunio
valkey-cli ping  # Expected result: 'PONG'


== 7. Install and Configure Rspamd ==
== 7. Install and Configure Rspamd ==


=== 1. Install Rspamd: ===
=== Install Rspamd ===
```bash
 
apk add rspamd rspamd-client
  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
 


=== 2. Configure Rspamd: ===
Configure controller worker:
Modify Rspamd configuration files:
```bash
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
cat > /etc/rspamd/local.d/worker-controller.inc << EOF
read_servers = "127.0.0.1";
# Controller worker is used to manage rspamd stats, to learn rspamd and to serve WebUI
write_servers = "127.0.0.1";
# If the mailer is running on the same host use a unix socket
EOF
# 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


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
```


=== 3. Add Postfix to Rspamd Group: ===
Create the user password and replace it in worker-controller.inc:
```bash
addgroup postfix rspamd
```


=== 4. Configure DKIM Signing: ===
Command: rspamadm pw --encrypt -p P4ssword
```bash
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
```


=== 5. Generate DKIM Key Pair: ===
sed -i s/\<encrypted_password_string\>/$(rspamadm pw --encrypt -p "${ADMIN_PASS}")/ /etc/rspamd/local.d/worker-controller.inc
```bash
cat /etc/rspamd/local.d/worker-controller.inc
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
```


=== 6. Start Rspamd: ===
```bash
rc-update add rspamd
rcctl start rspamd
```


---
Redirect rspamd logs to syslog:


== 8. Finalize and Verify Installation ==
cat > /etc/rspamd/local.d/logging.inc << EOF
# Redirect rspamd logs to the mail log
type = "syslog";
facility = "mail";
level = "notice";
EOF


=== 1. Restart Services: ===
 
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:<br>
'''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:
Restart all services:
```bash
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
```


=== 2. Verify Service Status: ===
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:
Check the status of all services:
```bash
rcctl status
```


=== 3. Check Logs: ===
rcctl status
 
 
=== Check Logs ===
Inspect logs for any errors or issues:
Inspect logs for any errors or issues:
```bash
find /var/log -type f | xargs tail -n50 | grep -iE '==>|fail|crit|error|alert|corrupt|warning'
```


=== 4. Web UI Access: ===
find /var/log -type f | xargs tail -n50 | grep -iE '==>|fail|crit|error|alert|corrupt|warning'
Admin UI: [https://mail.example.local:8443](https://mail.example.local:8443)
 
 
=== 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


---


== End User Configuration: ==
The default (fallback) community license is defined in the following file
vi /usr/share/grommunio-admin-api/tools/license.py


=== 1. Admin UI: ===
== See Also ==
Log into the Admin UI with the username `admin` and the previously created `ADMIN_PASS`.


=== 2. License Configuration: ===
[[Category:Server]]
If you have a license, you can configure it under **Grommunio settings** in the Admin UI.
[[Category:Mail]]

Latest revision as of 09:16, 31 January 2025

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

  1. Install and configure MariaDB
  2. Install and configure Nginx
  3. Install and configure PHP
  4. Install and configure Postfix
  5. Install and configure Grommunio
  6. Configure Valkey (Redis replacement)
  7. Install and configure Rspamd
  8. 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:

  1. 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
  2. Create an Organization
  3. Create a Domain belonging to this organization. Select as well "Create domain admin role"
  4. 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