<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.nocrash.net/index.php?action=history&amp;feed=atom&amp;title=Modern_Mail_Server_Setup_Guide</id>
	<title>Modern Mail Server Setup Guide - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.nocrash.net/index.php?action=history&amp;feed=atom&amp;title=Modern_Mail_Server_Setup_Guide"/>
	<link rel="alternate" type="text/html" href="https://wiki.nocrash.net/index.php?title=Modern_Mail_Server_Setup_Guide&amp;action=history"/>
	<updated>2026-07-15T22:08:37Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.9</generator>
	<entry>
		<id>https://wiki.nocrash.net/index.php?title=Modern_Mail_Server_Setup_Guide&amp;diff=3024&amp;oldid=prev</id>
		<title>Chris: Created page with &quot;__TOC__  This runbook builds a single-domain AlmaLinux 9 mail server using Postfix, Dovecot, Rspamd, Redis, nginx, PHP-FPM, Roundcube, SQLite, Sieve, DKIM, and Let&#039;s Encrypt.  The design uses local Unix accounts and per-user Maildir storage. It is intended for a small server such as a 1 GB Nanode and does not require Docker, mailcow, or ClamAV.  This page uses MediaWiki&#039;s SyntaxHighlight extension for command and configuration blocks. If the extension is unavailable, rep...&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.nocrash.net/index.php?title=Modern_Mail_Server_Setup_Guide&amp;diff=3024&amp;oldid=prev"/>
		<updated>2026-07-14T15:12:49Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;__TOC__  This runbook builds a single-domain AlmaLinux 9 mail server using Postfix, Dovecot, Rspamd, Redis, nginx, PHP-FPM, Roundcube, SQLite, Sieve, DKIM, and Let&amp;#039;s Encrypt.  The design uses local Unix accounts and per-user Maildir storage. It is intended for a small server such as a 1 GB Nanode and does not require Docker, mailcow, or ClamAV.  This page uses MediaWiki&amp;#039;s SyntaxHighlight extension for command and configuration blocks. If the extension is unavailable, rep...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
This runbook builds a single-domain AlmaLinux 9 mail server using Postfix, Dovecot, Rspamd, Redis, nginx, PHP-FPM, Roundcube, SQLite, Sieve, DKIM, and Let&amp;#039;s Encrypt.&lt;br /&gt;
&lt;br /&gt;
The design uses local Unix accounts and per-user Maildir storage. It is intended for a small server such as a 1 GB Nanode and does not require Docker, mailcow, or ClamAV.&lt;br /&gt;
&lt;br /&gt;
This page uses MediaWiki&amp;#039;s SyntaxHighlight extension for command and configuration blocks. If the extension is unavailable, replace each &amp;lt;code&amp;gt;&amp;amp;lt;syntaxhighlight ...&amp;amp;gt;&amp;lt;/code&amp;gt; block with a standard &amp;lt;code&amp;gt;&amp;amp;lt;pre&amp;amp;gt;&amp;lt;/code&amp;gt; block.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border-left: 5px solid #2e6e9e; background: #eaf2f6; padding: 0.7em 1em; margin: 1em 0;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Before starting:&amp;#039;&amp;#039;&amp;#039; Replace every value enclosed in angle brackets. Keep &amp;lt;code&amp;gt;&amp;amp;lt;Old Host&amp;amp;gt;&amp;lt;/code&amp;gt; available and serving mail until the cutover and validation steps are complete. Never copy private keys, passwords, or the Roundcube &amp;lt;code&amp;gt;des_key&amp;lt;/code&amp;gt; into documentation.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Port&lt;br /&gt;
! Service&lt;br /&gt;
! Purpose&lt;br /&gt;
! Exposure&lt;br /&gt;
|-&lt;br /&gt;
| 25/tcp || SMTP || Inbound Internet mail || Public&lt;br /&gt;
|-&lt;br /&gt;
| 80/tcp || HTTP || ACME challenge and HTTPS redirect || Public&lt;br /&gt;
|-&lt;br /&gt;
| 443/tcp || HTTPS || Roundcube webmail || Public&lt;br /&gt;
|-&lt;br /&gt;
| 587/tcp || Submission || Authenticated SMTP with STARTTLS || Public&lt;br /&gt;
|-&lt;br /&gt;
| 993/tcp || IMAPS || Encrypted mailbox access || Public&lt;br /&gt;
|-&lt;br /&gt;
| 11332/tcp || Rspamd proxy || Postfix milter || Loopback only&lt;br /&gt;
|-&lt;br /&gt;
| 11334/tcp || Rspamd controller || Administration/API || Loopback only&lt;br /&gt;
|-&lt;br /&gt;
| 6379/tcp || Redis || Rspamd data || Loopback only&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Phase 1: Build the mail system ==&lt;br /&gt;
&lt;br /&gt;
=== 1. Define the deployment variables ===&lt;br /&gt;
&lt;br /&gt;
Run the build as &amp;lt;code&amp;gt;root&amp;lt;/code&amp;gt; on &amp;lt;code&amp;gt;&amp;amp;lt;New Host&amp;amp;gt;&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
MAIL_HOST=&amp;quot;mail.example.com&amp;quot;&lt;br /&gt;
MAIL_DOMAIN=&amp;quot;example.com&amp;quot;&lt;br /&gt;
MAIL_USER=&amp;quot;alice&amp;quot;&lt;br /&gt;
OLD_IP=&amp;quot;&amp;lt;Old Host&amp;gt;&amp;quot;&lt;br /&gt;
NEW_IP=&amp;quot;&amp;lt;New Host&amp;gt;&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Use the public mail identity in Postfix, Dovecot, TLS, and Roundcube even if the temporary build hostname is &amp;lt;code&amp;gt;mailnew&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
hostnamectl set-hostname mailnew&lt;br /&gt;
vi /etc/hosts&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Add the public mail identity locally:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
127.0.1.1 mail.example.com mail&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Verify the base system:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
hostname&lt;br /&gt;
hostname -f&lt;br /&gt;
timedatectl&lt;br /&gt;
free -h&lt;br /&gt;
df -h&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Confirm that the public IPv4 address is static, the provider permits inbound and outbound TCP 25, and the provider can set the PTR record. Add 1.5–2 GB of swap on a 1 GB VM.&lt;br /&gt;
&lt;br /&gt;
=== 2. Install repositories and packages ===&lt;br /&gt;
&lt;br /&gt;
Update AlmaLinux and enable EPEL:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
dnf -y update&lt;br /&gt;
dnf -y install epel-release&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Roundcube 1.7 requires PHP 8.1 or newer. Enable PHP 8.3 from Remi:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
dnf -y install https://rpms.remirepo.net/enterprise/remi-release-9.rpm&lt;br /&gt;
dnf -y module reset php&lt;br /&gt;
dnf -y module enable php:remi-8.3&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Add the official Rspamd repository:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
curl -fsSL https://rspamd.com/rpm-stable/centos-9/rspamd.repo \&lt;br /&gt;
  -o /etc/yum.repos.d/rspamd.repo&lt;br /&gt;
&lt;br /&gt;
rpm --import https://rspamd.com/rpm-stable/gpg.key&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Install the mail, web, security, and administration packages:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
dnf -y install \&lt;br /&gt;
  postfix dovecot dovecot-pigeonhole rspamd redis nginx \&lt;br /&gt;
  php php-fpm php-cli php-curl php-gd php-intl php-mbstring \&lt;br /&gt;
  php-pdo php-sqlite3 php-xml php-zip \&lt;br /&gt;
  certbot firewalld chrony bind-utils rsync unzip tar wget curl \&lt;br /&gt;
  vim-enhanced sqlite policycoreutils-python-utils&lt;br /&gt;
&lt;br /&gt;
systemctl enable --now firewalld chronyd redis php-fpm&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 3. Create the mailbox account ===&lt;br /&gt;
&lt;br /&gt;
Check the UID and GID on &amp;lt;code&amp;gt;&amp;amp;lt;Old Host&amp;amp;gt;&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
id &amp;quot;$MAIL_USER&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the account with the same UID and GID on &amp;lt;code&amp;gt;&amp;amp;lt;New Host&amp;amp;gt;&amp;lt;/code&amp;gt;. Matching them prevents ownership trouble during migration and recovery.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
groupadd -g &amp;lt;OLD_GID&amp;gt; &amp;quot;$MAIL_USER&amp;quot;&lt;br /&gt;
useradd -u &amp;lt;OLD_UID&amp;gt; -g &amp;lt;OLD_GID&amp;gt; -m -s /bin/bash &amp;quot;$MAIL_USER&amp;quot;&lt;br /&gt;
passwd &amp;quot;$MAIL_USER&amp;quot;&lt;br /&gt;
&lt;br /&gt;
install -d -m 700 -o &amp;quot;$MAIL_USER&amp;quot; -g &amp;quot;$MAIL_USER&amp;quot; \&lt;br /&gt;
  /home/$MAIL_USER/Maildir/{cur,new,tmp}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 4. Configure Postfix ===&lt;br /&gt;
&lt;br /&gt;
Back up the packaged configuration:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cp -a /etc/postfix/main.cf /etc/postfix/main.cf.orig&lt;br /&gt;
cp -a /etc/postfix/master.cf /etc/postfix/master.cf.orig&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Apply the main settings:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
postconf -e &amp;quot;myhostname = $MAIL_HOST&amp;quot;&lt;br /&gt;
postconf -e &amp;quot;mydomain = $MAIL_DOMAIN&amp;quot;&lt;br /&gt;
postconf -e &amp;#039;myorigin = $mydomain&amp;#039;&lt;br /&gt;
postconf -e &amp;#039;inet_interfaces = all&amp;#039;&lt;br /&gt;
postconf -e &amp;#039;inet_protocols = ipv4&amp;#039;&lt;br /&gt;
postconf -e &amp;#039;mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain&amp;#039;&lt;br /&gt;
postconf -e &amp;#039;home_mailbox = Maildir/&amp;#039;&lt;br /&gt;
postconf -e &amp;#039;mailbox_transport = lmtp:unix:private/dovecot-lmtp&amp;#039;&lt;br /&gt;
postconf -e &amp;#039;smtpd_sasl_type = dovecot&amp;#039;&lt;br /&gt;
postconf -e &amp;#039;smtpd_sasl_path = private/auth&amp;#039;&lt;br /&gt;
postconf -e &amp;#039;smtpd_sasl_auth_enable = yes&amp;#039;&lt;br /&gt;
postconf -e &amp;#039;smtpd_sasl_security_options = noanonymous&amp;#039;&lt;br /&gt;
postconf -e &amp;#039;smtpd_milters = inet:127.0.0.1:11332&amp;#039;&lt;br /&gt;
postconf -e &amp;#039;non_smtpd_milters = inet:127.0.0.1:11332&amp;#039;&lt;br /&gt;
postconf -e &amp;#039;milter_protocol = 6&amp;#039;&lt;br /&gt;
postconf -e &amp;#039;milter_default_action = accept&amp;#039;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Edit the Postfix service definition:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
vi /etc/postfix/master.cf&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enable authenticated submission on TCP 587:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
submission inet n       -       n       -       -       smtpd&lt;br /&gt;
  -o syslog_name=postfix/submission&lt;br /&gt;
  -o smtpd_tls_security_level=encrypt&lt;br /&gt;
  -o smtpd_sasl_auth_enable=yes&lt;br /&gt;
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject&lt;br /&gt;
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject&lt;br /&gt;
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Do not start Postfix yet if the configured TLS certificate does not exist.&lt;br /&gt;
&lt;br /&gt;
=== 5. Configure Dovecot ===&lt;br /&gt;
&lt;br /&gt;
Back up the original configuration:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cp -a /etc/dovecot /etc/dovecot.orig&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set the enabled protocols in &amp;lt;code&amp;gt;/etc/dovecot/dovecot.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
protocols = imap lmtp&lt;br /&gt;
listen = *&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set Maildir storage in &amp;lt;code&amp;gt;/etc/dovecot/conf.d/10-mail.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mail_location = maildir:~/Maildir&lt;br /&gt;
first_valid_uid = 1000&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set authentication in &amp;lt;code&amp;gt;/etc/dovecot/conf.d/10-auth.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
disable_plaintext_auth = yes&lt;br /&gt;
auth_mechanisms = plain login&lt;br /&gt;
auth_username_format = %Ln&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border-left: 5px solid #c98720; background: #fff5e3; padding: 0.7em 1em; margin: 1em 0;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Important:&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;auth_username_format = %Ln&amp;lt;/code&amp;gt; converts &amp;lt;code&amp;gt;alice@example.com&amp;lt;/code&amp;gt; to the local Unix user &amp;lt;code&amp;gt;alice&amp;lt;/code&amp;gt;. Without it, IMAP authentication may work while LMTP delivery fails.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enable Sieve during LMTP delivery in &amp;lt;code&amp;gt;/etc/dovecot/conf.d/20-lmtp.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
protocol lmtp {&lt;br /&gt;
  mail_plugins = $mail_plugins sieve&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside &amp;lt;code&amp;gt;service auth&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/dovecot/conf.d/10-master.conf&amp;lt;/code&amp;gt;, create the Postfix SASL socket:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
unix_listener /var/spool/postfix/private/auth {&lt;br /&gt;
  mode = 0660&lt;br /&gt;
  user = postfix&lt;br /&gt;
  group = postfix&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Configure the LMTP socket in the same file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
service lmtp {&lt;br /&gt;
  unix_listener /var/spool/postfix/private/dovecot-lmtp {&lt;br /&gt;
    mode = 0600&lt;br /&gt;
    user = postfix&lt;br /&gt;
    group = postfix&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 6. Configure Redis and Rspamd ===&lt;br /&gt;
&lt;br /&gt;
Ensure Redis listens only on loopback. The packaged &amp;lt;code&amp;gt;/etc/redis/redis.conf&amp;lt;/code&amp;gt; should contain:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
bind 127.0.0.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create &amp;lt;code&amp;gt;/etc/rspamd/local.d/redis.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
servers = &amp;quot;127.0.0.1&amp;quot;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create &amp;lt;code&amp;gt;/etc/rspamd/local.d/classifier-bayes.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
backend = &amp;quot;redis&amp;quot;;&lt;br /&gt;
servers = &amp;quot;127.0.0.1&amp;quot;;&lt;br /&gt;
autolearn = true;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create &amp;lt;code&amp;gt;/etc/rspamd/local.d/actions.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
reject = 15;&lt;br /&gt;
add_header = 6;&lt;br /&gt;
greylist = 4;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create &amp;lt;code&amp;gt;/etc/rspamd/local.d/milter_headers.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
use = [&lt;br /&gt;
  &amp;quot;x-rspamd-action&amp;quot;,&lt;br /&gt;
  &amp;quot;x-spamd-result&amp;quot;,&lt;br /&gt;
  &amp;quot;authentication-results&amp;quot;&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
authenticated_headers = [&amp;quot;authentication-results&amp;quot;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Use Rspamd&amp;#039;s proxy self-scan mode in &amp;lt;code&amp;gt;/etc/rspamd/local.d/worker-proxy.inc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
upstream &amp;quot;local&amp;quot; {&lt;br /&gt;
  self_scan = yes;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Disable unused workers:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
# /etc/rspamd/local.d/worker-normal.inc&lt;br /&gt;
enabled = false;&lt;br /&gt;
&lt;br /&gt;
# /etc/rspamd/local.d/worker-fuzzy.inc&lt;br /&gt;
enabled = false;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set a task timeout in &amp;lt;code&amp;gt;/etc/rspamd/local.d/options.inc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
task_timeout = 12s;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 7. Generate and publish the DKIM key ===&lt;br /&gt;
&lt;br /&gt;
Create the key directory and generate a 2048-bit key:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
install -d -m 750 -o _rspamd -g _rspamd /var/lib/rspamd/dkim&lt;br /&gt;
&lt;br /&gt;
rspamadm dkim_keygen \&lt;br /&gt;
  -b 2048 \&lt;br /&gt;
  -s dkim \&lt;br /&gt;
  -d &amp;quot;$MAIL_DOMAIN&amp;quot; \&lt;br /&gt;
  -k &amp;quot;/var/lib/rspamd/dkim/${MAIL_DOMAIN}.dkim.key&amp;quot; \&lt;br /&gt;
  &amp;gt; &amp;quot;/root/${MAIL_DOMAIN}-dkim-dns.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
chown _rspamd:_rspamd /var/lib/rspamd/dkim/*.key&lt;br /&gt;
chmod 600 /var/lib/rspamd/dkim/*.key&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create &amp;lt;code&amp;gt;/etc/rspamd/local.d/dkim_signing.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
selector = &amp;quot;dkim&amp;quot;;&lt;br /&gt;
path = &amp;quot;/var/lib/rspamd/dkim/$domain.$selector.key&amp;quot;;&lt;br /&gt;
sign_authenticated = true;&lt;br /&gt;
sign_local = true;&lt;br /&gt;
use_domain = &amp;quot;header&amp;quot;;&lt;br /&gt;
use_esld = true;&lt;br /&gt;
allow_username_mismatch = true;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Publish the TXT value from &amp;lt;code&amp;gt;/root/&amp;amp;lt;domain&amp;amp;gt;-dkim-dns.txt&amp;lt;/code&amp;gt; at &amp;lt;code&amp;gt;dkim._domainkey.example.com&amp;lt;/code&amp;gt;. Never publish the private &amp;lt;code&amp;gt;.key&amp;lt;/code&amp;gt; file.&lt;br /&gt;
&lt;br /&gt;
=== 8. Create automatic Junk filing ===&lt;br /&gt;
&lt;br /&gt;
Create and subscribe the Junk mailbox:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
doveadm mailbox create -u &amp;quot;$MAIL_USER&amp;quot; Junk&lt;br /&gt;
doveadm mailbox subscribe -u &amp;quot;$MAIL_USER&amp;quot; Junk&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the user Sieve directory:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
install -d -m 700 -o &amp;quot;$MAIL_USER&amp;quot; -g &amp;quot;$MAIL_USER&amp;quot; \&lt;br /&gt;
  /home/$MAIL_USER/sieve&lt;br /&gt;
&lt;br /&gt;
vi /home/$MAIL_USER/sieve/rspamd.sieve&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Add the filtering rule:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
require [&amp;quot;fileinto&amp;quot;];&lt;br /&gt;
&lt;br /&gt;
if header :is &amp;quot;X-Rspamd-Action&amp;quot; &amp;quot;add header&amp;quot; {&lt;br /&gt;
  fileinto &amp;quot;Junk&amp;quot;;&lt;br /&gt;
  stop;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Compile and activate it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
sievec /home/$MAIL_USER/sieve/rspamd.sieve&lt;br /&gt;
ln -sfn /home/$MAIL_USER/sieve/rspamd.sieve \&lt;br /&gt;
  /home/$MAIL_USER/.dovecot.sieve&lt;br /&gt;
&lt;br /&gt;
chown -R &amp;quot;$MAIL_USER:$MAIL_USER&amp;quot; \&lt;br /&gt;
  /home/$MAIL_USER/sieve \&lt;br /&gt;
  /home/$MAIL_USER/.dovecot.sieve&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Phase 2: Publish and validate the services ==&lt;br /&gt;
&lt;br /&gt;
=== 9. Configure the firewall ===&lt;br /&gt;
&lt;br /&gt;
Open only the required public services:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
firewall-cmd --permanent --add-service=ssh&lt;br /&gt;
firewall-cmd --permanent --add-service=http&lt;br /&gt;
firewall-cmd --permanent --add-service=https&lt;br /&gt;
firewall-cmd --permanent --add-service=smtp&lt;br /&gt;
firewall-cmd --permanent --add-service=imaps&lt;br /&gt;
firewall-cmd --permanent --add-port=587/tcp&lt;br /&gt;
firewall-cmd --reload&lt;br /&gt;
firewall-cmd --list-all&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Redis and Rspamd must remain on loopback. They must not be opened in the firewall.&lt;br /&gt;
&lt;br /&gt;
=== 10. Bootstrap nginx and obtain the certificate ===&lt;br /&gt;
&lt;br /&gt;
Create the ACME webroot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
install -d -m 755 -o nginx -g nginx \&lt;br /&gt;
  /var/www/letsencrypt/.well-known/acme-challenge&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create &amp;lt;code&amp;gt;/etc/nginx/conf.d/mail.conf&amp;lt;/code&amp;gt; with the initial HTTP virtual host:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nginx&amp;quot;&amp;gt;&lt;br /&gt;
server {&lt;br /&gt;
    listen 80;&lt;br /&gt;
    server_name mail.example.com;&lt;br /&gt;
&lt;br /&gt;
    location ^~ /.well-known/acme-challenge/ {&lt;br /&gt;
        root /var/www/letsencrypt;&lt;br /&gt;
        try_files $uri =404;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    location / {&lt;br /&gt;
        return 301 https://$host$request_uri;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Validate and start nginx:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nginx -t&lt;br /&gt;
systemctl enable --now nginx&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The public A record must reach &amp;lt;code&amp;gt;&amp;amp;lt;New Host&amp;amp;gt;&amp;lt;/code&amp;gt; for HTTP-01 validation. If DNS still points to &amp;lt;code&amp;gt;&amp;amp;lt;Old Host&amp;amp;gt;&amp;lt;/code&amp;gt;, temporarily move the record, use a supported DNS challenge, or wait until cutover.&lt;br /&gt;
&lt;br /&gt;
Request the certificate:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
certbot certonly \&lt;br /&gt;
  --webroot \&lt;br /&gt;
  -w /var/www/letsencrypt \&lt;br /&gt;
  -d &amp;quot;$MAIL_HOST&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11. Configure TLS for Postfix and Dovecot ===&lt;br /&gt;
&lt;br /&gt;
Configure Postfix:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
postconf -e &amp;quot;smtpd_tls_cert_file = /etc/letsencrypt/live/$MAIL_HOST/fullchain.pem&amp;quot;&lt;br /&gt;
postconf -e &amp;quot;smtpd_tls_key_file = /etc/letsencrypt/live/$MAIL_HOST/privkey.pem&amp;quot;&lt;br /&gt;
postconf -e &amp;#039;smtpd_tls_security_level = may&amp;#039;&lt;br /&gt;
postconf -e &amp;#039;smtp_tls_security_level = may&amp;#039;&lt;br /&gt;
postconf -e &amp;#039;smtp_tls_CAfile = /etc/pki/tls/certs/ca-bundle.crt&amp;#039;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Configure &amp;lt;code&amp;gt;/etc/dovecot/conf.d/10-ssl.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
ssl = required&lt;br /&gt;
ssl_cert = &amp;lt;/etc/letsencrypt/live/mail.example.com/fullchain.pem&lt;br /&gt;
ssl_key = &amp;lt;/etc/letsencrypt/live/mail.example.com/privkey.pem&lt;br /&gt;
ssl_cipher_list = PROFILE=SYSTEM&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the renewal deploy hook:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
vi /etc/letsencrypt/renewal-hooks/deploy/reload-mail-services.sh&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Use:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
systemctl reload nginx postfix dovecot&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Make it executable:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
chmod 750 /etc/letsencrypt/renewal-hooks/deploy/reload-mail-services.sh&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 12. Install Roundcube 1.7.2 ===&lt;br /&gt;
&lt;br /&gt;
Roundcube is installed manually and is not maintained by &amp;lt;code&amp;gt;dnf&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /usr/local/src&lt;br /&gt;
RC=1.7.2&lt;br /&gt;
&lt;br /&gt;
wget &amp;quot;https://github.com/roundcube/roundcubemail/releases/download/$RC/roundcubemail-$RC-complete.tar.gz&amp;quot;&lt;br /&gt;
tar xzf &amp;quot;roundcubemail-$RC-complete.tar.gz&amp;quot;&lt;br /&gt;
mv &amp;quot;roundcubemail-$RC&amp;quot; /var/www/roundcube&lt;br /&gt;
&lt;br /&gt;
chown -R root:root /var/www/roundcube&lt;br /&gt;
&lt;br /&gt;
install -d -m 750 -o nginx -g nginx \&lt;br /&gt;
  /var/www/roundcube/{temp,logs,db}&lt;br /&gt;
&lt;br /&gt;
runuser -u nginx -- sqlite3 /var/www/roundcube/db/roundcube.db \&lt;br /&gt;
  &amp;lt; /var/www/roundcube/SQL/sqlite.initial.sql&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Generate a new encryption secret:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl rand -base64 48&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create &amp;lt;code&amp;gt;/var/www/roundcube/config/config.inc.php&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$config = [];&lt;br /&gt;
&lt;br /&gt;
$config[&amp;#039;db_dsnw&amp;#039;] = &amp;#039;sqlite:////var/www/roundcube/db/roundcube.db&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
$config[&amp;#039;imap_host&amp;#039;] = &amp;#039;ssl://127.0.0.1:993&amp;#039;;&lt;br /&gt;
$config[&amp;#039;imap_conn_options&amp;#039;] = [&lt;br /&gt;
    &amp;#039;ssl&amp;#039; =&amp;gt; [&lt;br /&gt;
        &amp;#039;verify_peer&amp;#039; =&amp;gt; true,&lt;br /&gt;
        &amp;#039;verify_peer_name&amp;#039; =&amp;gt; true,&lt;br /&gt;
        &amp;#039;peer_name&amp;#039; =&amp;gt; &amp;#039;mail.example.com&amp;#039;,&lt;br /&gt;
        &amp;#039;cafile&amp;#039; =&amp;gt; &amp;#039;/etc/pki/tls/certs/ca-bundle.crt&amp;#039;,&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
$config[&amp;#039;smtp_host&amp;#039;] = &amp;#039;tls://127.0.0.1:587&amp;#039;;&lt;br /&gt;
$config[&amp;#039;smtp_user&amp;#039;] = &amp;#039;%u&amp;#039;;&lt;br /&gt;
$config[&amp;#039;smtp_pass&amp;#039;] = &amp;#039;%p&amp;#039;;&lt;br /&gt;
$config[&amp;#039;smtp_conn_options&amp;#039;] = [&lt;br /&gt;
    &amp;#039;ssl&amp;#039; =&amp;gt; [&lt;br /&gt;
        &amp;#039;verify_peer&amp;#039; =&amp;gt; true,&lt;br /&gt;
        &amp;#039;verify_peer_name&amp;#039; =&amp;gt; true,&lt;br /&gt;
        &amp;#039;peer_name&amp;#039; =&amp;gt; &amp;#039;mail.example.com&amp;#039;,&lt;br /&gt;
        &amp;#039;cafile&amp;#039; =&amp;gt; &amp;#039;/etc/pki/tls/certs/ca-bundle.crt&amp;#039;,&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
$config[&amp;#039;des_key&amp;#039;] = &amp;#039;&amp;lt;PASTE A NEW RANDOM SECRET HERE&amp;gt;&amp;#039;;&lt;br /&gt;
$config[&amp;#039;product_name&amp;#039;] = &amp;#039;Webmail&amp;#039;;&lt;br /&gt;
$config[&amp;#039;mail_domain&amp;#039;] = &amp;#039;example.com&amp;#039;;&lt;br /&gt;
$config[&amp;#039;message_id_domain&amp;#039;] = &amp;#039;example.com&amp;#039;;&lt;br /&gt;
$config[&amp;#039;skin&amp;#039;] = &amp;#039;elastic&amp;#039;;&lt;br /&gt;
$config[&amp;#039;enable_installer&amp;#039;] = false;&lt;br /&gt;
$config[&amp;#039;plugins&amp;#039;] = [&amp;#039;archive&amp;#039;, &amp;#039;zipdownload&amp;#039;, &amp;#039;markasjunk&amp;#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Secure the configuration:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
chown root:nginx /var/www/roundcube/config/config.inc.php&lt;br /&gt;
chmod 640 /var/www/roundcube/config/config.inc.php&lt;br /&gt;
chmod 000 /var/www/roundcube/installer 2&amp;gt;/dev/null || true&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 13. Tune PHP-FPM and enforce SELinux ===&lt;br /&gt;
&lt;br /&gt;
Edit &amp;lt;code&amp;gt;/etc/php-fpm.d/www.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
user = nginx&lt;br /&gt;
group = nginx&lt;br /&gt;
listen = /run/php-fpm/www.sock&lt;br /&gt;
listen.owner = nginx&lt;br /&gt;
listen.group = nginx&lt;br /&gt;
listen.mode = 0660&lt;br /&gt;
&lt;br /&gt;
pm = ondemand&lt;br /&gt;
pm.max_children = 3&lt;br /&gt;
pm.process_idle_timeout = 10s&lt;br /&gt;
pm.max_requests = 300&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Apply persistent SELinux contexts and permit PHP to connect to the local IMAP and SMTP services:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
semanage fcontext -a -t httpd_sys_content_t \&lt;br /&gt;
  &amp;#039;/var/www/roundcube(/.*)?&amp;#039;&lt;br /&gt;
&lt;br /&gt;
semanage fcontext -a -t httpd_sys_rw_content_t \&lt;br /&gt;
  &amp;#039;/var/www/roundcube/(temp|db)(/.*)?&amp;#039;&lt;br /&gt;
&lt;br /&gt;
semanage fcontext -a -t httpd_log_t \&lt;br /&gt;
  &amp;#039;/var/www/roundcube/logs(/.*)?&amp;#039;&lt;br /&gt;
&lt;br /&gt;
restorecon -Rv /var/www/roundcube&lt;br /&gt;
setsebool -P httpd_can_network_connect 1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 14. Add the Roundcube HTTPS virtual host ===&lt;br /&gt;
&lt;br /&gt;
Append this server block to &amp;lt;code&amp;gt;/etc/nginx/conf.d/mail.conf&amp;lt;/code&amp;gt;. Roundcube 1.7 requires the &amp;lt;code&amp;gt;public_html&amp;lt;/code&amp;gt; document root. The PHP expression also supports paths such as &amp;lt;code&amp;gt;static.php/...&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nginx&amp;quot;&amp;gt;&lt;br /&gt;
server {&lt;br /&gt;
    listen 443 ssl http2;&lt;br /&gt;
    server_name mail.example.com;&lt;br /&gt;
&lt;br /&gt;
    root /var/www/roundcube/public_html;&lt;br /&gt;
    index index.php;&lt;br /&gt;
&lt;br /&gt;
    ssl_certificate /etc/letsencrypt/live/mail.example.com/fullchain.pem;&lt;br /&gt;
    ssl_certificate_key /etc/letsencrypt/live/mail.example.com/privkey.pem;&lt;br /&gt;
    ssl_protocols TLSv1.2 TLSv1.3;&lt;br /&gt;
&lt;br /&gt;
    client_max_body_size 25M;&lt;br /&gt;
&lt;br /&gt;
    location = /installer.php {&lt;br /&gt;
        return 404;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    location ^~ /installer/ {&lt;br /&gt;
        return 404;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    location / {&lt;br /&gt;
        try_files $uri $uri/ /index.php?$query_string;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    location ~ ^(.+\.php)(/.*)?$ {&lt;br /&gt;
        try_files $1 =404;&lt;br /&gt;
        include fastcgi_params;&lt;br /&gt;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;&lt;br /&gt;
        fastcgi_param SCRIPT_FILENAME $document_root$1;&lt;br /&gt;
        fastcgi_param PATH_INFO $2;&lt;br /&gt;
        fastcgi_pass unix:/run/php-fpm/www.sock;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    location ~ /\. {&lt;br /&gt;
        deny all;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Validate every configuration before starting the complete stack:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
php -l /var/www/roundcube/config/config.inc.php&lt;br /&gt;
postfix check&lt;br /&gt;
doveconf -n &amp;gt;/dev/null&lt;br /&gt;
rspamadm configtest&lt;br /&gt;
nginx -t&lt;br /&gt;
&lt;br /&gt;
systemctl enable --now postfix dovecot rspamd nginx php-fpm&lt;br /&gt;
systemctl restart redis rspamd postfix dovecot php-fpm nginx&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15. Run the pre-cutover validation gate ===&lt;br /&gt;
&lt;br /&gt;
Validate services and configuration:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
systemctl --failed&lt;br /&gt;
postfix check&lt;br /&gt;
doveconf -n &amp;gt;/dev/null&lt;br /&gt;
rspamadm configtest&lt;br /&gt;
nginx -t&lt;br /&gt;
redis-cli ping&lt;br /&gt;
&lt;br /&gt;
postconf -n | egrep &amp;#039;^(myhostname|mailbox_transport|smtpd_milters)&amp;#039;&lt;br /&gt;
doveconf -n | egrep &amp;#039;^(mail_location|ssl|auth_username_format)&amp;#039;&lt;br /&gt;
&lt;br /&gt;
ss -lntp | egrep &amp;#039;:(25|80|443|587|993|11332|11334|6379)\b&amp;#039;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Expected public listeners are 25, 80, 443, 587, and 993. Redis and Rspamd must remain on loopback.&lt;br /&gt;
&lt;br /&gt;
Test certificates against &amp;lt;code&amp;gt;&amp;amp;lt;New Host&amp;amp;gt;&amp;lt;/code&amp;gt; before production DNS is moved:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl s_client \&lt;br /&gt;
  -connect &amp;quot;$NEW_IP:993&amp;quot; \&lt;br /&gt;
  -servername &amp;quot;$MAIL_HOST&amp;quot; \&lt;br /&gt;
  &amp;lt;/dev/null&lt;br /&gt;
&lt;br /&gt;
openssl s_client \&lt;br /&gt;
  -starttls smtp \&lt;br /&gt;
  -connect &amp;quot;$NEW_IP:587&amp;quot; \&lt;br /&gt;
  -servername &amp;quot;$MAIL_HOST&amp;quot; \&lt;br /&gt;
  &amp;lt;/dev/null&lt;br /&gt;
&lt;br /&gt;
curl --resolve &amp;quot;$MAIL_HOST:443:$NEW_IP&amp;quot; \&lt;br /&gt;
  -fsSI &amp;quot;https://$MAIL_HOST/&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test local delivery through Postfix and Dovecot LMTP:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
printf &amp;#039;Subject: local test\n\nLMTP works.\n&amp;#039; \&lt;br /&gt;
  | sendmail &amp;quot;$MAIL_USER@$MAIL_DOMAIN&amp;quot;&lt;br /&gt;
&lt;br /&gt;
postqueue -p&lt;br /&gt;
journalctl -u postfix -u dovecot -u rspamd --since -10m --no-pager&lt;br /&gt;
doveadm search -u &amp;quot;$MAIL_USER&amp;quot; mailbox INBOX ALL&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test certificate renewal only after the selected ACME method can reach &amp;lt;code&amp;gt;&amp;amp;lt;New Host&amp;amp;gt;&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
certbot renew --dry-run&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 16. Prepare production DNS ===&lt;br /&gt;
&lt;br /&gt;
Create or verify these records:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
mail.example.com.             A      &amp;lt;New Host&amp;gt;&lt;br /&gt;
example.com.                  MX 0   mail.example.com.&lt;br /&gt;
example.com.                  TXT    &amp;quot;v=spf1 ip4:&amp;lt;New Host&amp;gt; -all&amp;quot;&lt;br /&gt;
dkim._domainkey.example.com.  TXT    &amp;quot;&amp;lt;generated DKIM public key&amp;gt;&amp;quot;&lt;br /&gt;
_dmarc.example.com.           TXT    &amp;quot;v=DMARC1; p=none&amp;quot;&lt;br /&gt;
&amp;lt;New Host&amp;gt;                    PTR    mail.example.com.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Verify them:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
dig +short A &amp;quot;$MAIL_HOST&amp;quot;&lt;br /&gt;
dig +short MX &amp;quot;$MAIL_DOMAIN&amp;quot;&lt;br /&gt;
dig +short TXT &amp;quot;$MAIL_DOMAIN&amp;quot;&lt;br /&gt;
dig +short TXT &amp;quot;dkim._domainkey.$MAIL_DOMAIN&amp;quot;&lt;br /&gt;
dig +short TXT &amp;quot;_dmarc.$MAIL_DOMAIN&amp;quot;&lt;br /&gt;
dig +short -x &amp;quot;$NEW_IP&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border-left: 5px solid #a43b3b; background: #fcedec; padding: 0.7em 1em; margin: 1em 0;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Do not cut over&amp;#039;&amp;#039;&amp;#039; with a stale AAAA record, mismatched PTR, failing certificate name, open relay, or unverified DKIM record.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Phase 3: Migrate, cut over, and recover ==&lt;br /&gt;
&lt;br /&gt;
=== 17. Seed the Maildir while the old server is live ===&lt;br /&gt;
&lt;br /&gt;
Create a dedicated known-hosts file on &amp;lt;code&amp;gt;&amp;amp;lt;New Host&amp;amp;gt;&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
install -d -m 700 /root/.ssh&lt;br /&gt;
ssh-keyscan -H &amp;quot;$OLD_IP&amp;quot; &amp;gt; /root/.ssh/known_hosts.oldmail&lt;br /&gt;
chmod 600 /root/.ssh/known_hosts.oldmail&lt;br /&gt;
ssh-keygen -lf /root/.ssh/known_hosts.oldmail&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Verify the displayed SSH fingerprint out-of-band before transferring mail.&lt;br /&gt;
&lt;br /&gt;
Perform the initial synchronization while &amp;lt;code&amp;gt;&amp;amp;lt;Old Host&amp;amp;gt;&amp;lt;/code&amp;gt; remains live:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
rsync -aHvx \&lt;br /&gt;
  --numeric-ids \&lt;br /&gt;
  --chown=&amp;quot;$MAIL_USER:$MAIL_USER&amp;quot; \&lt;br /&gt;
  --exclude=&amp;#039;/tmp/***&amp;#039; \&lt;br /&gt;
  --exclude=&amp;#039;/**/tmp/***&amp;#039; \&lt;br /&gt;
  --exclude=&amp;#039;dovecot.index*&amp;#039; \&lt;br /&gt;
  --exclude=&amp;#039;dovecot.list.index*&amp;#039; \&lt;br /&gt;
  --exclude=&amp;#039;dovecot.mailbox.log*&amp;#039; \&lt;br /&gt;
  -e &amp;#039;ssh -o UserKnownHostsFile=/root/.ssh/known_hosts.oldmail&amp;#039; \&lt;br /&gt;
  &amp;quot;root@$OLD_IP:/home/$MAIL_USER/Maildir/&amp;quot; \&lt;br /&gt;
  &amp;quot;/home/$MAIL_USER/Maildir/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
chown -R &amp;quot;$MAIL_USER:$MAIL_USER&amp;quot; &amp;quot;/home/$MAIL_USER/Maildir&amp;quot;&lt;br /&gt;
doveadm force-resync -u &amp;quot;$MAIL_USER&amp;quot; &amp;#039;*&amp;#039;&lt;br /&gt;
du -sh &amp;quot;/home/$MAIL_USER/Maildir&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border-left: 5px solid #c98720; background: #fff5e3; padding: 0.7em 1em; margin: 1em 0;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Data safety:&amp;#039;&amp;#039;&amp;#039; Do not exclude &amp;lt;code&amp;gt;.Junk&amp;lt;/code&amp;gt; unless you deliberately want to discard it. Excluding it is a mailbox-data decision, not a harmless optimization.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 18. Freeze mail, perform the final sync, and cut over ===&lt;br /&gt;
&lt;br /&gt;
If DNS rather than an IP move will be used, lower the relevant TTLs beforehand. Announce a short maintenance window.&lt;br /&gt;
&lt;br /&gt;
Stop mailbox changes on &amp;lt;code&amp;gt;&amp;amp;lt;Old Host&amp;amp;gt;&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
systemctl stop postfix dovecot&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run the final authoritative synchronization on &amp;lt;code&amp;gt;&amp;amp;lt;New Host&amp;amp;gt;&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
rsync -aHvx \&lt;br /&gt;
  --delete \&lt;br /&gt;
  --numeric-ids \&lt;br /&gt;
  --chown=&amp;quot;$MAIL_USER:$MAIL_USER&amp;quot; \&lt;br /&gt;
  --exclude=&amp;#039;/tmp/***&amp;#039; \&lt;br /&gt;
  --exclude=&amp;#039;/**/tmp/***&amp;#039; \&lt;br /&gt;
  --exclude=&amp;#039;dovecot.index*&amp;#039; \&lt;br /&gt;
  --exclude=&amp;#039;dovecot.list.index*&amp;#039; \&lt;br /&gt;
  --exclude=&amp;#039;dovecot.mailbox.log*&amp;#039; \&lt;br /&gt;
  -e &amp;#039;ssh -o UserKnownHostsFile=/root/.ssh/known_hosts.oldmail&amp;#039; \&lt;br /&gt;
  &amp;quot;root@$OLD_IP:/home/$MAIL_USER/Maildir/&amp;quot; \&lt;br /&gt;
  &amp;quot;/home/$MAIL_USER/Maildir/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
chown -R &amp;quot;$MAIL_USER:$MAIL_USER&amp;quot; &amp;quot;/home/$MAIL_USER/Maildir&amp;quot;&lt;br /&gt;
doveadm force-resync -u &amp;quot;$MAIL_USER&amp;quot; &amp;#039;*&amp;#039;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Move the production IP or update A, MX, and PTR records as planned. Then restart the new stack:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
systemctl restart redis rspamd postfix dovecot php-fpm nginx&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 19. Prove production end-to-end ===&lt;br /&gt;
&lt;br /&gt;
Verify production DNS and listeners:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
dig +short A &amp;quot;$MAIL_HOST&amp;quot;&lt;br /&gt;
dig +short -x &amp;quot;$NEW_IP&amp;quot;&lt;br /&gt;
ss -lntp | egrep &amp;#039;:(25|80|443|587|993)\b&amp;#039;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Verify HTTPS, IMAPS, and submission TLS:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
curl -fsSI &amp;quot;https://$MAIL_HOST/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
openssl s_client \&lt;br /&gt;
  -connect &amp;quot;$MAIL_HOST:993&amp;quot; \&lt;br /&gt;
  -servername &amp;quot;$MAIL_HOST&amp;quot; \&lt;br /&gt;
  &amp;lt;/dev/null&lt;br /&gt;
&lt;br /&gt;
openssl s_client \&lt;br /&gt;
  -starttls smtp \&lt;br /&gt;
  -connect &amp;quot;$MAIL_HOST:587&amp;quot; \&lt;br /&gt;
  -servername &amp;quot;$MAIL_HOST&amp;quot; \&lt;br /&gt;
  &amp;lt;/dev/null&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Check the queue and recent logs:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
postqueue -p&lt;br /&gt;
journalctl -u postfix -u dovecot -u rspamd --since -30m --no-pager&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Complete these manual acceptance tests from outside the local network:&lt;br /&gt;
&lt;br /&gt;
* Log into Roundcube and read migrated messages.&lt;br /&gt;
* Send a message to an external provider.&lt;br /&gt;
* Reply from the external provider back to the new server.&lt;br /&gt;
* Confirm IMAPS and authenticated submission work in a mail client.&lt;br /&gt;
* Move a message to Junk and verify the mailbox behavior.&lt;br /&gt;
* Inspect received headers for TLS, SPF pass, DKIM pass, DMARC pass, and the expected Rspamd action.&lt;br /&gt;
* Confirm Maildir size and message counts are reasonable.&lt;br /&gt;
* Confirm the queue is clear and no unexpected units have failed.&lt;br /&gt;
&lt;br /&gt;
Leave &amp;lt;code&amp;gt;&amp;amp;lt;Old Host&amp;amp;gt;&amp;lt;/code&amp;gt; intact and powered off—not repurposed—until several days of delivery, login, queue, backup, and renewal checks have passed.&lt;br /&gt;
&lt;br /&gt;
=== 20. Roll back without creating split-brain ===&lt;br /&gt;
&lt;br /&gt;
Rollback is justified for sustained inbound or outbound failure, mailbox-data trouble, or authentication/TLS failure that cannot be corrected inside the maintenance window. A cosmetic Roundcube problem alone may not justify moving SMTP back.&lt;br /&gt;
&lt;br /&gt;
Freeze &amp;lt;code&amp;gt;&amp;amp;lt;New Host&amp;amp;gt;&amp;lt;/code&amp;gt; before changing routing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
systemctl stop postfix dovecot&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Preserve messages accepted only by &amp;lt;code&amp;gt;&amp;amp;lt;New Host&amp;amp;gt;&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
rsync -aHvx \&lt;br /&gt;
  &amp;quot;/home/$MAIL_USER/Maildir/&amp;quot; \&lt;br /&gt;
  &amp;quot;root@$OLD_IP:/home/$MAIL_USER/Maildir.rollback-from-new/&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Move the production IP, DNS, and PTR back to &amp;lt;code&amp;gt;&amp;amp;lt;Old Host&amp;amp;gt;&amp;lt;/code&amp;gt;. Then start the old services:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
systemctl start dovecot postfix&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Verify A/PTR records, ports, queue state, inbound delivery, outbound delivery, and IMAPS. Reconcile &amp;lt;code&amp;gt;Maildir.rollback-from-new&amp;lt;/code&amp;gt; before deleting anything.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border-left: 5px solid #a43b3b; background: #fcedec; padding: 0.7em 1em; margin: 1em 0;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Never leave Postfix active on both servers while the same public identity is moving.&amp;#039;&amp;#039;&amp;#039; That is how a clean rollback turns into a mail rodeo.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 21. Back up the irreplaceable data ===&lt;br /&gt;
&lt;br /&gt;
Create a configuration backup:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
BACK=&amp;quot;/root/mail-backup-$(date +%F-%H%M)&amp;quot;&lt;br /&gt;
mkdir -p &amp;quot;$BACK&amp;quot;&lt;br /&gt;
&lt;br /&gt;
tar --xattrs --acls -czf &amp;quot;$BACK/configs.tgz&amp;quot; \&lt;br /&gt;
  /etc/postfix \&lt;br /&gt;
  /etc/dovecot \&lt;br /&gt;
  /etc/rspamd \&lt;br /&gt;
  /etc/nginx \&lt;br /&gt;
  /etc/php-fpm.d \&lt;br /&gt;
  /etc/letsencrypt \&lt;br /&gt;
  /etc/firewalld \&lt;br /&gt;
  /var/www/roundcube/config \&lt;br /&gt;
  /var/www/roundcube/db \&lt;br /&gt;
  /var/lib/rspamd/dkim&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Back up Maildir and Rspamd data to storage outside the mail server:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
rsync -aHAX \&lt;br /&gt;
  &amp;quot;/home/$MAIL_USER/Maildir/&amp;quot; \&lt;br /&gt;
  &amp;quot;&amp;lt;BACKUP_TARGET&amp;gt;/Maildir/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
cp -a /var/lib/rspamd &amp;quot;&amp;lt;BACKUP_TARGET&amp;gt;/rspamd-data/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
test ! -f /root/mail-admin-check || \&lt;br /&gt;
  cp -a /root/mail-admin-check &amp;quot;$BACK/&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Store backup encryption keys and DKIM private keys off-host. A backup kept only beside the server is merely a second copy waiting for the same shovel.&lt;br /&gt;
&lt;br /&gt;
=== 22. Recover on bare metal ===&lt;br /&gt;
&lt;br /&gt;
Use this order:&lt;br /&gt;
&lt;br /&gt;
# Install a clean AlmaLinux 9 system.&lt;br /&gt;
# Restore the original mailbox UID and GID.&lt;br /&gt;
# Install the repositories and packages from this guide.&lt;br /&gt;
# Restore Postfix, Dovecot, Rspamd, nginx, PHP-FPM, firewall, and certificate configuration.&lt;br /&gt;
# Restore the Roundcube configuration and SQLite database.&lt;br /&gt;
# Restore DKIM keys and Rspamd data.&lt;br /&gt;
# Restore Maildir.&lt;br /&gt;
# Correct ownership and SELinux labels.&lt;br /&gt;
# Validate every configuration.&lt;br /&gt;
# Start services and repeat the end-to-end tests.&lt;br /&gt;
&lt;br /&gt;
Final recovery commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
chown -R &amp;quot;$MAIL_USER:$MAIL_USER&amp;quot; &amp;quot;/home/$MAIL_USER/Maildir&amp;quot;&lt;br /&gt;
chown _rspamd:_rspamd /var/lib/rspamd/dkim/*.key&lt;br /&gt;
restorecon -RFv /etc /var/www/roundcube /var/lib/rspamd&lt;br /&gt;
&lt;br /&gt;
postfix check&lt;br /&gt;
doveconf -n &amp;gt;/dev/null&lt;br /&gt;
rspamadm configtest&lt;br /&gt;
nginx -t&lt;br /&gt;
&lt;br /&gt;
systemctl restart redis rspamd postfix dovecot php-fpm nginx&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 23. Patch, renew, and monitor ===&lt;br /&gt;
&lt;br /&gt;
Install the completed health script and run the summary with update checks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
install -o root -g root -m 750 \&lt;br /&gt;
  &amp;lt;MAIL_ADMIN_CHECK&amp;gt; \&lt;br /&gt;
  /root/mail-admin-check&lt;br /&gt;
&lt;br /&gt;
/root/mail-admin-check --summary --check-updates&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Routine administration commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
dnf check-update || test $? -eq 100&lt;br /&gt;
postqueue -p&lt;br /&gt;
systemctl --failed&lt;br /&gt;
certbot renew --dry-run&lt;br /&gt;
journalctl -p warning --since today --no-pager&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Roundcube is installed manually, so &amp;lt;code&amp;gt;dnf&amp;lt;/code&amp;gt; will not update it. Before every Roundcube upgrade:&lt;br /&gt;
&lt;br /&gt;
# Back up &amp;lt;code&amp;gt;/var/www/roundcube&amp;lt;/code&amp;gt; and the SQLite database.&lt;br /&gt;
# Download and unpack the current complete release tarball.&lt;br /&gt;
# Read the release&amp;#039;s &amp;lt;code&amp;gt;UPGRADING&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;INSTALL&amp;lt;/code&amp;gt; files.&lt;br /&gt;
# Run the bundled upgrade tool.&lt;br /&gt;
# Reapply SELinux contexts and validate nginx and PHP.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /usr/local/src/roundcubemail-&amp;lt;NEW_VERSION&amp;gt;&lt;br /&gt;
bin/installto.sh /var/www/roundcube&lt;br /&gt;
&lt;br /&gt;
php -l /var/www/roundcube/config/config.inc.php&lt;br /&gt;
restorecon -RFv /var/www/roundcube&lt;br /&gt;
nginx -t&lt;br /&gt;
systemctl reload php-fpm nginx&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reference links ==&lt;br /&gt;
&lt;br /&gt;
* [https://github.com/roundcube/roundcubemail/releases Roundcube releases]&lt;br /&gt;
* [https://github.com/roundcube/roundcubemail/wiki/Upgrade Roundcube upgrade instructions]&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/installation/ Rspamd installation guide]&lt;br /&gt;
* [https://doc.dovecot.org/main/howto/lmtp/postfix.html Dovecot LMTP with Postfix]&lt;br /&gt;
* [https://doc.dovecot.org/main/howto/sasl/postfix.html Dovecot SASL with Postfix]&lt;br /&gt;
* [https://eff-certbot.readthedocs.io/en/stable/using.html Certbot user guide and renewal hooks]&lt;br /&gt;
&lt;br /&gt;
[[Category:Linux]]&lt;br /&gt;
[[Category:Mail servers]]&lt;br /&gt;
[[Category:AlmaLinux]]&lt;br /&gt;
[[Category:System administration]]&lt;/div&gt;</summary>
		<author><name>Chris</name></author>
	</entry>
</feed>