This article covers the how to steps for configuring an email server on Fedora 35. This move was instigated by the demise of the free Google Suite service which ultimately decided to convert into a pay service. What was promised as free for many years had become an irrationally priced service and therefore the move had to be made. This article will cover how to set up a multi domain email server with web access and migrating emails from Google server onto your new server. The goal of this installation is a multi domain server with SSL and web access via round cube. This will involve the installation of Postfix, Dovecot, MySQL, Spamassassin, Apache, and Roundcube. SSL will be handled by Let’s Encrypt.
Of course this was not possible without the help of many excellent posts found on the interwebs. Here is a list of those links.
https://devinstechblog.com/postfix-mailserver-configuration-part-1/
https://z-issue.com/wp/letsencrypt-ssl-certificates-for-vhosts-within-the-mail-stack-via-postfix-smtp-and-dovecot-imap/
https://www.digitalocean.com/community/tutorials/how-to-configure-a-mail-server-using-postfix-dovecot-mysql-and-spamassassin
https://ixnfo.com/en/mail-server-postfix-dovecot-mysql.html
https://linux-audit.com/postfix-hardening-guide-for-security-and-privacy/
https://pub.nethence.com/mail/postfix
https://www.linode.com/docs/guides/email-with-postfix-dovecot-and-mysql/
https://www.davekb.com/browse_computer_tips:spamassassin_with_postfix
https://www.linuxbabe.com/redhat/spamassassin-centos-rhel-block-email-spam
https://cwiki.apache.org/confluence/display/SPAMASSASSIN/customplugins
https://www.digitalocean.com/community/tutorials/how-to-install-your-own-webmail-client-with-roundcube-on-ubuntu-16-04
https://blog.stefandroid.com/2021/06/20/migrate-gmail-emails.html
https://tecadmin.net/use-imapsync-on-ubuntu/
https://www.immuniweb.com/ssl
Prerequisite
dnf update
dnf install wget
dnf install rsyslog
systemctl start rsyslog
SSH configuration
By default port 22 is used and root can connect. Digital Ocean requires SSH key generation so that root can ssh in without passwords initially. Password authentication is disabled and a ssh key is required. New SSH keys can be added in the settings section of DigitalOcean. They can also be manually added post server creation by creating a file locally on the server then using cat key.pub >> .ssh/authorized_keys
This manual key add is not allowing console access… TODO we need to see if this is caused by the port 2229 change by testing on the originating box, ireland.
Port 2229 is never set in SSH configuration, rather it is configured in the firewall as a forwarding rule from 2229 to 22.
Firewall
Adjust IP addresses on internal zone to match SQL server
/etc/firewalld/zones/public.xm
<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>Internal</short>
<description>Custom internal zone by europheus.</description>
<interface name="eth1"/>
<service name="dhcpv6-client"/>
<rule family="ipv4">
<source address="10.100.100.34"/>
<service name="mysql"/>
<accept/>
</rule>
<rule family="ipv4">
<source address="10.100.100.33"/>
<service name="ssh"/>
<accept/>
</rule>
</zone>
DNS Configuration
For each domain that you will set up on this server, you need to add the following DNS entries. Remove any other existing entries such as GMAIL.
Type Name Data TTL MX
@ mail.domain1.com 1 hour TXT
@ v=spf1 ip4:mailserverIPHERE ~all 1 hour
SSL Certificates
https://z-issue.com/wp/letsencrypt-ssl-certificates-for-vhosts-within-the-mail-stack-via-postfix-smtp-and-dovecot-imap/
For the cert generation to work you need to configure apache to serve the domain on port 80. This will be a temporary setup that you will change later when installing Roundcube.
Edit: /etc/httpd/conf.d/yourhostname.conf
<VirtualHost *:80>
ServerName mail.domain1.com
ServerAdmin domain@domain1.com
DocumentRoot /var/www
ErrorLog /var/log/httpd/error.log
CustomLog /var/log/httpd/access.log combined
</VirtualHost>
Then run certbot to issue a cert per domain. Repeat these steps for each domain.
certbot certonly --agree-tos --email you@yourmail.com --non-interactive --webroot --webroot-path /var/www --domains mail.domain1.com
Setup cron to update the certificates because they expire every 90 days.
As root – crontab -e
## LetsEncrypt certificate renewals on first of each month
0 2 1 * * /usr/bin/certbot renew --quiet
Next you will need to add a custom script in the renewal hooks directory to restart postfix and apache.
/etc/letsencrypt/renewal-hooks/post/
#!/bin/sh
#
# Simple script to restart services after certificate update
#
/usr/sbin/postmap -F hash:/etc/postfix/vmail_ssl
/usr/bin/systemctl restart postfix
/usr/bin/systemctl restart dovecot
/usr/bin/systemctl restart httpd
How to guide for using MySQL as the source of accounts and domains for Postfix.
https://www.digitalocean.com/community/tutorials/how-to-configure-a-mail-server-using-postfix-dovecot-mysql-and-spamassassin
Install then start it with systemctl start mysqld
Check /var/log/mysqld.log for the temp password
Login using the temp password and apply a strong password change
ALTER USER 'root'@'localhost' IDENTIFIED BY 'bigass-strong-password-with-all-stuff’;
Create tables and usermail user
CREATE USER 'usermail'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT ON servermail.* TO 'usermail'@'localhost';
CREATE TABLE `virtual_domains` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `virtual_users` (
`id` INT NOT NULL AUTO_INCREMENT,
`domain_id` INT NOT NULL,
`password` VARCHAR(256) NOT NULL,
`email` VARCHAR(120) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
alter table `virtual_users` modify column `password` VARCHAR(148) NOT NULL;
CREATE TABLE `virtual_aliases` (
`id` INT NOT NULL AUTO_INCREMENT,
`domain_id` INT NOT NULL,
`source` varchar(100) NOT NULL,
`destination` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `servermail`.`virtual_domains`
(`id` ,`name`)
VALUES
('1', domain1.com'),
('2', domain2.com');
INSERT INTO `servermail`.`virtual_users`
(`id`, `domain_id`, `password` , `email`)
VALUES
('1', '1', TO_BASE64(UNHEX(SHA2('password-here', 512))), 'user@domain1.com'),
('2', '2', TO_BASE64(UNHEX(SHA2('password-here', 512))), 'user@domain2.com');
INSERT INTO `servermail`.`virtual_aliases`
(`id`, `domain_id`, `source`, `destination`)
VALUES
('1', '1', 'domain@domain1.com', user@domain1.com'),
('2', '2', 'domain@domain2.com', 'user@domain2.com');
Postfix
dnf install postfix postfix-mysql
Install
https://ixnfo.com/en/mail-server-postfix-dovecot-mysql.html
Hardening
https://linux-audit.com/postfix-hardening-guide-for-security-and-privacy/
https://pub.nethence.com/mail/postfix
Edit /etc/postfix/master.cf
These sections will be commented out by default. You can uncomment the top level line and include these -o options or you can leave them commented out and append this to the end of the file. There will be more changes later for adding spam assassin.
submission inet n - n - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
-o smtpd_reject_unlisted_recipient=no
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
smtps inet n - - - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
Edit /etc/postfix/main.cf
As with other files, the easy aka lazy way is to append these settings as a chunk at the bottom of the file. The problem with this one in particular is that Postfix will nag you endlessly when some of the settings are already enabled and therefore overridden by your settings at the end of the file. To resolve this you can either check the log file or check the enabled settings by running the postconf command: postconf -n
The postconf -n command is handy for various reasons, such as for asking for help in forums and you will see it used there frequently.
Be sure to tag your custom changes with a comment separating the default settings with your own.
# BEGIN CUSTON CHANGES
smtpd_banner = $myhostname SPAMMERS ARE REPORTED
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
# TLS parameters
smtpd_tls_cert_file=/etc/letsencrypt/live/mail.domain1.com/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/mail.domain1.com/privkey.pem
smtpd_use_tls=yes
smtpd_tls_auth_only = yes
smtp_tls_security_level = encrypt
smtpd_tls_security_level = encrypt
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
# Authentication
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
# Restrictions
smtpd_helo_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_invalid_helo_hostname,
reject_non_fqdn_helo_hostname
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_non_fqdn_recipient,
reject_unknown_recipient_domain,
reject_unlisted_recipient,
reject_unauth_destination
smtpd_sender_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_non_fqdn_sender,
reject_unknown_sender_domain
smtpd_relay_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
defer_unauth_destination
myhostname = mail.domain1.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydomain = mail.domain1.com
myorigin = $mydomain
mydestination = localhost
inet_interfaces = all
inet_protocols = all
# disable insecure encryption
smtpd_tls_mandatory_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtp_tls_mandatory_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtpd_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtp_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtpd_tls_exclude_ciphers = MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL, ARIA, RSA, AES128
smtp_tls_exclude_ciphers = MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL, ARIA, RSA, AES128
# Handing off local delivery to Dovecot's LMTP, and telling it where to store mail
virtual_transport = lmtp:unix:private/dovecot-lmtp
# Virtual domains, users, and aliases
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf
# Even more Restrictions and MTA params
disable_vrfy_command = yes
strict_rfc821_envelopes = yes
smtpd_delay_reject = yes
smtpd_helo_required = yes
smtp_always_send_ehlo = yes
smtpd_timeout = 30s
smtp_helo_timeout = 15s
smtp_rcpt_timeout = 15s
smtpd_recipient_limit = 40
minimal_backoff_time = 180s
maximal_backoff_time = 3h
# Reply Rejection Codes
invalid_hostname_reject_code = 550
non_fqdn_reject_code = 550
unknown_address_reject_code = 550
unknown_client_reject_code = 550
unknown_hostname_reject_code = 550
unverified_recipient_reject_code = 550
unverified_sender_reject_code = 550
## Mappings for SMTP SSL certs when SNI is enabled
tls_server_sni_maps = hash:/etc/postfix/vmail_ssl
Create file /etc/postfix/mysql-virtual-mailbox-domains.cf
user = usermail
password = mailpassword
hosts = 127.0.0.1
dbname = servermail
query = SELECT 1 FROM virtual_domains WHERE name='%s'
Create file /etc/postfix/mysql-virtual-mailbox-maps.cf
user = usermail
password = mailpassword
hosts = 127.0.0.1
dbname = servermail
query = SELECT 1 FROM virtual_users WHERE email='%s'
Create file /etc/postfix/mysql-virtual-alias-maps.cf
user = usermail
password = mailpassword
hosts = 127.0.0.1
dbname = servermail
query = SELECT destination FROM virtual_aliases WHERE source='%s'
Run tests to verify the mysql connections
service postfix restart
postmap -q domain1.com mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
postmap -q user@domain1.com mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
postmap -q alias@domain1.com mysql:/etc/postfix/mysql-virtual-alias-maps.cf
SSL Configuration
Create file /etc/postfix/vmail_ssl
mail.domain1.com /etc/letsencrypt/live/mail.domain1.com/privkey.pem /etc/letsencrypt/live/mail.domain1.com/fullchain.pem
mail.domain2.com /etc/letsencrypt/live/mail.domain2.com/privkey.pem /etc/letsencrypt/live/mail.domain2.com/fullchain.pem
postmap -F hash:/etc/postfix/vmail_ssl
service postfix restart
Dovecot
https://www.linode.com/docs/guides/email-with-postfix-dovecot-and-mysql/
dnf install dovecot dovecot-mysql
Make a backup of files that are to be edited
cp /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.orig
cp /etc/dovecot/conf.d/10-mail.conf /etc/dovecot/conf.d/10-mail.conf.orig
cp /etc/dovecot/conf.d/10-auth.conf /etc/dovecot/conf.d/10-auth.conf.orig
cp /etc/dovecot/dovecot-sql.conf.ext /etc/dovecot/dovecot-sql.conf.ext.orig
cp /etc/dovecot/conf.d/10-master.conf /etc/dovecot/conf.d/10-master.conf.orig
cp /etc/dovecot/conf.d/10-ssl.conf /etc/dovecot/conf.d/10-ssl.conf.orig
Edit /etc/dovecot/dovecot.conf
!include_try /usr/share/dovecot/protocols.d/*.protocol
protocols = imap pop3 lmtp
listen = *, ::
log_path = /var/log/dovecot.log
Create /etc/logrotate.d/dovecot
# dovecot SIGUSR1: Re-opens the log files.
/var/log/dovecot*.log {
missingok
notifempty
delaycompress
sharedscripts
postrotate
/bin/kill -USR1 `cat /var/run/dovecot/master.pid 2>/dev/null` 2> /dev/null || true
endscript
}
Edit /etc/dovecot/conf.d/10-mail.conf
mail_location = maildir:/var/mail/vhosts/%d/%n
mail_privileged_group = mail
Create directories for each domain
mkdir -p /var/mail/vhosts/
Set permissions
groupadd -g 5000 vmail
useradd -g vmail -u 5000 vmail -d /var/mail
chown -R vmail:vmail /var/mail/vhosts
Edit /etc/dovecot/conf.d/10-auth.conf
disable_plaintext_auth = yes
auth_mechanisms = plain login
!include auth-sql.conf.ext
!include auth-system.conf.ext
Edit /etc/dovecot/conf.d/auth-sql.conf.ext
passdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}
userdb {
driver = static
args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n
}
Create / edit /etc/dovecot/dovecot-sql.conf.ext
Use SHA512 instead of SHA512-CRYPT for mysql 8
driver = mysql
connect = host=127.0.0.1 dbname=servermail user=usermail password=mailpassword
default_pass_scheme = SHA512
password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';
Set permissions
chown -R vmail:dovecot /etc/dovecot
chmod -R o-rwx /etc/dovecot
Edit /etc/dovecot/conf.d/10-master.conf
Uncomment inet_listener_imap and modify to port 0
service imap-login {
inet_listener imap {
port = 0
}
inet_listener imaps {
port = 993
ssl = yes
}
service pop3-login {
inet_listener pop3 {
port = 0
}
inet_listener pop3s {
port = 995
ssl = yes
}
}
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
#inet_listener lmtp {
# Avoid making LMTP visible for the entire internet
#address =
#port =
#}
}
service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
unix_listener auth-userdb {
mode = 0600
user = vmail
#group =
}
#unix_listener /var/spool/postfix/private/auth {
# mode = 0666
#}
user = dovecot
}
service auth-worker {
# Auth worker process is run as root by default, so that it can access
# /etc/shadow. If this isn't necessary, the user should be changed to
# $default_internal_user.
user = vmail
}
Edit /etc/dovecot/conf.d/10-ssl.conf
Add entries for each domain
local_name mail.domain1.com {
ssl_cert = /etc/letsencrypt/live/mail.domain1.com/fullchain.pem
ssl_key = /etc/letsencrypt/live/mail.domain1.com/privkey.pem
}
Test connections
openssl s_client -connect mail.domain1.com:993
openssl s_client -connect mail.domain1.com:995
Test account authentication
doveadm auth test user@domain1.com 'password’
Spamassassin
https://www.davekb.com/browse_computer_tips:spamassassin_with_postfix:txt
https://www.linuxbabe.com/redhat/spamassassin-centos-rhel-block-email-spam
https://cwiki.apache.org/confluence/display/SPAMASSASSIN/customplugins
dnf install spamassassin
useradd --base-dir=/var/lib/spamassassin --shell=/usr/sbin/nologin spamd
Edit /etc/sysconfig/spamassassin
SPAMD_HOME="/var/lib/spamassassin"
Add -u spamd -H ${SPAMD_HOME} to SPAMDOPTIONS
ENABLED=1
CRON=1
systemctl enable spamassassin
systemctl start spamassassin
Edit /etc/postfix/master.cf
smtp inet n - - - - smtpd
-o content_filter=spamassassin
spamassassin unix - n n - - pipe
user=spamd argv=/usr/bin/spamc -f -e
/usr/sbin/sendmail -oi -f ${sender} ${recipient}
Apache
dnf install httpd mod_ssl
dnf install php-cli php-fpm php-mysqlnd php-zip php-devel php-gd php-mcrypt php-mbstring php-curl php-xml php-pear php-bcmath php-json
systemctl enable httpd
dnf install php-xml php-mbstring php-intl php-zip php-pear zip unzip git composer
Visit https://roundcube.net/download/ and download with wget the Complete version.
wget https://github.com/roundcube/roundcubemail/releases/download/1.5.2/roundcubemail-1.5.2-complete.tar.gz
tar -xzf roundcubemail-1.5.2-complete.tar.gz
mv roundcubemail-1.5.2 /var/www/roundcube
vi /etc/httpd/conf.d/mail.domain1.conf
This assumes you have configured SSL, if not, comment out the Rewrite and SSL directives.
<VirtualHost *:80>
ServerName mail.domain1.com
ServerAdmin domain@domain1.com
DocumentRoot /var/www/roundcube
ErrorLog /var/log/httpd/error.log
CustomLog /var/log/httpd/access.log combined
RewriteEngine on
RewriteCond %{SERVER_NAME} =mail.domain1.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName mail.domain1.com
ServerAdmin domain@domain1.com
DocumentRoot /var/www/roundcube
ErrorLog /var/log/httpd/error.log
CustomLog /var/log/httpd/access.log combined
SSLCertificateFile /etc/letsencrypt/live/mail.domain1.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/mail.domain1.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/mail.domain1.com/chain.pem
</VirtualHost>
</IfModule>
Login to mysql as root
CREATE DATABASE roundcubemail /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
CREATE USER 'roundcube'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON roundcubemail.* to 'roundcube'@'localhost';
FLUSH PRIVILEGES;
Next import pre-existing DDL that comes with roundcube
mysql -u roundcube -p roundcubemail < /var/www/roundcube/SQL/mysql.initial.sql
Start apache and navigate to the installer page or you can edit the config file
http://your_server_ip_or_domain/installer
/var/www/roundcube/config/config.inc.php
$config['default_host'] = 'ssl://mail.domain1.com';
Remove installer after complete
sudo rm -rf /var/www/roundcube/installer/
If needed, config settings can be split out under each domain with a config sub file per domain.
https://github.com/roundcube/roundcubemail/wiki/Configuration%3A-Multi-Domain-Setup
$config['smtp_server'] = 'tls://serverhostname.com';
Migrating Mail From Gmail
https://blog.stefandroid.com/2021/06/20/migrate-gmail-emails.html
The best way to get this imapsync up and running is to install it on Ubuntu. Trying to find all the required packages on Fedora is a test in patience unless you are a perl expert.
git clone https://github.com/imapsync/imapsync.git
https://tecadmin.net/use-imapsync-on-ubuntu/
sudo apt-get install git rcs make makepasswd cpanminus
sudo apt-get install gcc libssl-dev libauthen-ntlm-perl \
libclass-load-perl libcrypt-ssleay-perl liburi-perl \
libdata-uniqid-perl libdigest-hmac-perl libdist-checkconflicts-perl \
libfile-copy-recursive-perl libio-compress-perl libio-socket-inet6-perl \
libio-socket-ssl-perl libio-tee-perl libmail-imapclient-perl \
libmodule-scandeps-perl libnet-ssleay-perl libpar-packer-perl \
libreadonly-perl libsys-meminfo-perl libterm-readkey-perl \
libtest-fatal-perl libtest-mock-guard-perl libtest-pod-perl \
libtest-requires-perl libtest-simple-perl libunicode-string-perl
I ran into errors and also needed to install these.
sudo apt-get install libencode-imaputf7-perl libregexp-common-perl libfile-tail-perl
Using a passfile rather than directly using a password is not only more secure, but also is more likely to actually work. Any number of characters can throw off this script even when the password is contained in single or double quotes.
This mail transfer can take a long time to run. It is better to run the command on the Ubuntu VM directly or use nohup when running over an SSH connection.
./imapsync --host1 imap.gmail.com --user1 sales@domain1.com --passfile1 passfile1.txt' --ssl1 --host2 mail.domain1.com --user2 sales@domain1.com --passfile2 passfile2.txt --ssl2 --gmail1 --regextrans2 "s,\[Google Mail\].,," --folderfirst "INBOX" --exclude "All Mail" --exclude "Important" --exclude "Spam" --folderlast "Starred" --addheader
SSL Testing tool
DMARC Record is the next chapter
https://www.linuxbabe.com/redhat/opendmarc-postfix-centos-rhel