# Install iRedMail Mail Server on Proxmox behind HAProxy using OPNSense [TOC] ## Sources: - [Older LinuxBabe guide](https://www.linuxbabe.com/mail-server/debian-9-stretch-iredmail-email-server) for iRedMail installation guide - [iRedMail website](https://www.iredmail.org/download.html) - [Postfix SMTP Relay on Debian with Brevo](https://www.linuxbabe.com/mail-server/set-up-postfix-smtp-relay-debian-sendinblue) (from 2021) ## Introduction: Would you like to have your own mail server that serves emails for multiple domains? Are you struggling with MX Blacklists and would like to have your emails in your possession but still send emails out without them reaching spam boxes? Then let’s set up iRedMail with SMTP relay using Brevo (or another provider). With Brevo, you can send up to 300 emails daily for free, which is enough for most people who do not run a business. Let’s dive into the plethora of open source options that can take us there! ## Desired State - We want to have a running mail server that is integrated with an OPNSense firewall and terminates SSL for web services (RoundCube / iRedmail Admin) on the firewall using HAProxy. - Able to reach the mailboxes from anywhere via IMAP. - Send emails via a free SMTP relay to avoid getting on spam lists. ![Image download failed](assets/image_1_failed.png) RounCube running on HTTPS behind HAProxy ## Expected Gear / Skills - Proxmox (or you can run iRedMail on another Hypervisor or bare-bone) - OPNSense (whether virtualized or bare-bone does not matter for this set up) - the steps will work similarly with Pfsense as well. - At least 3 GB RAM available for iRedMail and all the software that it comes with - HAProxy (in this guide, we will use HAProxy as a plugin of OPNSense). - A free CloudFlare account with your domain name servers using CloudFlare. - Basic Linux skills (Debian) are recommended (all required steps are covered with screenshots) - Possibility to open ports 25 (SMTP in), 465/587 (SMTPS out), 993 (IMAP), 80/443 (HTTP/HTTPS) on your firewall. If you are using a hosted VPS, check with the provider that port 25 can be opened to avoid disappointment. - About 3 hours of your time to go through the entire guide (if things go according to plan) ## What you do NOT need - Static IP by your ISP or VPS hosting provider - Database skills (we will be using MariaDB SQL) - Previous experience of working with Postfix, Dovecot, iRedMail Admin, Nginx, HAProxy. ## Pointing your MX records (DNS): - In this case, I have the domain I want to use to receive email on CloudFlare. So I create two records: - An MX record that points to [mail.bachelor-tech.com](http://mail.bachelor-tech.com/) with a priority of 10 (the number does not matter, since we will have only one mail server). - An A that expands on where does the ‘mail’ record point to, which should be the public IP address.

1 what you do not need

- Note that the MX record should not be shielded by CloudFlare’s proxy service, as the other mail servers need to be able to resolve the public IP of the mail server to be able to reach it. ## Generic Proxmox VM Installation: - On Proxmox, create a new VM. Have a standard net-iso for Debian 12.x or newer ready. - The VM should have at least 3 GB of RAM as the number of services installed is a lot. Recommended at least 2x CPU and at least 40 GB of space (if not more). - Once the VM is set up, note that the MAC address of the network card, so that we can add it into static DHCP on OPNSense (or your existing firewall).

2 what you do not need

## Firewall - static DHCP assignment - On OPNSense, go to Services → ISC DHCPv4 → [LAN] (or whatever the interface is called for your local network). - Scroll down and click on the + sign to add a new host.

3 what you do not need

- Add the MAC address and IP address for your host, save it and apply the configuration.

4 what you do not need

## Firewall - Ports to open on your firewall (OPNSense): - On OPNSense, go to Firewall → Rules → WAN 1 (or whatever the name of your inteface is). - Here is a [**list of ports used by iRedMail services**](https://docs.iredmail.org/network.ports.html) - not all of them need to be opened. - Port 80 and 443 for HAProxy (unless you have it set up already) - Port 25: required for mail server exchange. - Port 993 for IMAP over SSL - We will not need to open ports for SMTP since we will be using the relay by default. This is safer in case the public IP changes to ensure that communication can be sent out. - Firewall rules - go to Firewall → Rules → WAN 1 (or just WAN) and click on the + sign to make sure you have the following set up - Rule 1: Port 80 - Action: Pass - Interface: WAN - Direction: in - TCP/IP: IPv4 - Protocol: TCP - Destination: This Firewall - Destination port range: from HTTP to HTTP

5 what you do not need

- Rule 2: Port 443 - Same as above with the difference of the port range starting from HTTPS to HTTPS (or choose ‘other’ and manually type 443 in both boxes). - Rule 3: Port 25 - Destination here is not ‘This firewall’ but ‘LAN net’ since we will be forwarding it as a NAT rule to our mail server later. - Port range starting from SMTP to SMTP (or choose ‘other’ and manually type 25 in box boxes).

6 what you do not need

- Rule 4: Port 993 (IMAPS) - As before, the Destination is not ‘This firewall’ but ‘LAN net’. - Port range starts from IMAPS to IMAPS (or 993 to 993)

7 what you do not need

- Rule 5: Port 465 (SMPTS) - As before,the Destination is not ‘This firewall’ but ‘LAN net’ - Port range starts from SMTPS to SMPTS (or 465 to 465). Alternatively, you can use port 587 if you manually define it (then you must be consistent even with the NAT rule and how you connect your email client).

8 what you do not need

- NAT rule for port 25 - On OPNSense, go to Firewall → NAT → Port Forward and create a new rule. - Interface: your WAN - TCP/IP: IPv4 - Protocol: TCP - Destination: WAN address - Destination port range: SMTP to SMTP (or select other and manually type in 25 to 25) - Redirect target IP: the IP of your mail server - Redirect target port: SMTP - Log it in for debugging purposes - Everything else leave as default or tweak as per your needs.

9 what you do not need

- NAT rule for port 993 (IMAPS) - On OPNSense, go to Firewall → NAT → Port Forward and create a new rule. - Interface: your WAN - TCP/IP: IPv4 - Protocol: TCP - Destination: WAN address - Destination port range: IMAPS to IMAPS (or select other and manually type in 993 to 993) - Redirect target IP: the IP of your mail server - Redirect target port: IMAPS - Log it in for debugging purposes - Everything else leave as default or tweak as per your needs.

10 what you do not need

- NAT Rule for port 465 (SMPTS) - As above, use port 465 or SMTPS

11 what you do not need

- Upon creation of the NAT rules, remember to click on the ‘Apply’ button in OPNSense to activate it. ## Dynamic DNS for our ‘mail’ DNS record on CloudFlare with OPNSense - In order to make sure that our mail server is reachable even if our ISP changes the public-facing IP address, you can leverage OPNSense’s DDNS service with CloudFlare. - In CloudFlare, create a token that can handle records for your domain:

12 what you do not need

- Confirm the creation of the token and save it somewhere safe. - In OPSense, go to Services → Dynamic DNS → Settings → and click on the + sign. - From the list, choose: - Service: CloudFlare - Username: the word ‘token’ - Password: the long token ID that you have received from CloudFlare - Zone: your domain’s name - Hostname: the A or CNAME record that you want to edit - Check ip method - use ‘freedns’

13 what you do not need

- Once done, click on the ‘Apply’ button and check the Log file under Settings to see that it completed successfully. You can even change the IP in CloudFlare to something else and wait 1-2 minutes to see that it changes in CloudFlare. Cool, hmm? ## SSL termination for web services - Whereas for postfix and dovecot (IMAP), we will use the OPNSense firewall and NAT rules to the mail server and terminate SSL there, we will terminate SSL on OPNSense using haproxy for the web services. - This is required if you run more than one website and want to use your public IP for those as well (rather than just running a TCP passthrough, which does not care about hostnames, since it is L4 vs L7). - In OPNSense, go to System → Status → Plugins and make sure that the os-acme-client is installed (if not, click on the + sign to install it).

14 what you do not need

- Then go to Services → ACME Client → Accounts. Create a new account with Let’s Encrypt. Fill in the name (ideally the name of the domain for easier tracking) and your email address. The email address should be something you actually use so that you can get warned in case renewal fails in the future.

15 what you do not need

- Then go to the ‘Challenges Type’ section menu item on the left (within ACME Client) and add a new challenge type. The challenge type should be DNS-01 using the [Cloudflare.com](http://cloudflare.com/) service. Leave some sleep time just in case. Then as your CF Account ID, copy over the ID from your CloudFlare account (after dash.cloudflare.com). - The API Token is the same as we created earlier for Dynamic DNS.

16 what you do not need

17 what you do not need

- Then lastly, go to the ‘Certificates’ section within the ‘ACME Client’ menu on the left and create a new certificate using the challenge type and the cert authority you defined earlier:

18 what you do not need

- Wait for a bit and check the ‘Log Files’ menu option on the left to see what the result was. If successfully created (or updated), it may look similar to the output below:

19 what you do not need

## HAProxy on OPNSense - SSL Termination: - Sources: - [Video guide from TheMaw Tech](https://www.youtube.com/watch?v=uACQrhtsgFk) - This guide covers set up on OPNSense. We will need to open traffic for nginx web server, so that we can reach the admin web interface + RoundCube, both installed on the iRedMail server. - If you do not have haproxy installed on your OPNSense instance, you can install it under System → Firmware → Packages - find os-haproxy and click on the + symbol to install it.

20 what you do not need

- Under Services → HaProxy → Settings, go to Real Servers → Real Servers and add a new host. In this guide, we will use port 8082 that will be forwarded internally on the LAN to the iRedMail VM.

21 what you do not need

- Make sure the ‘Enabled’ tick box is ticked. - Now go to Virtual Services → Backend pools and add a new host there:

22 what you do not need

- Let’s define a condition. Go to Rules & Checks → Conditions and create a new condition:

23 what you do not need

- Now we can set up a rule what to do if the condition is met. Go to Rules & Checks → Rules and create a new rule:

24 what you do not need

- In the rule, we say that if the condition is met, traffic should be forwarded to the backend host we have created. Yet there is no service listening in the front-end yet to handle the traffic. - Go to Virtual Services → Public Services and unless you have a service there already, add a new one that listens on port 80.

25 what you do not need

- Once done, click on the ‘Test syntax’ button and only click on ‘Apply’ if everything is okay. ## Services To Be Installed On Our Mail Server (OPTIONAL READ): - Source: [iRedmail documentation](https://docs.iredmail.org/file.locations.html#roundcube) - Please note that there are usually more config and log files available for each service, I only highlight the ones you may need during our set up. - **nginx **→ web server to display the admin interface (iRedAdmin) and webmail (RoundCube) - Config files: - /etc/nginx/nginx.conf → definition of the base user (e.g. www-data), max worker connections and additional resources to include. - /etc/nginx/sites-enabled/default.conf → default config for unencrypted communication, server and location directives - /etc/nginx/sites-enabled/default-ssl.conf → default config for encrypted comms, server and location directives - Log files: - /var/log/nginx/access.log → the default location for the access log. - /var/log/nginx/error.log → the default location for the error log. - **dovecot **→ SMTP functionality - Config files: - /etc/dovecot/dovecot-mysql.conf → used to query mail users and passwords for MySQL / MariaDB engine. - /etc/dovecot/dovecot-pgsql.conf → used to query mail users and passwords on PostgreSQL engine. - /etc/dovecotr/dovecot-used-quota.conf → store and query per-user email quota. - Log files: - /var/log/dovecot/dovecot.log → log of changes to the dovecot service - /var/log/dovecot/imap.log → IMAP connection logs - **postfix **→ for IMAP/LMTP/LDA - Config files: - /etc/postfix/main.cf → most config that we will be working with - /etc/postfix/master.cf → transport-related settings - /etc/postfix/mysql/*.cf → configs for MySQL / MariaDB - Log file: /var/log/mail.log - **iRedAPD** → a postfix policy server (default port 7777) - Config file: /opt/iredapd/settings.py - Log file: /var/log/iredapd/iredapd.log - **iRedAdmin **→ web application (PHP-based, hence why we use nginx with the php-fpm package) - Config file: /opt/www/iredadmin/settings.py - Log file: /var/log/uwsgi/app/iredadmin.log - **mariadb **→ database engine to store data in - Config files - /etc/mysql/my.cnf → modify basic parameters like port number - /etc/mysql/mariadb.conf.d/50-server.cnf → server-related config (inc. SSL certificates) - /etc/mysql/mariadb.conf.d/50-client.cnf → client-related config - Log file: by default, it is integrated with syslog (run `sudo cat /var/log/syslog | grep mysql`) - **mlmmj** → a simple and slim mailing list manager (MLM) inspired by ezmlm. It works with many different Mail Transport Agents (MTAs). - Config file: /opt/mlmmjadmin/settings.py - Log file: /var/log/mlmmjadmin/mlmmjadmin.log - **spamassassin** → spam filtering service - called by amavis, not executed as a daemon - Config file: /etc/mail/spamassassin/local.cf - Log file: /var/log/mail.log - **clamav **→ antivirus scanner - Config files - /etc/clamav/clamd.conf → basic behavior of the AV - /etc/clamav/freshclam.conf → AV database configuration - Log file: - /var/log/clamav/clamav.log → records of findings (must be activated via clamd.conf - remove the ‘#’ to uncomment a line starting with ‘#LogFile’. - /var/log/clamav/freshclam.log → AV database update log - **RoundCube **- webmail (optional but recommended to install) - Config file: /opt/www/roundcubemail/config/config.inc.php → here we can disable the requirement to run on HTTPS internally - Log file: postfix log is used by default - **php-8.2 inc.** the fpm package that is used for compatibility with nginx - **fail2ban **(optional) - auto-ban repeated failed login attempts - Config file: /etc/fail2ban/jail.local - Log file: /var/log/fail2ban.log - **nftables **(optional) → a new generation iptables firewall (can be replaced by ufw) - **netdata** → system application monitor - **amavis **(amavisd-new) → analysis and filtering capabilities, works with postfix and spamassasin, uses external content filters and changes ratings of senders. Helps with the generation of the DKIM record - Config file: /etc/amavis/conf.d/50-user - Log file: stored in postfix logs by default - **rsyslog** → the ‘rocket-fast’ system for log processing ## VM preparation on Proxmox - QEMU agent: - Install the QEMU agent (Proxmox) for better monitoring and soft shut downs (important esp. if you will be running a db on it as well). ```bash sudo apt install qemu-guest-agent sudo systemctl enable qemu-guest-agent ``` - Then on the VM, run ‘shutdown now’ and make sure the VM properly switches off. - On the Proxmox node, go to the VM:

26 what you do not need

- Start the VM again and run the following command to ensure that the agent is running. ```bash systemctl status qemu-guest-agent ``` - You should now be able to see more details about your VM in the Proxmox console for your VM under ‘Summary’ - If unsure, SSH into the Proxmox host and run: ```bash qm agent ping # No errors means it got through qm agent info # Prints out all kinds of details about the VM ``` ## iRedMail installation: - Update + set up the desired hostname ```bash sudo apt update && sudo apt upgrade sudo hostnamectl set-hostname mail.your-domain.com # Modify the hosts file like this: sudo nano /etc/hosts 127.0.0.1 mail.your-domain.com localhost # Check that it is correct: hostname -f ``` - Install iRedMail and related tools (check the latest version [here](https://www.iredmail.org/download.html)) ```bash # Download the package https://github.com/iredmail/iRedMail/archive/refs/tags/1.7.1.tar.gz # Unpack it and go in tar xvf 1.7.1.tar.gz cd iRedMail-1.7.1 # Make the bash file executable and run it (it will download more packages): chmod +x iRedMail.sh sudo bash iRedMail.sh ``` - A wizard will be launched, eventually. Click on ‘Yes’ to proceed.

27 what you do not need

- Next, you will be asked for where to store emails - the default location is /var/vmail (as /var/mail cannot be used) - you can confirm the default, unless preferred otherwise.

28 what you do not need

- Web server step - Nginx is the default (in the past, it was Apache) - this allows you to set up mailboxes as admin and will be used for RoundCube webmail, so it is definitely recommended.

29 what you do not need

- Database menu - for our use, MariaDB is more than enough.

30 what you do not need

- Database root password - be careful to type it correctly, as you do not get to see what you are typing and there is no re-type confirmation.

31 what you do not need

- First domain name - what is the first (and could be the only) domain that you will want to be receiving emails on? This can be different from the hostname’s domain.

32 what you do not need

- Postmaster’s email admin password - again, keep track of what you enter. It should be different from the DB root password.

33 what you do not need

- Additional components - Roundcube replaces SoGo in terms of webmail capabilities (SoGo still runs on Apache). Others are good to install.

34 what you do not need

- At the end of the wizard, confirm ‘y’ to proceed with the installation. - Once configured, you will be asked to confirm your SSH port for the firewall to ensure that you do not get locked out and then to restart the firewall:

35 what you do not need

- Before restarting, take a look at the tips file to confirm that you have got the passwords typed correctly and know the interfaces: ```bash nano iRedMail.tips ``` - Reboot your VM. ## Nginx configuration for iRedMail behind reverse proxy: - By default, nginx is configured to forward traffic from HTTP (80) to HTTPS (443). However, in our case, we terminate SSL on HAProxy and are operating on an unencrypted port 8082. So we need to tweak the config a bit to make it work. - Essentially, we can just comment out the redirect and copy over the content of the 00-default-ssl.conf file and merge it with what we have 00-default.conf. Your resulting 00-default.conf file should look as follows: ```bash sudo nano /etc/nginx/sites-available/00-default.conf # # Note: This file must be loaded before other virtual host config files, # # HTTPS server { listen 8082; listen [::]:8082; #root /var/www/html; #index index.php index.html; #server_name _; #Redirect all insecure http:// requests to https:// #return 301 https://$host$request_uri; server_name _; root /var/www/html; index index.php index.html; include /etc/nginx/templates/misc.tmpl; include /etc/nginx/templates/ssl.tmpl; include /etc/nginx/templates/iredadmin.tmpl; include /etc/nginx/templates/roundcube.tmpl; include /etc/nginx/templates/sogo.tmpl; include /etc/nginx/templates/netdata.tmpl; include /etc/nginx/templates/php-catchall.tmpl; include /etc/nginx/templates/stub_status.tmpl; } ``` - Verify the config and reload nginx: ```bash # Verify the nginx config: sudo nginx -t # Restart the nginx config sudo systemctl reload nginx ``` - Since we changed the default port on nginx, we need to update nftable’s config (firewall) ```bash # Check the status of nftables: sudo systemctl status nftables.service # Show existing rules (port 80 will likely be there) sudo nft list ruleset # Change port 80 to 8082 (or whatever you chose on OPNSense under Real Servers sudo nano /etc/nftables.conf # Reload nftables: sudo systemctl reload nftables.service ``` - To get RounCube work unencrypted, go to the following path and change the force HTTP directive to ‘false’. No services need to be restarted after that. ```bash sudo nano /opt/www/roundcubemail/config/config.inc.php # Change the following directive from 'true' to 'false: $config['force_https'] = false; ``` - Now you can finally try all three management interfaces: ```bash # iRedAdmin: https://your-domain.tld/iredadmin/ # Roundcube webmail: https://your-domain.tld/mail/ # Monitoring https://your-domain.tld/netdata ``` - If you encounter difficulties, you can review the following logs - HAProxy logs (e.g. on OPNSense) → is the traffic being passed to the right back-end? - HAProxy → Is SSL offloading box ticked in the Virtual Services → Public Service? - nginx logs → check `sudo tail /var/log/nginx/access.log` and `error.log`. - Confirm that nginx is listening on the desired ports → `ss -tln` to confirm. - Have you re-configured and reloaded the firewall? Run `sudo nft list ruleset`. ## Access iRedAdmin - Now iRedAdmin should become reachable on [https://mail.your-domain/iredadmin/](https://mail.bachelor-tech.com/iredadmin/)

36 what you do not need

- You can log in with your [postmaster@your-domain.](mailto:postmaster@your-domain.com)tld login and password. - Find a list of users for your domain:

37 what you do not need

- Add a new user:

38 what you do not need

## Configure Roundcube - Just to repeat from earlier, with HAProxy in place to decrypt incoming SSL communication, in order to access RoundCube on what seems from the mail server’s point of view as HTTP, you need to disable the `force_https` directive in the Roundcube config file: ```bash sudo nano /opt/www/roundcubemail/config/config.inc.php # Find this part under //SYSTEM $config['force_https'] = false; ``` - Access the web interface on [https://mail.your-domain.tld/mail/](https://mail.bachelor-tech.com/mail/)

39 what you do not need

- Try logging in with the details you created earlier in the iRedAdmin interface. - You can observe your login (or a failure to login) by looking at the end of this file: ```bash sudo tail -n 3 /var/log/mail.log ``` - At this point, you can try sending an email to your new mailbox from outside - providing that port 25 on your firewall is unblocked and points to the IP address of your mail server on the LAN.

40 what you do not need

- If the email is not arriving into your mailbox and there is nothing in the `/var/log/mail.log` file, it is likely being blocked by your firewall. - If you use OPNSense, you can check it in the section called Firewall → Log files → Live view. - Expand the view to at least a 100 items and watch it as you send an email to your mailbox. - Add two filters - one that is ‘Action - is - block’ (click on the + sign) and then one that says dst_port (destination port) - is - 25 (again click on the + sign). - If you see repeated attempts to reach port 25 then you know that your firewall rule is not set up correctly.

41 what you do not need

- If the email arrives correctly, you will be able to access it in RoundCube:

42 what you do not need

- You can review the test mail’s header to confirm that amavis service on our mail server provided virus scanning (together with SpamAssassin):

43 what you do not need

- This does not yet mean that you can send emails out, as your mail server and domain does not have its own DKIM, SPF and DMARC DNS records set up, nor have we set up an SMTP relay yet. ## CertBot Certificate Creation - Up to this point, we did not need to handle SSL on the mail server. However, in order to securely handle mail exchange, we will need to provide a cert. - If you would like to check all the services running on your mail server up to this point, run `sudo service --status-all` to ponder on your achievements so far. - Sources: - [Certbot documentation on CloudFlare integration](https://certbot-dns-cloudflare.readthedocs.io/en/stable/#) - [Python local library for the CloudFlare plugin](https://mangolassi.it/topic/18355/setup-letsencrypt-certbot-with-cloudflare-dns-authentication-ubuntu) - [Certbot manual](https://eff-certbot.readthedocs.io/en/stable/using.html) - [PEP 517 parameter](https://peps.python.org/pep-0517/) - [Certbot via virtual environment in Python](https://certbot.eff.org/instructions?ws=other&os=pip) - There are two ways of how to install and configure certbot - either by using your distributions default package manager, which means always being on an older version that may not support the newest features but is easier to maintain, OR by using a virtual python environment where you can stay away from the default package manager (i.e. APT). - In our case, since we plan to use the CloudFlare plugin using API token (that we used earlier for OPNSense), as of the time of writing the article (10-2024), the package distribution version only supports the global API key, which is less safe. For the purpose of this article, both ways are provided: - **OPTION 1**: Use API token (recommended) ```bash # WITH TOKEN sudo apt install python3-pip sudo apt install python3-venv # Create a new virtual environment for python3 sudo python3 -m venv /opt/certbot/ # Upgrade pip to the newest version sudo /opt/certbot/bin/pip install --upgrade pip # Install certbot in the virtual environment (to get the most recent version versus APT) sudo /opt/certbot/bin/pip install certbot certbot-dns-cloudflare # Make the certbot command reachable globally sudo ln -s /opt/certbot/bin/certbot /usr/bin/certbot # Create an .ini file and protect it to store the API key: sudo mkdir /root/.secrets sudo chmod 0700 /root/.secrets/ sudo touch /root/.secrets/cloudflare.ini sudo chmod 0400 /root/.secrets/cloudflare.ini # Open the file and append this line - replace XYZ with your actual API key: sudo nano /root/.secrets/cloudflare.ini dns_cloudflare_api_token = "XYZ" # Automate updates to pip + certbot + certbot-dns-cloudflare via crontab sudo nano crontab -e # Append the following two lines: # Update pip + certbot + CloudFlare plugin monthly @monthly sudo /opt/certbot/bin/pip install --upgrade pip certbot certbot-dns-cloudflare >/dev/null 2>&1 ``` - **OPTION 2:** Use Global API key: ```bash # WITH GLOBAL API KEY # Install certbot sudo apt install -y certbot python3-certbot-dns-cloudflare sudo mkdir /root/.secrets sudo chmod 0700 /root/.secrets/ sudo touch /root/.secrets/cloudflare.ini sudo chmod 0400 /root/.secrets/cloudflare.ini sudo nano /root/.secrets/cloudflare.ini dns_cloudflare_email = "your-cloudflare-email-address" dns_cloudflare_api_key = "your-global-api-key-not-token" # Create a cert via the CloudFlare DNS route sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials /root/.secrets/cloudflare.ini -d mail.your-domain.tld --preferred-challenges dns-01 ``` - Review your certificates - you will see that those are symlinks that lead to the archive directory. ```bash sudo ls -lah /etc/letsencrypt/live/mail.your-domain.tld/ ``` - There are four .pem files: - cert.pem → the public part of the key - chain.pem → an additional intermediary certificate in order to validate the server certificate - fullchain.pem → all certs inc. the server certificate - privkey.pem → the private part of the key that is only known to your server - Automate the renewal by the following: ```bash # Test a renewal sudo certbot renew -w /var/www/html/ --dry-run # Open crontab sudo crontab -e # Append the following two lines: # Certbot: Check if renewal is needed weekly and reload affected services @weekly certbot renew -w /var/www/html/ --quiet && systemctl reload dovecot postfix nginx ``` ## Apply the certificates - Nginx - While for nginx, we do not need to apply the cert, since we terminate SSL at HAProxy, it may still be a good practice in case we implement SSL termination at the mail server in the future, instead. - Edit the following ssl template file: ```bash sudo nano /etc/nginx/templates/ssl.tmpl # Find the following: ssl_certificate /etc/ssl/certs/iRedMail.crt; ssl_certificate_key /etc/ssl/private/iRedMail.key; # Replace them with: ssl_certificate /etc/letsencrypt/live/mail.your-domain.tld/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/mail.your-domain.tld/privkey.pem; ``` - Run a syntax check and reload nginx: ```bash sudo nginx -t sudo systemctl reload nginx ``` ## Apply Certificates - Dovecot (IMAP) - Since we plan to connect to the mail server directly to sync emails with an email client via IMAPS, we will need to set the certificates on Dovecot to secure the communication over SSL port 993 (instead of unencrypted 143). POP3 is rarely used anymore (although port 995 would do the job for that). ```bash sudo nano /etc/dovecot/dovecot.conf # Find these two lines ssl_cert = Understanding general spoofing protection types: - ***SPF*** → a TXT DNS record that identifies which IP addresses are authorized (trusted) to send emails out from your domain (i.e. from your mail server). The receiving email client (to which you are delivering email from your mail server) can then verify that is coming from the authorized origin. The process has three steps: - Step 1 - policy establishment (as per our TXT DNS record) - which hosts can send emails out - Step 2 - DNS server lookup - the receiving mail server verifies that the IP matches the DNS name - Step 3- Response - the receiving mail server either accepts, flags or rejects the email - Tags (some of them): - → spf version - → authorized incoming mail servers for the domain - → other systems authorized to send on behalf of the domain. - → all other trusted external domains that can send on behalf of our domain (such as Mail Chimp, Brevo). - → catch-all mechanism for anything not caught in the other rules (typically all else is not authorized) - Example simple record: "`v=spf1 mx ~all`” → SPF version 1, allow all mail servers under the MX record and for all others, mark them as suspicious. - ***DKIM*** → Domain Keys Identified Mail is a digital certificate supplied with an outgoing email from your mail server. It allows the receiving client to verify that the header or body of the email was not modified during transmission. The receiving mail server fetches the public part of the key from the DNS record. - Tags (some of them): - → version -

→ public key - → time stamp - → cryptographic algorithm used for creating the signature - → selector tag for finding the public key in the DNS record - → accompanies the selector tag by specifying the (sub)domain name used by the sender (our mail server) - Example record: `subdomain_domainkey TXT 3600 "v=DKIM1; a=rsa; p=MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDU2+0C4M94mNWIk..."` - ***DMARC*** → leverages both SPF + DKIM alignment tests but specifies its own policy (set of instructions to the receiving mail server where we are sending an email to on how to proceed). Policy is either ‘none’ (do nothing), ‘quarantine’ (keep for a while, evaluate) and ‘reject’ (do not deliver). - The receiving mail server will then return a report of all the emails that were not delivered (as feedback). It includes a number of tags: - → % of failed policy should comply with (e.g. only certain % to quarantine) - value 1 to 100 - → specific policy for emails from subdomains (e.g. p = none means no subdomains) - → level of strictness DMARC should use for DKIM. Default is ‘relaxed’ (only SPF or DKIM needs to pass), ‘strict’ is another option - → same as adkim but for the SPF requirements - strict/relaxed options (e.g. are emails from subdomain allowed or do emails have to match the ‘from’ envelope from the domain with the ‘return-to’ address? - → how often to receive aggregate reports (in seconds - default is 86400 = every 24 hours). - → settings for forensics report. Value 0 = send a report if all checks fail (DKIM + SPF), 1 = if one of them fails, D = if DKIM fails, S = if SPF fails. Default is 0. - → aggregated reports; sent daily by default with no personal identifiable information (PII), supplied in XML - → forensic reports; individual report sent instantly after a failure to meet policy, it contains PII information, the format is in plain text - Example: "`v=DMARC1; p=quarantine; pct=100; `[`rua=mailto:postmaster@mydomain.com`](mailto:rua%3Dmailto:postmaster@writekayan.com)" → version 1, if policy is not met then quarantine, screen 100% of emails, send reports of emailing failing the policy to this address. - **Postfix’s Configuration Security Hardening Directives** - Typically available in `/etc/postfix/main.cf` - **smtpd_sender_restrictions** → restrictions in the context of the MAIL FROM information, i.e. from the envelope email address supplied by the sender (e.g. somemailbox@domain.com). Note that this is not the FROM directive in the header of the email as that is part of the DATA stream - see above. | **Postfix’s Directive** | **Explanation** | | --- | --- | | reject_sender_login_mismatch | An alias to two parameters if SASL auth is passed but the MAIL FROM does not match. | | reject_non_fqdn_sender | A numerical response to rejecting the request when the RCPT TO address specifies a domain that is not in fully-qualified domain form. | | reject_unlisted_sender | Rejects mail from unknown sender addresses, even when no explicit reject_unlisted_sender access restriction is specified (default is 'no', this sets it to 'yes'). | | permit_mynetworks | Lookup the variable $mynetworks and treat the IP addresses of those clients as trusted. | | permit_sasl_authenticated | Respond with a pass to a client that is successfully authenticated via the RFC 4954 (AUTH) protocol. | | reject_unauth_destination | Proceed only if the RCPT TO domain matches what we have defined in $relay_domains (if forwarding) or $mydestination (if delivering). | | reject_invalid_helo_hostname | Reject if EHLO is malformed (may need to add 'smtpd_helo_required = yes' as it only works if HELO/EHLO are sent). | | reject_unknown_sender_domain | Reject if Postfix is not the final destination for the sender and the MAIL FROM domain has either no DNS MX record, no DNS A record, or has a malformed MX record. | | reject_unauth_pipelining | Reject the request if the email client sent SMTP commands before asked (such as piping EHLO commands or ESMTP commands before asked). | - **smtpd_client_restrictions** → restrictions in the context of the connecting HOST (e.g. mail.domain.com) - to set boundaries related to the client connection request. | **Postfix’s Directive** | **Explanation** | | --- | --- | | permit_mynetworks | Lookup the variable $mynetworks and treat the IP addresses of those clients as trusted. | | permit_sasl_authenticated | Respond with a pass to a client that is successfully authenticated via the RFC 4954 (AUTH) protocol. | | reject_unauth_destination | Proceed only if the RCPT TO domain matches what we have defined in $relay_domains (if forwarding) or $mydestination (if delivering). | - Do you find it hard to believe that these restrictions are important? Just review your mail.log after a few days of it being online: ```bash sudo grep -E '(reject|warning|error|fatal|panic):' /var/log/mail.log ``` ## Securing Your Mail Server (Postfix) - Sources: - [AnandraJPandey’s blog](https://anandarajpandey.com/2019/12/30/postfix-how-to-prevent-open-relay/) - [Postfix manual](https://www.postfix.org/postconf.5.html#check_client_access) - [StackExchange](https://serverfault.com/questions/878882/postfix-smtpd-relay-restrictions-how-to-allow-relay-from-third-party-smtp-ser) - [Old Postfix manual](https://mailing.postfix.users.narkive.com/0x74XPMe/smtpd-client-vs-smtpd-sender) - [Postfix documentation on access lists](http://www.postfix.org/SMTPD_ACCESS_README.html#lists) - [MailTrap](https://mailtrap.io/blog/smtp-security/) - The next item on the list related to postfix is to ensure that unauthorized senders outside of our domain cannot use our SMTP as an open relay. If you were to start receiving undeliverable emails related to mails you never sent out, it is most likely due to that. ```bash sudo nano /etc/postfix/main.cf # Find this directive and match it to the following: smtpd_sender_restrictions = reject_sender_login_mismatch, reject_non_fqdn_sender, reject_unlisted_sender, permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, reject_invalid_helo_hostname, reject_unknown_sender_domain, reject_unauth_pipelining # Add a new directive that verifies the client (i.e. the connecting machine) as well smtpd_client_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination # Exit nano and perform a check + reload the config sudo postfix check sudo systemctl reload postfix ``` ## SMTP Relay with Brevo - While we could set up DKIM, SPF and DMARC DNS records for our mail server / domain, the likelihood is that your public IP is on one of the MX blacklists and you will struggle getting unlisted. What is more, if you are running your mail server on a ISP-provided non-static IP address, the address may change over time and you would face issues again. For this reason, it is best to use an SMTP relay. - The advantage of using Brevo (formerly Sendinblue) is that they allow for 300 emails a day for free, an offering that is unmatched by other providers (like MailTrap, MailChimp, etc.). - Once you create a free account on [Brevo.com](http://brevo.com/): - Go to to Senders, [Domains & Dedicated IPs](https://app.brevo.com/senders/domain/list) page. - Click on the ‘Add domain’ button and add your domain.

44 what you do not need

- If you use CloudFlare or a similar provider that Brevo supports, you can have Brevo add the required DNS records for you. Once done, you may have to wait a few hours for DNS to propagate.

45 what you do not need

- To set up your SMTP relay login with Brevo: - Proceed to the [SMTP & API keys section](https://app.brevo.com/settings/keys/smtp). - Click on the button to ‘Generate SMTP Login and Master password’ (unless created already).

46 what you do not need

- Go to the ‘[Senders](https://app.brevo.com/senders/list)’ section and add an authorized sender from your domain:

47 what you do not need

- Configure SMTP transactional relay with Brevo - Go to Transactional → Email → Real time ([here](https://app-smtp.brevo.com/real-time)) - Find the notice at the bottom in orange - click on the ‘Complete my profile’ button and provide the required details.

48 what you do not need

- Once you're done, proceed to the verification step so that Brevo is awaiting some traffic. Leave the window open.

49 what you do not need

- Head to your Postfix’s main configuration file and scroll to the button to add the following: ```bash sudo nano /etc/postfix/main.cf # Brevo set up as SMTP relayhost + configuration # Host and port numbers: relayhost = [smtp-relay.brevo.com]:587 # Enforce SASL authentication smtp_sasl_auth_enable = yes # Location of the username and password smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd # Forbid anonymous use smtp_sasl_security_options = noanonymous # Opportunistic TLS - ask if available smtp_tls_security_level = may # Max header size limit (1 MB) header_size_limit = 1048576 # Max email size limit (19 MB). Brevo's limit is 20 MB message_size_limit = 19922944 ``` - Check that your config is visible: ```bash sudo postconf header_size_limit sudo postconf message_size_limit ``` - In case you get a warning message that one of these parameters are being overridden, it means that the [main.cf](http://main.cf/) file contains this directive already - please remove the duplicates. - Define the credentials: ```bash sudo nano /etc/postfix/sasl_passwd [smtp-relay.brevo.com]:587 your_username@smtp-brevo.com:your_password ``` - Create the SASL database (for new mail servers) and change ownership to limit access: ```bash sudo postmap sasl_passwd sudo chmod 0600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db ``` - Run last check in case something is wrong: ```bash sudo postfix check ``` - Restart postfix: ```bash sudo systemctl restart postfix ``` - Now log into Roundcube and attempt to send out an email

50 what you do not need

- After sending it, check the log by running `sudo vi /var/log/mail.log` (press SHIFT + G to go to the end; then to exit the vim text editor, type : followed by q! and press enter to confirm the exit). - You can also check the Logs and Statistics section in Brevo:

51 what you do not need

52 what you do not need

- Other useful commands: ```bash # To rebuild the index file if you make changes to the sasl_passwd file: sudo postmap /etc/postfix/sasl_passwd # Flush an existing email queue sudo systemctl restart postfix # Remember to restart postfix after changes to the config files sudo systemctl restart postfix ``` ## Enable logging for ClamAV - Open the ClamAV’s config file and uncomment the LogFile. - Set a max size of your log file. ```bash sudo nano /etc/clamav/clamd.conf #LogFile /var/log/clamav/clamav.log --> uncomment this line by removing the # sign LogFileMaxSize = 20M # Reload ClamAV's daemon sudo systemctl reload clamav-daemon ``` ## Test Your Outgoing Email Ranking - You can use [mail-tester.com](http://mail-tester.com/) for up to three tests in 24 hours. - Once you reach the website, you will be asked to send an email to a unique email address / hostname. - Save the email address and create a new email, for example via Roundcube

53 what you do not need

- Then click on the ‘Check your score’ button to see what you get:

54 what you do not need

- Hopefully the boat has successfully travelled to new lands and the score is close to 10. With the free version of Brevo, an invisible tracking pixel is inserted by the relay and for some reason, it does not contain an alt tag, which lowers the score:

55 what you do not need

- The issue of the tracker is [discussed a lot on their forum](https://community.brevo.com/t/no-way-to-disable-by-option-tracking-in-transactional-e-mail/201/27) since late 2023. It seems to be present even when [anonymous tracking is disabled](https://app-smtp.brevo.com/tracking):

56 what you do not need

- While in my tests, emails to all major providers inc. Google and O365 were delivered fine, if you encounter issues, reach out to Brevo’s support to manually remove the tracking from all transactional emails. - An alternative is to sign up for another provider. For instance, [Mailer Lite](https://www.mailerlite.com/pricing-mailersend#compare) provides 3,000 emails per month for free. ## Connect Your Device To The Mail Server Via Email Client - While the choice of an email client is beyond the scope of our article, we will at least verify that it works using a BetterBird (Windows / Mac OS / Linux). - Download & install [Betterbird](https://www.betterbird.eu/downloads/index.php) (an improved Thunderbird fork) - or use any other email client of your choice.

57 what you do not need

- Then enter your login details to your mailbox and either click on the ‘Continue’ button if you are feeling lucky or tick the ‘Configure manually’ button. Usually, the email client will be able to detect the port numbers and the host name but will not work out the ‘Username’ field - make sure it is the full mailboxname@your-domain.tld, as shown below:

58 what you do not need

- Try sending an email out and then replying to it. If one of these two things do not work out, verify the following: - Firewall and NAT rules - Check the mail log in `/var/log/mail.log` - Make sure dovecot and postfix are running on your mail server