Files
obsidian/tank/programming/Mailserver setup/Complete Mail Server Setup Guide.md
2026-05-07 14:39:10 +02:00

28 KiB
Executable File

id, aliases, tags
id aliases tags
Complete Mail Server Setup Guide

Complete Mail Server Setup Guide - Arch Linux Edition

Postfix + Dovecot + MySQL + Notmuch + Encryption on Arch Linux LXC

This is the Arch Linux version of the complete mail server setup.


Part 1: Proxmox LXC Container Setup for Arch Linux

Download Arch Linux Template

In Proxmox shell:

pveam update
pveam available | grep arch
pveam download local archlinux-base_20231118-1_amd64.tar.zst

Or download manually from: https://uk.lxd.images.canonical.com/images/archlinux/current/amd64/default/

Create the LXC Container

In Proxmox UI:

  • CT ID: Choose available ID
  • Template: archlinux-base
  • Disk: 20GB minimum
  • CPU: 2 cores
  • RAM: 2GB minimum
  • Network: Bridge, static IP (e.g., 192.168.1.100)
  • Unprivileged: Yes
  • Features: nesting=1 (if you want Docker option later)

Start the container and note the IP address.

Initial Container Setup

SSH into your Arch LXC container:

# Initialize pacman keyring (Arch specific)
pacman-key --init
pacman-key --populate archlinux

# Update system
pacman -Syu --noconfirm

# Install essential tools
pacman -S --noconfirm base-devel vim wget curl git sudo openssh

# Set hostname
hostnamectl set-hostname mail.example.com
echo "192.168.1.100 mail.example.com mail" >> /etc/hosts

# Enable time sync
systemctl enable --now systemd-timesyncd

Configure Locale

# Uncomment your locale in /etc/locale.gen
vim /etc/locale.gen
# Uncomment: en_US.UTF-8 UTF-8

# Generate locale
locale-gen

# Set locale
echo "LANG=en_US.UTF-8" > /etc/locale.conf

Part 2: MySQL/MariaDB Database Setup

Install MariaDB

pacman -S --noconfirm mariadb mariadb-clients

Initialize MariaDB

# Initialize the database
mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql

# Start and enable MariaDB
systemctl enable --now mariadb

# Secure installation
mysql_secure_installation

During secure installation:

  • Set root password: Yes (choose strong password)
  • Remove anonymous users: Yes
  • Disallow root login remotely: Yes
  • Remove test database: Yes
  • Reload privilege tables: Yes

Create Mail Database and Tables

mysql -u root -p

In MySQL prompt:

CREATE DATABASE mailserver CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

CREATE USER 'mailuser'@'localhost' IDENTIFIED BY '1ChagemaiL';

GRANT ALL PRIVILEGES ON mailserver.* TO 'mailuser'@'localhost';

FLUSH PRIVILEGES;

USE mailserver;

-- Domains table
CREATE TABLE IF NOT EXISTS virtual_domains (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Users table
CREATE TABLE IF NOT EXISTS virtual_users (
    id INT NOT NULL AUTO_INCREMENT,
    domain_id INT NOT NULL,
    password VARCHAR(200) 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=utf8mb4;

-- Aliases table
CREATE TABLE IF NOT EXISTS 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=utf8mb4;

-- Add your domain
INSERT INTO virtual_domains (name) VALUES ('liphlink.xyz');

-- Verify
SELECT * FROM virtual_domains;

EXIT;

Save your database password securely!


Part 3: Postfix Installation and Configuration

Install Postfix

pacman -S --noconfirm postfix postfix-mysql

Backup Original Configuration

cp /etc/postfix/main.cf /etc/postfix/main.cf.orig

Configure Postfix Main Settings

Edit /etc/postfix/main.cf:

vim /etc/postfix/main.cf

Replace with:

# Basic Settings
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
mydestination = localhost.$mydomain, localhost
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = ipv4

# Compatibility with Dovecot
compatibility_level = 3.6

# Virtual Mailbox Settings
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

# Virtual transport
virtual_transport = lmtp:unix:private/dovecot-lmtp

# Mailbox Location
virtual_mailbox_base = /var/mail/vhosts
virtual_minimum_uid = 100
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000

# SMTP-AUTH Settings
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain = $myhostname
broken_sasl_auth_clients = yes

# TLS Settings
smtpd_tls_cert_file=/etc/letsencrypt/live/mail.example.com/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/mail.example.com/privkey.pem
smtpd_use_tls=yes
smtpd_tls_auth_only = yes
smtpd_tls_security_level = may
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_ciphers = high
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_mandatory_ciphers = high

smtp_tls_security_level = may
smtp_tls_loglevel = 1
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# SMTP Restrictions
smtpd_recipient_restrictions =
    permit_sasl_authenticated,
    permit_mynetworks,
    reject_unauth_destination,
    reject_invalid_hostname,
    reject_non_fqdn_hostname,
    reject_non_fqdn_sender,
    reject_non_fqdn_recipient,
    reject_unknown_sender_domain,
    reject_unknown_recipient_domain,
    reject_rbl_client zen.spamhaus.org,
    reject_rbl_client bl.spamcop.net,
    permit

smtpd_helo_restrictions =
    permit_mynetworks,
    permit_sasl_authenticated,
    reject_invalid_helo_hostname,
    reject_non_fqdn_helo_hostname,
    reject_unknown_helo_hostname,
    permit

smtpd_sender_restrictions =
    permit_mynetworks,
    permit_sasl_authenticated,
    reject_non_fqdn_sender,
    reject_unknown_sender_domain,
    permit

# Milter settings (for DKIM)
milter_default_action = accept
milter_protocol = 6
smtpd_milters = inet:127.0.0.1:8891
non_smtpd_milters = $smtpd_milters

# Additional Settings
disable_vrfy_command = yes
smtpd_helo_required = yes
message_size_limit = 52428800

Create MySQL Connection Files

1. Virtual Domains

vim /etc/postfix/mysql-virtual-mailbox-domains.cf
user = mailuser
password = your_strong_password_here
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM virtual_domains WHERE name='%s'

2. Virtual Mailboxes

vim /etc/postfix/mysql-virtual-mailbox-maps.cf
user = mailuser
password = your_strong_password_here
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM virtual_users WHERE email='%s'

3. Virtual Aliases

vim /etc/postfix/mysql-virtual-alias-maps.cf
user = mailuser
password = your_strong_password_here
hosts = 127.0.0.1
dbname = mailserver
query = SELECT destination FROM virtual_aliases WHERE source='%s'

Secure MySQL Configuration Files

chmod 640 /etc/postfix/mysql-*.cf
chown root:postfix /etc/postfix/mysql-*.cf

Configure Postfix Master.cf

Edit /etc/postfix/master.cf:

vim /etc/postfix/master.cf

Add these lines at the end:

submission inet n       -       n       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_tls_auth_only=yes
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o smtpd_helo_restrictions=
  -o smtpd_sender_restrictions=
  -o smtpd_recipient_restrictions=
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

smtps     inet  n       -       n       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o smtpd_helo_restrictions=
  -o smtpd_sender_restrictions=
  -o smtpd_recipient_restrictions=
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

Create Mail Directory Structure

# Create vmail user and group
groupadd -g 5000 vmail
useradd -g vmail -u 5000 vmail -d /var/mail/vhosts -m -s /bin/bash

# Create mail directory
mkdir -p /var/mail/vhosts/example.com
chown -R vmail:vmail /var/mail/vhosts
chmod -R 770 /var/mail/vhosts

Test MySQL Connectivity

postmap -q example.com mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf

Should return 1 if working.

Initialize Postfix Aliases

newaliases
postmap /etc/postfix/virtual

Part 4: Dovecot Installation and Configuration

Install Dovecot

pacman -S --noconfirm dovecot pigeonhole

Main Dovecot Configuration

Edit /etc/dovecot/dovecot.conf:

vim /etc/dovecot/dovecot.conf

Replace with:

protocols = imap lmtp sieve
listen = *, ::
dict {
}

!include conf.d/*.conf
!include_try local.conf

Configure Mail Location

Edit /etc/dovecot/conf.d/10-mail.conf:

vim /etc/dovecot/conf.d/10-mail.conf

Set these values:

mail_location = maildir:/var/mail/vhosts/%d/%n
mail_privileged_group = vmail

namespace inbox {
  inbox = yes

  mailbox Drafts {
    special_use = \Drafts
  }
  mailbox Junk {
    special_use = \Junk
  }
  mailbox Trash {
    special_use = \Trash
  }
  mailbox Sent {
    special_use = \Sent
  }
}

first_valid_uid = 5000
last_valid_uid = 5000
first_valid_gid = 5000
last_valid_gid = 5000

Configure Authentication

Edit /etc/dovecot/conf.d/10-auth.conf:

vim /etc/dovecot/conf.d/10-auth.conf

Modify:

disable_plaintext_auth = yes
auth_mechanisms = plain login

# Comment out
#!include auth-system.conf.ext

# Uncomment
!include auth-sql.conf.ext

SQL Authentication Backend

Edit /etc/dovecot/conf.d/auth-sql.conf.ext:

vim /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
}

SQL Connection Configuration

Create /etc/dovecot/dovecot-sql.conf.ext:

vim /etc/dovecot/dovecot-sql.conf.ext
driver = mysql
connect = host=127.0.0.1 dbname=mailserver user=mailuser password=your_strong_password_here

default_pass_scheme = SHA512-CRYPT

password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';

user_query = SELECT email as user, 'maildir:/var/mail/vhosts/%d/%n' as mail, 5000 AS uid, 5000 AS gid FROM virtual_users WHERE email='%u';

iterate_query = SELECT email AS username FROM virtual_users;

Secure it:

chown root:root /etc/dovecot/dovecot-sql.conf.ext
chmod 600 /etc/dovecot/dovecot-sql.conf.ext

Master Services Configuration

Edit /etc/dovecot/conf.d/10-master.conf:

vim /etc/dovecot/conf.d/10-master.conf

Configure services:

service imap-login {
  inet_listener imap {
    port = 143
  }
  inet_listener imaps {
    port = 993
    ssl = yes
  }
}

service pop3-login {
  inet_listener pop3 {
    port = 110
  }
  inet_listener pop3s {
    port = 995
    ssl = yes
  }
}

service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0600
    user = postfix
    group = postfix
  }
}

service imap {
}

service pop3 {
}

service auth {
  unix_listener /var/spool/postfix/private/auth {
    mode = 0666
    user = postfix
    group = postfix
  }

  unix_listener auth-userdb {
    mode = 0600
    user = vmail
    group = vmail
  }

  user = dovecot
}

service auth-worker {
  user = vmail
}

service dict {
  unix_listener dict {
  }
}

SSL Configuration

Edit /etc/dovecot/conf.d/10-ssl.conf:

vim /etc/dovecot/conf.d/10-ssl.conf
ssl = required
ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.example.com/privkey.pem

ssl_min_protocol = TLSv1.2
ssl_cipher_list = ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK
ssl_prefer_server_ciphers = yes

Sieve Configuration

Edit /etc/dovecot/conf.d/90-sieve.conf:

vim /etc/dovecot/conf.d/90-sieve.conf
plugin {
  sieve = file:~/sieve;active=~/.dovecot.sieve
  sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute
}

Set Permissions

chown -R vmail:dovecot /etc/dovecot
chmod -R o-rwx /etc/dovecot

Part 5: SSL Certificates with Let's Encrypt

Install Certbot

pacman -S --noconfirm certbot

Obtain Certificate

Ensure ports 80 and 443 are accessible:

certbot certonly --standalone -d mail.example.com

Follow prompts and enter your email.

Set Up Auto-Renewal

Create systemd timer:

systemctl enable --now certbot-renew.timer

Create renewal hook:

mkdir -p /etc/letsencrypt/renewal-hooks/post
vim /etc/letsencrypt/renewal-hooks/post/reload-mail-services.sh
#!/bin/bash
systemctl reload postfix
systemctl reload dovecot

Make executable:

chmod +x /etc/letsencrypt/renewal-hooks/post/reload-mail-services.sh

Part 6: OpenDKIM Setup

Install OpenDKIM

pacman -S --noconfirm opendkim

Configure OpenDKIM

Edit /etc/opendkim/opendkim.conf:

vim /etc/opendkim/opendkim.conf
AutoRestart             Yes
AutoRestartRate         10/1h
UMask                   002
Syslog                  yes
SyslogSuccess           Yes
LogWhy                  Yes

Canonicalization        relaxed/simple

ExternalIgnoreList      refile:/etc/opendkim/TrustedHosts
InternalHosts           refile:/etc/opendkim/TrustedHosts
KeyTable                refile:/etc/opendkim/KeyTable
SigningTable            refile:/etc/opendkim/SigningTable

Mode                    sv
PidFile                 /run/opendkim/opendkim.pid
SignatureAlgorithm      rsa-sha256

UserID                  opendkim:mail

Socket                  inet:8891@localhost

Create Directory Structure

mkdir -p /etc/opendkim/keys/example.com
chown -R opendkim:mail /etc/opendkim
chmod -R 700 /etc/opendkim/keys

Create Configuration Files

TrustedHosts:

vim /etc/opendkim/TrustedHosts
127.0.0.1
localhost
192.168.1.0/24
*.example.com

KeyTable:

vim /etc/opendkim/KeyTable
mail._domainkey.example.com example.com:mail:/etc/opendkim/keys/example.com/mail.private

SigningTable:

vim /etc/opendkim/SigningTable
*@example.com mail._domainkey.example.com

Generate DKIM Keys

cd /etc/opendkim/keys/liphlink.xyz
opendkim-genkey -s mail -d liphlink.xyz
chown opendkim:mail mail.private
chmod 600 mail.private

Get Public Key for DNS

cat /etc/opendkim/keys/liphlink.xyz/mail.txt

Save this for your DNS records.

Start OpenDKIM

systemctl enable --now opendkim

Part 7: Start and Enable Services

Start All Services

# Create postfix spool directory for dovecot
mkdir -p /var/spool/postfix/private
chown postfix:postfix /var/spool/postfix/private

# Start services
systemctl enable --now postfix
systemctl enable --now dovecot
systemctl enable --now opendkim

# Check status
systemctl status postfix
systemctl status dovecot
systemctl status opendkim

All should show active (running).


Part 8: Notmuch Integration

Install Notmuch

pacman -S --noconfirm notmuch

Configure Notmuch for vmail User

# Switch to vmail user
su - vmail
cd /var/mail/vhosts/liphlink.xyz

# Create user directory if doesn't exist
mkdir -p phil
cd phil

# Initialize notmuch
notmuch setup

Prompts:

  • Your full name: User Name
  • Primary email: user@example.com
  • Maildir location: /var/mail/vhosts/example.com/user
  • Tags for new messages: unread;inbox
  • Tags to exclude: deleted;spam

Index existing mail:

notmuch new
exit  # Exit vmail user

Create Indexing Script

vim
#!/bin/bash

for domain in /var/mail/vhosts/*; do
    if [ -d "$domain" ]; then
        for user in "$domain"/*; do
            if [ -d "$user" ]; then
                cd "$user" || continue

                if [ ! -d ".notmuch" ]; then
                    su -s /bin/bash vmail -c "cd '$user' && notmuch new"
                else
                    su -s /bin/bash vmail -c "cd '$user' && notmuch new"
                fi
            fi
        done
    fi
done

Make executable:

chmod +x /usr/local/bin/notmuch-index-all.sh

Set Up Automatic Indexing

Create systemd service:

vim /etc/systemd/system/notmuch-index.service
[Unit]
Description=Notmuch email indexing
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/notmuch-index-all.sh
User=root

[Install]
WantedBy=multi-user.target

Create timer:

vim /etc/systemd/system/notmuch-index.timer
[Unit]
Description=Run notmuch indexing every 5 minutes

[Timer]
OnBootSec=2min
OnUnitActiveSec=5min

[Install]
WantedBy=timers.target

Enable:

systemctl daemon-reload
systemctl enable --now notmuch-index.timer

Configure Dovecot for Real-time Indexing

vim /usr/local/bin/notmuch-index-single
#!/bin/bash
if [ -n "$HOME" ]; then
    cd "$HOME" || exit 0
    /usr/bin/notmuch new > /dev/null 2>&1
fi

Make executable:

chmod +x /usr/local/bin/notmuch-index-single

Create global sieve:

mkdir -p /var/mail/vhosts/sieve
vim /var/mail/vhosts/sieve/after.sieve
require ["vnd.dovecot.pipe", "copy", "environment"];
pipe :copy "notmuch-index-single";

Compile:

sievec /var/mail/vhosts/sieve/after.sieve
chown -R vmail:vmail /var/mail/vhosts/sieve

Update Dovecot config:

vim /etc/dovecot/conf.d/90-sieve.conf

Add:

plugin {
  sieve = file:~/sieve;active=~/.dovecot.sieve
  sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute
  sieve_after = /var/mail/vhosts/sieve/after.sieve
}

Restart:

systemctl restart dovecot

Part 9: Email Encryption with Dovecot Mail-Crypt

Install Required Packages

# Already included in dovecot package on Arch
pacman -S --noconfirm openssl

Generate Master Encryption Keys

mkdir -p /var/lib/dovecot/mail-crypt
cd /var/lib/dovecot/mail-crypt

# Generate EC keys
openssl ecparam -name prime256v1 -genkey | openssl pkey -out ecprivkey.pem
openssl pkey -in ecprivkey.pem -pubout -out ecpubkey.pem

# Set permissions
chown vmail:vmail *.pem
chmod 400 *.pem

Configure Mail-Crypt Plugin

Create /etc/dovecot/conf.d/90-mail-crypt.conf:

vim /etc/dovecot/conf.d/90-mail-crypt.conf
mail_plugins = $mail_plugins mail_crypt

plugin {
  mail_crypt_global_private_key = </var/lib/dovecot/mail-crypt/ecprivkey.pem
  mail_crypt_global_public_key = </var/lib/dovecot/mail-crypt/ecpubkey.pem
  mail_crypt_save_version = 2
  mail_crypt_curve = secp521r1
  mail_crypt_private_password = %w
}

Update main mail config:

vim /etc/dovecot/conf.d/10-mail.conf

Find mail_plugins and add mail_crypt:

mail_plugins = mail_crypt

Restart Dovecot:

systemctl restart dovecot

Install PGP/GPG Tools

pacman -S --noconfirm gnupg

PGP Key Management Script

vim /usr/local/bin/mail-pgp-manager
#!/bin/bash

USER_EMAIL="$1"
ACTION="$2"

GNUPG_HOME="/var/mail/vhosts/$(echo $USER_EMAIL | cut -d'@' -f2)/$(echo $USER_EMAIL | cut -d'@' -f1)/.gnupg"

case "$ACTION" in
    generate)
        mkdir -p "$GNUPG_HOME"
        chmod 700 "$GNUPG_HOME"
        chown vmail:vmail "$GNUPG_HOME"

        su - vmail -c "gpg --homedir $GNUPG_HOME --batch --gen-key <<EOF
Key-Type: RSA
Key-Length: 4096
Subkey-Type: RSA
Subkey-Length: 4096
Name-Real: $USER_EMAIL
Name-Email: $USER_EMAIL
Expire-Date: 0
%no-protection
%commit
EOF"
        ;;

    export-public)
        su - vmail -c "gpg --homedir $GNUPG_HOME --armor --export $USER_EMAIL"
        ;;

    list)
        su - vmail -c "gpg --homedir $GNUPG_HOME --list-keys"
        ;;

    *)
        echo "Usage: $0 <email> <generate|export-public|list>"
        exit 1
        ;;
esac

Make executable:

chmod +x /usr/local/bin/mail-pgp-manager

Part 10: Creating Email Accounts

Generate Password Hash

doveadm pw -s SHA512-CRYPT

Enter your password. Copy the hash.

Add User to Database

mysql -u root -p mailserver
-- Get domain ID
SELECT id FROM virtual_domains WHERE name='liphlink.xyz';

-- Add user (replace hash with your generated hash)
INSERT INTO virtual_users (domain_id, password, email)
VALUES (1, '{SHA512-CRYPT}$6$OWM2H5QRTyR5qLky$n1lDTBB7CcHpMNNtXGe4pwC.OH9/hA6Gen3pwWr9R5AoboJuqg9P/gl..WQXuy82cGizNy3WDEzRNWnevqfbx0', 'phil@liphlink.xyz');

-- Verify
SELECT * FROM virtual_users;

EXIT;
-- Get domain ID
SELECT id FROM virtual_domains WHERE name='liphlink.xyz';

-- Add user (replace hash with your generated hash)
INSERT INTO virtual_users (domain_id, password, email)
VALUES (1, '{SHA512-CRYPT}$6$5ZnV6ungZJDq00k4$UhNVKgXoJp1AvfTMnqyDFtyECG4/5UNd.ZPlQP9OVdeOpgjssJwP0qaOWCoP0AHjL7/zAsgUR4GIY/YczK2hB1', 'liph@liphlink.xyz');

-- Verify
SELECT * FROM virtual_users;

EXIT;

Create Maildir for User

mkdir -p /var/mail/vhosts/liphlink.xyz/liph/Maildir/{cur,new,tmp}
chown -R vmail:vmail /var/mail/vhosts/liphlink.xyz/liph
chmod -R 770 /var/mail/vhosts/liphlink.xyz/liph

Part 11: Firewall Configuration

Install and Configure UFW

pacman -S --noconfirm ufw

# Allow SSH first!
ufw allow 22/tcp

# Mail server ports
ufw allow 25/tcp    # SMTP
ufw allow 587/tcp   # Submission
ufw allow 465/tcp   # SMTPS
ufw allow 993/tcp   # IMAPS
ufw allow 143/tcp   # IMAP
ufw allow 80/tcp    # HTTP (Let's Encrypt)
ufw allow 443/tcp   # HTTPS

# Enable
ufw enable
systemctl enable ufw

Part 12: Security Hardening with Fail2ban

Install Fail2ban

pacman -S --noconfirm fail2ban

Configure Fail2ban

vim /etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5

[postfix-sasl]
enabled = true
port = smtp,submission,smtps
logpath = /var/log/mail.log
filter = postfix-sasl

[dovecot]
enabled = true
port = pop3,pop3s,imap,imaps,submission,smtps
logpath = /var/log/mail.log
filter = dovecot

Enable and start:

systemctl enable --now fail2ban

Part 13: Testing Everything

Test Postfix

# Check config
postfix check

# Test MySQL lookups
postmap -q example.com mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
postmap -q user@example.com mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf

Test Dovecot Authentication

doveadm auth test user@example.com

Send Test Email

# Install mail utility
pacman -S --noconfirm s-nail

# Send test
echo "Test email" | mail -s "Test" phil@liphlink.xyz

# Check logs
journalctl -u postfix -f
journalctl -u dovecot -f

ALTER USER 'mailuser'@'localhost' IDENTIFIED BY '1ChagemaiL'; FLUSH PRIVILEGES; EXIT;

Test IMAP

openssl s_client -connect localhost:993 -crlf

Then:

a LOGIN user@example.com password
b SELECT INBOX
c LOGOUT

Test Notmuch

su - vmail
cd /var/mail/vhosts/example.com/user
notmuch search tag:inbox
notmuch search from:sender@example.com
exit

Part 14: Monitoring and Logs

View Logs

# Postfix logs
journalctl -u postfix -f

# Dovecot logs
journalctl -u dovecot -f

# All mail logs
journalctl -u postfix -u dovecot -f

# OpenDKIM logs
journalctl -u opendkim -f

Check Service Status

systemctl status postfix dovecot opendkim mariadb

Check Mail Queue

postqueue -p
mailq

Part 15: Backup Script

vim /usr/local/bin/mail-backup.sh
#!/bin/bash

BACKUP_DIR="/root/mail-backup"
DATE=$(date +%Y-%m-%d)

mkdir -p "$BACKUP_DIR"

# Backup configs
tar -czf "$BACKUP_DIR/postfix-$DATE.tar.gz" /etc/postfix/
tar -czf "$BACKUP_DIR/dovecot-$DATE.tar.gz" /etc/dovecot/
tar -czf "$BACKUP_DIR/opendkim-$DATE.tar.gz" /etc/opendkim/

# Backup database
mysqldump -u root -p mailserver > "$BACKUP_DIR/mailserver-$DATE.sql"

# Backup mail (optional - can be large)
# tar -czf "$BACKUP_DIR/vmail-$DATE.tar.gz" /var/mail/vhosts/

# Keep only last 7 days
find "$BACKUP_DIR" -type f -mtime +7 -delete

echo "Backup completed: $DATE"

Make executable:

chmod +x /usr/local/bin/mail-backup.sh

Schedule with cron or systemd timer.


Arch Linux Specific Notes

Package Management

# Update system
pacman -Syu

# Search packages
pacman -Ss package-name

# Install package
pacman -S package-name

# Remove package
pacman -R package-name

AUR Helper (Optional)

For packages not in official repos:

# Install yay
pacman -S --noconfirm git base-devel
git clone https://aur.archlinux.org/yay.git
cd yay
makepkg -si

Systemd Journal Size

Limit journal size:

vim /etc/systemd/journald.conf
[Journal]
SystemMaxUse=500M

Restart:

systemctl restart systemd-journald

Quick Reference Commands

User Management

# Create user with encryption
doveadm pw -s SHA512-CRYPT
mysql -u root -p mailserver -e "INSERT INTO virtual_users VALUES (1, 'HASH', 'user@example.com');"
mail-pgp-manager user@example.com generate

Service Management

# Restart services
systemctl restart postfix dovecot opendkim

# View logs
journalctl -u postfix -u dovecot --since "1 hour ago"

# Check mail queue
postqueue -p

Notmuch

# Manual index
/usr/local/bin/notmuch-index-all.sh

# Check timer
systemctl status notmuch-index.timer

Troubleshooting

Services won't start

# Check for errors
systemctl status servicename
journalctl -xeu servicename

# Check configuration
postfix check
doveconf -n

Permission issues

# Fix ownership
chown -R vmail:vmail /var/mail/vhosts
chown -R postfix:postfix /var/spool/postfix
chown -R dovecot:dovecot /etc/dovecot

# Fix permissions
chmod -R 770 /var/mail/vhosts
chmod 600 /etc/dovecot/dovecot-sql.conf.ext

Database connection fails

# Test connection
mysql -u mailuser -p mailserver

# Check postfix mysql maps
postmap -q example.com mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf

Advantages of Arch Linux for Mail Server

Rolling release: Always latest stable versions Minimal bloat: Only what you install Great documentation: Arch Wiki is excellent Performance: Optimized packages Control: Full customization SystemD native: Modern service management Fast package manager: Pacman is very fast


Next Steps

  1. Configure DNS records (see dns-cloudflare-reference.md)
  2. Test email delivery with mail-tester.com
  3. Set up webmail (Roundcube) if desired
  4. Configure backups
  5. Monitor deliverability
  6. Set up additional security (AppArmor/SELinux)

Additional Resources

Your Arch Linux mail server is now complete with encryption, notmuch indexing, and all modern security features! 🚀

Enjoy your fully customizable, privacy-respecting email infrastructure on Arch Linux!