**Purpose**:
Self-Hosted Open-Source email server that can be setup in minutes, and is enterprise-grade if upgraded with an iRedAdmin-Pro license.

!!! note "Assumptions"
    It is assumed you are running at least Rocky Linux 9.3.  While you can use CentOS Stream, Alma, Debian, Ubuntu, FreeBSD, and OpenBSD, the more enterprise-level sections of my homelab are built on Rocky Linux.

!!! warning "iRedMail / iRedAdmin-Pro Version Mismatching"
    This document assumes you are deploying iRedMail 1.6.8, which at the time of writing, coincided with iRedAdmin-Pro 5.5.  If you are not careful, you may end up with mismatched versions down the road as iRedMail keeps getting updates.  Due to how you have to pay for a license in order to get access to the original iRedAdmin-Pro-SQL repository data, if a newer version of iRedAdmin-Pro comes out after February 2025, this document may not account for that, leaving you on an older version of the software.  This is unavoidable if you want to avoid paying $500/year for licensing this software.

## Overview
The instructions below are specific to my homelab environment, but can be easily ported depending on your needs.  This guide also assumes you want to operate a PostgreSQL-based iRedMail installation.  You can follow along with the official documentation on [Installation](https://docs.iredmail.org/install.iredmail.on.rhel.html) as well as [DNS Record Configuration](https://docs.iredmail.org/setup.dns.html) if you want more detailed explanations throughout the installation process.

## Configure FQDN
Ensure the FQDN of the server is correctly set in `/etc/hostname`.  The `/etc/hosts` file will be automatically injected using the FQDN from `/etc/hostname` in a script further down, don't worry about editing it.

## Disable SELinux
iRedMail doesn't work with SELinux, so please disable it by setting below value in its config file /etc/selinux/config. After server reboot, SELinux will be completely disabled.
``` sh
# Elevate to Root User
sudo su

# Disable SELinux
sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config # (1)
setenforce 0
```

1.  If you prefer to let SELinux prints warnings instead of enforcing, you can set this value instead: `SELINUX=permissive`

## iRedMail Installation

### Set Domain and iRedMail Version
Start by connecting to the server / VM via SSH, then set silent deployment variables below.
``` sh
# Define some deployment variables.  
VERSION="1.6.8" # (1)
MAIL_DOMAIN="bunny-lab.io" # (2)
```

1.  This is the version of iRedMail you are deploying.  You can find the newest version on the [iRedMail Download Page](https://www.iredmail.org/download.html).
2.  This is the domain suffix that appears after mailbox names.  e.g. `first.last@bunny-lab.io` would use a domain value of `bunny-lab.io`.

You will then proceed to bootstrap a silent unattended installation of iRedMail.  (I've automated as much as I can to make this as turn-key as possible).  Just copy/paste this whole thing into your terminal and hit ENTER.

!!! danger "Storage Space Requirements"
    You absolutely need to ensure that `/var/vmail` has a lot of space.  At least 16GB.  This is where all of your emails / mailboxes / a lot of settings will be.  If possible, create a second physical/virtual disk specifically for the `/var` partition, or specifically for `/var/vmail` at minimum, so you can expand it over time if necessary.  LVM-based provisioning is recommended but not required.

### Install iRedMail
``` sh
# Automatically configure the /etc/hosts file to point to the server listed in "/etc/hostname".
sudo sed -i "1i 127.0.0.1 $(cat /etc/hostname) $(cut -d '.' -f 1 /etc/hostname) localhost localhost.localdomain localhost4 localhost4.localdomain4" /etc/hosts

# Check for Updates in the Package Manager
yum update -y

# Install Extra Packages for Enterprise Linux
dnf -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm

# Download the iRedMail binaries and extract them
cd /root
curl https://codeload.github.com/iredmail/iRedMail/tar.gz/refs/tags/$VERSION -o iRedMail-$VERSION.tar.gz
tar zxf iRedMail-$VERSION.tar.gz

# Create the unattend config file for silent deployment.  This will automatically generate random 32-character passwords for all of the databases.
(echo "export STORAGE_BASE_DIR='/var/vmail'"; echo "export WEB_SERVER='NGINX'"; echo "export BACKEND_ORIG='PGSQL'"; echo "export BACKEND='PGSQL'"; for var in VMAIL_DB_BIND_PASSWD VMAIL_DB_ADMIN_PASSWD MLMMJADMIN_API_AUTH_TOKEN NETDATA_DB_PASSWD AMAVISD_DB_PASSWD IREDADMIN_DB_PASSWD RCM_DB_PASSWD SOGO_DB_PASSWD SOGO_SIEVE_MASTER_PASSWD IREDAPD_DB_PASSWD FAIL2BAN_DB_PASSWD PGSQL_ROOT_PASSWD DOMAIN_ADMIN_PASSWD_PLAIN; do echo "export $var='$(openssl rand -base64 48 | tr -d '+/=' | head -c 32)'"; done; echo "export FIRST_DOMAIN='$MAIL_DOMAIN'"; echo "export USE_IREDADMIN='YES'"; echo "export USE_SOGO='YES'"; echo "export USE_NETDATA='YES'"; echo "export USE_FAIL2BAN='YES'"; echo "#EOF") > /root/iRedMail-$VERSION/config

# Make Config Read-Only
chmod 400 /root/iRedMail-$VERSION/config

# Set Environment Variables for Silent Deployment
cd /root/iRedMail-$VERSION

# Deploy iRedMail via the Install Script
AUTO_USE_EXISTING_CONFIG_FILE=y \
    AUTO_INSTALL_WITHOUT_CONFIRM=y \
    AUTO_CLEANUP_REMOVE_SENDMAIL=y \
    AUTO_CLEANUP_REPLACE_FIREWALL_RULES=y \
    AUTO_CLEANUP_RESTART_FIREWALL=n \
    AUTO_CLEANUP_REPLACE_MYSQL_CONFIG=y \
    bash iRedMail.sh
```

When the installation is completed, take note of any output it gives you for future reference.  Then reboot the server to finalize the server installation.
```
reboot
```

!!! warning "Automatically-Generated Postmaster Password"
    When you deploy iRedMail, it will give you a username and password for the postmaster account.  If you accidentally forget to document this, you can log back into the server via SSH and see the credentials at `/root/iRedMail-$VERSION/iRedMail.tips`.  This file is critical and contains passwords and DNS information such as DKIM record information as well.

## Networking Configuration

### Nested Reverse Proxy Configuration
In my homelab environment, I run Traefik reverse proxy in front of everything, which includes the NGINX reverse proxy that iRedMail creates.  In my scenario, I have to make some custom adjustments to the reverse proxy dynamic configuration data to ensure it will step aside and let the NGINX reverse proxy inside of iRedMail handle everything, including handling its own SSL termination with Let's Encrypt.

``` sh
tcp:
  routers:
    mail-tcp-router:
      rule: "HostSNI(`mail.bunny-lab.io`)"
      entryPoints: ["websecure"]
      service: mail-nginx-service
      tls:
        passthrough: true

  services:
    mail-nginx-service:
      loadBalancer:
        servers:
          - address: "192.168.3.13:443"
```

### Let's Encrypt ACME Certbot
At this point, we want to set up automatic Let's Encrypt SSL termination inside of iRedMail so we don't have to manually touch this in the future.

#### Generate SSL Certificate
=== "Debian/Ubuntu"

    ``` sh
    # Download the Certbot
    sudo apt update
    sudo apt install -y certbot
    sudo certbot certonly --webroot -w /var/www/html -d mail.bunny-lab.io
   
    # Set up Symbolic Links (Where iRedMail Expects Them)
    sudo mv /etc/ssl/certs/iRedMail.crt{,.bak}
    sudo mv /etc/ssl/private/iRedMail.key{,.bak}
    sudo ln -s /etc/letsencrypt/live/mail.bunny-lab.io/fullchain.pem /etc/ssl/certs/iRedMail.crt
    sudo ln -s /etc/letsencrypt/live/mail.bunny-lab.io/privkey.pem /etc/ssl/private/iRedMail.key

    # Restart iRedMail Services
    sudo systemctl restart postfix dovecot nginx
    ```
        
=== "CentOS/Rocky/AlmaLinux"

    ``` sh
    # Download the Certbot
    sudo yum install -y epel-release
    sudo yum install -y certbot
    sudo certbot certonly --webroot -w /var/www/html -d mail.bunny-lab.io

    # Set up Symbolic Links (Where iRedMail Expects Them)
    sudo mv /etc/pki/tls/certs/iRedMail.crt{,.bak}
    sudo mv /etc/pki/tls/private/iRedMail.key{,.bak}
    sudo ln -s /etc/letsencrypt/live/mail.bunny-lab.io/fullchain.pem /etc/pki/tls/certs/iRedMail.crt
    sudo ln -s /etc/letsencrypt/live/mail.bunny-lab.io/privkey.pem /etc/pki/tls/private/iRedMail.key

    # Restart iRedMail Services
    sudo systemctl restart postfix dovecot nginx
    ```

#### Configure Automatic Renewal
To automate the renewal process, set up a cron job that runs the certbot renew command regularly. This command will renew certificates that are due to expire within 30 days.

Open the crontab editor with the following command:
```
sudo crontab -e
```

Add the following line to run the renewal process daily at 3:01 AM:
```
1 3 * * * certbot renew --post-hook 'systemctl restart postfix dovecot nginx'
```

### DNS Records
Now you need to set up DNS records in Cloudflare (or the DNS Registrar you have configured) so that the mail server can be found and validated.

| **Type** | **Name** | **Content** | **Proxy Status** | **TTL** |
| :--- | :--- | :--- | :--- | :--- |
| MX | bunny-lab.io | mail.bunny-lab.io | DNS Only | Auto |
| TXT | bunny-lab.io | "v=spf1 include:mail.bunny-lab.io ~all" | DNS Only | Auto |
| TXT | dkim._domainkey | v=DKIM1; p=`IREDMAIL-DKIM-VALUE` | DNS Only | 1 Hour |
| TXT | _dmarc | "v=DMARC1; p=reject; pct=100; rua=mailto:postmaster@bunny-lab.io; ruf=mailto:postmaster@bunny-lab.io" | DNS Only | Auto |

### Port Forwarding
Lastly, we need to set up port forwarding to open the ports necessary for the server to send and receive email.

| **Protocol** | **Port** | **Destination Server** | **Description** |
| :--- | :--- | :--- | :--- |
| TCP | 995 | 192.168.3.13 | POP3 service: port 110 over STARTTLS |
| TCP | 993 | 192.168.3.13 | IMAP service: port 143 over STARTTLS |
| TCP | 587 | 192.168.3.13 | SMTP service: port 587 over STARTTLS |
| TCP | 25 | 192.168.3.13 | SMTP (Email Server-to-Server Communication) |

## Install iRedAdmin-Pro
When it comes to adding extra features, start by copying the data from this [Bunny Lab repository](https://git.bunny-lab.io/bunny-lab/iRedAdmin-Pro-SQL) to the following folder by running these commands first:

``` sh
# Stop the iRedMail Services
sudo systemctl stop postfix dovecot nginx

# Grant Temporary Access to the iRedAdmin Files and Folders
sudo chown nicole:nicole -R /opt/www/iRedAdmin-2.5

# Copy the data from the repository mentioned above into this folder, merging identical folders and files.  Feel free to use your preferred file transfer tool tool / method (e.g. MobaXTerm / WinSCP).

# Change permissions back to normal
sudo chown iredadmin:iredadmin -R /opt/www/iRedAdmin-2.5

# Reboot the Server
sudo reboot
```

### Activate iRedAdmin-Pro
At this point, if you want to use iRedAdmin-Pro, you either have a valid license key, or you adjust the python function responsible for checking license keys to bypass the check, effectively forcing iRedAdmin to be activated.  In this instance, we will be forcing activation by adjusting this function, seen below.

There is someone else who outlined all of these changes, and additional (aesthetic) ones, like removing the renew license button from the license page, but the core functionality is seen below.  If you want to see the original repository this was inspired from, it can be found [Here](https://github.com/marcus-alicia/iRedAdmin-Pro-SQL)

``` sh
# Take permission of the python script
sudo chown nicole:nicole /opt/www/iRedAdmin-2.5/libs/sysinfo.py
```

=== "Original Activation Function"

    ```jsx title="/opt/www/iRedAdmin-2.5/libs/sysinfo.py"
    def get_license_info():
    if len(__id__) != 32:
        web.conn_iredadmin.delete("updatelog")
        session.kill()
        raise web.seeother("/login?msg=INVALID_PRODUCT_ID")

    params = {
        "v": __version__,
        "f": __id__,
        "lang": settings.default_language,
        "host": get_hostname(),
        "backend": settings.backend,
        "webmaster": settings.webmaster,
        "mac": ",".join(get_all_mac_addresses()),
    }

    url = "https://lic.iredmail.org/check_version/licenseinfo/" + __id__ + ".json"
    url += "?" + urllib.parse.urlencode(params)

    try:
        urlopen = __get_proxied_urlopen()
        _json = urlopen(url).read()
        lic_info = json.loads(_json)
        lic_info["id"] = __id__
        return True, lic_info
    except Exception as e:
        return False, web.urlquote(e)
    ```
        
=== "Bypassed Activation Function"

    ```jsx title="/opt/www/iRedAdmin-2.5/libs/sysinfo.py"
    def get_license_info():
    return True, {
        "status": "active",
        "product": "iRedAdmin-Pro-SQL",
        "licensekey": "forcefully-open-source",
        "upgradetutorials": "https://docs.iredmail.org/iredadmin-pro.releases.html",
        "purchased": "Never",
        "contacts": "nicole.rappe@bunny-lab.io",
        "latestversion": "5.5",
        "expired": "Never",
        "releasenotes": "https://docs.iredmail.org/iredadmin-pro.releases.html",
        "id": __id__
    }
    ```

``` sh
# Revert permission of the python script
sudo chown iredadmin:iredadmin /opt/www/iRedAdmin-2.5/libs/sysinfo.py

# Reboot the Server (To be safe)
sudo reboot
```

!!! success "Successful Activation"
    At this point, if you navigate to the [iRedAdmin-Pro License Page](https://mail.bunny-lab.io/iredadmin/system/license) you should see the server is activated successfully.