--- id: Complete Mail Server Setup Guide aliases: [] tags: [] --- # 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: ```bash 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: ```bash # 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 ```bash # 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 ```bash pacman -S --noconfirm mariadb mariadb-clients ``` ### Initialize MariaDB ```bash # 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 ```bash mysql -u root -p ``` In MySQL prompt: ```sql 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 ```bash pacman -S --noconfirm postfix postfix-mysql ``` ### Backup Original Configuration ```bash cp /etc/postfix/main.cf /etc/postfix/main.cf.orig ``` ### Configure Postfix Main Settings Edit `/etc/postfix/main.cf`: ```bash 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** ```bash 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** ```bash 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** ```bash 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 ```bash chmod 640 /etc/postfix/mysql-*.cf chown root:postfix /etc/postfix/mysql-*.cf ``` ### Configure Postfix Master.cf Edit `/etc/postfix/master.cf`: ```bash 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 ```bash # 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 ```bash postmap -q example.com mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf ``` Should return `1` if working. ### Initialize Postfix Aliases ```bash newaliases postmap /etc/postfix/virtual ``` --- ## Part 4: Dovecot Installation and Configuration ### Install Dovecot ```bash pacman -S --noconfirm dovecot pigeonhole ``` ### Main Dovecot Configuration Edit `/etc/dovecot/dovecot.conf`: ```bash 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`: ```bash 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`: ```bash 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`: ```bash 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`: ```bash 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: ```bash 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`: ```bash 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`: ```bash vim /etc/dovecot/conf.d/10-ssl.conf ``` ``` ssl = required ssl_cert = /dev/null 2>&1 fi ``` Make executable: ```bash chmod +x /usr/local/bin/notmuch-index-single ``` Create global sieve: ```bash 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: ```bash sievec /var/mail/vhosts/sieve/after.sieve chown -R vmail:vmail /var/mail/vhosts/sieve ``` Update Dovecot config: ```bash 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: ```bash systemctl restart dovecot ``` --- ## Part 9: Email Encryption with Dovecot Mail-Crypt ### Install Required Packages ```bash # Already included in dovecot package on Arch pacman -S --noconfirm openssl ``` ### Generate Master Encryption Keys ```bash 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`: ```bash vim /etc/dovecot/conf.d/90-mail-crypt.conf ``` ``` mail_plugins = $mail_plugins mail_crypt plugin { mail_crypt_global_private_key = " exit 1 ;; esac ``` Make executable: ```bash chmod +x /usr/local/bin/mail-pgp-manager ``` --- ## Part 10: Creating Email Accounts ### Generate Password Hash ```bash doveadm pw -s SHA512-CRYPT ``` Enter your password. Copy the hash. ### Add User to Database ```bash mysql -u root -p mailserver ``` ```sql -- 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; ``` ```sql -- 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 ```bash 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 ```bash 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 ```bash pacman -S --noconfirm fail2ban ``` ### Configure Fail2ban ```bash vim /etc/fail2ban/jail.local ``` ```ini [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: ```bash systemctl enable --now fail2ban ``` --- ## Part 13: Testing Everything ### Test Postfix ```bash # 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 ```bash doveadm auth test user@example.com ``` ### Send Test Email ```bash # 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 ```bash openssl s_client -connect localhost:993 -crlf ``` Then: ``` a LOGIN user@example.com password b SELECT INBOX c LOGOUT ``` ### Test Notmuch ```bash 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 ```bash # 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 ```bash systemctl status postfix dovecot opendkim mariadb ``` ### Check Mail Queue ```bash postqueue -p mailq ``` --- ## Part 15: Backup Script ```bash vim /usr/local/bin/mail-backup.sh ``` ```bash #!/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: ```bash chmod +x /usr/local/bin/mail-backup.sh ``` Schedule with cron or systemd timer. --- ## Arch Linux Specific Notes ### Package Management ```bash # 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: ```bash # 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: ```bash vim /etc/systemd/journald.conf ``` ```ini [Journal] SystemMaxUse=500M ``` Restart: ```bash systemctl restart systemd-journald ``` --- ## Quick Reference Commands ### User Management ```bash # 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 ```bash # Restart services systemctl restart postfix dovecot opendkim # View logs journalctl -u postfix -u dovecot --since "1 hour ago" # Check mail queue postqueue -p ``` ### Notmuch ```bash # Manual index /usr/local/bin/notmuch-index-all.sh # Check timer systemctl status notmuch-index.timer ``` --- ## Troubleshooting ### Services won't start ```bash # Check for errors systemctl status servicename journalctl -xeu servicename # Check configuration postfix check doveconf -n ``` ### Permission issues ```bash # 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 ```bash # 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 - **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!