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
- Configure DNS records (see dns-cloudflare-reference.md)
- Test email delivery with mail-tester.com
- Set up webmail (Roundcube) if desired
- Configure backups
- Monitor deliverability
- Set up additional security (AppArmor/SELinux)
Additional Resources
- Arch Wiki Mail Server: https://wiki.archlinux.org/title/Mail_server
- Arch Wiki Postfix: https://wiki.archlinux.org/title/Postfix
- Arch Wiki Dovecot: https://wiki.archlinux.org/title/Dovecot
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!