Self-Hosted Mail Server
A to Z Guide
A complete, battle-tested guide to setting up SMTP + IMAP on a Linux VPS using Postfix, Dovecot, and OpenDKIM. Every command, every config file, every error we hit, and every fix that worked.
Before You Start — Honest Expectations
Setting up a mail server is deceptively complex. The installation itself takes a few hours; the surrounding work — DNS, IP reputation, deliverability — takes much longer to get right.
What actually makes it hard
- DNS is unforgiving — SPF, DKIM, DMARC, PTR/rDNS, and MX must all align. Any one being wrong tanks deliverability.
- Deliverability is a separate beast — Gmail, Outlook, and Yahoo are aggressive. Fresh IPs get soft-rejected for weeks. IP warm-up takes time.
- Maintenance is real — cert renewals, log monitoring, spam filtering, blacklist tracking, brute-force protection.
When self-hosting makes sense
| Use case | Self-host? |
|---|---|
| Personal mailbox / hobby project | ✓ Yes |
| Internal/transactional mail for an app | ✓ Yes |
| Critical client-facing business email | ✗ No — use Migadu, Fastmail, Zoho |
Why this stack
Researched and confirmed in 2026:
- Postfix — the most widely deployed MTA, dominates enterprise Linux deployments
- Dovecot — the dominant IMAP server, high-performance, battle-tested
- OpenDKIM — standard DKIM milter
Alternatives considered and rejected: Exim (more complex syntax, no advantage), Haraka (overkill, Node.js-based), Maddy (too immature), Mailcow/Mail-in-a-Box (heavier Docker stack, harder to debug).
Architecture Overview
Key design decisions
| Decision | Choice | Reasoning |
|---|---|---|
| Mailbox storage | Flat files (Maildir) | No DB backend needed; single-domain, simpler, zero dependencies |
| Auth backend | passwd-file + static userdb | Tiny user count, no LDAP/SQL complexity |
| OpenDKIM socket | inet:12301@localhost | Unix sockets fail — Postfix runs in chroot at /var/spool/postfix |
| TLS challenge | DNS-01 via Cloudflare API | Ports 80/443 in use by Caddy; DNS-01 doesn't need them |
| IPv6 | Disabled (ipv4 only) | IPv6 PTR not set → SPF fails on IPv6; simpler to disable |
VPS Readiness Audit
A 10-minute audit prevents 4 hours of failed installs. Run this via Claude Code on your VPS before touching anything.
Audit prompt
You are a Linux sysadmin assistant. Audit this Ubuntu VPS and
determine if it's ready for a self-hosted email server (Postfix
SMTP + Dovecot IMAP). Do not install anything.
Network & IP: public IP, outbound port 25/587/465/993/143 test,
PTR/rDNS lookup.
DNS: hostname -f, /etc/hosts, check MX/SPF/DKIM/DMARC records.
IP Reputation: check against Spamhaus ZEN, SpamCop, Barracuda,
SORBS via MXToolbox.
System: OS, disk, RAM, swap, UFW/iptables, any existing mail
packages (postfix/dovecot/exim/sendmail), local DNS resolver.
SSL/TLS: certbot installed? /etc/letsencrypt/ exists?
Verdict: Ready / Needs fixes / Blocked (list specific blockers).
Critical blockers
- Port 25 outbound BLOCKED — DigitalOcean, Vultr, Linode block this on new accounts. You literally cannot send mail. Open a support ticket or change providers. Contabo allows it by default.
- IP on major blacklists — check Spamhaus ZEN, SpamCop, Barracuda, SORBS at mxtoolbox.com/blacklists.aspx
- Generic PTR record (e.g.
vmi3038401.contaboserver.net) — must be changeable to your mail FQDN
Manual port test from outside
telnet mail.yourdomain.com 25
# Expected: 220 mail.yourdomain.com ESMTP Postfix (Ubuntu)
DNS & Network Setup
4.1 PTR (reverse DNS)
The single most important record for deliverability. Without a matching PTR, Gmail rejects your mail at EHLO.
- Open your VPS provider's control panel → find rDNS / PTR settings
- Set PTR for your IP to
mail.yourdomain.com - Contabo: self-serve in the VPS panel, no support ticket needed
dig -x YOUR_IP +short
# Expected: mail.yourdomain.com.
4.2 Cloudflare DNS records
* A record that's proxied, create an explicit mail A record — it will override the wildcard.
A record
| Type | Name | Content | Proxy |
|---|---|---|---|
| A | mail | YOUR_VPS_IP | DNS only (grey) |
MX record
| Type | Name | Mail server | Priority | Proxy |
|---|---|---|---|---|
| MX | @ | mail.yourdomain.com | 10 | DNS only |
SPF — TXT record
TXT for both.
| Type | Name | Content |
|---|---|---|
| TXT | @ | v=spf1 mx -all |
DMARC — TXT record
| Type | Name | Content |
|---|---|---|
| TXT | _dmarc | v=DMARC1; p=none; rua=mailto:[email protected] |
Start with p=none (monitor mode). Tighten to p=quarantine or p=reject after a few weeks of clean sends.
DKIM — skip for now
Add this in Phase 7 after OpenDKIM generates the key.
4.3 Verify propagation
dig MX yourdomain.com +short
# Expected: 10 mail.yourdomain.com.
dig A mail.yourdomain.com +short
# Expected: YOUR_IP (NOT 104.x or 172.x Cloudflare IPs)
dig TXT yourdomain.com +short
# Expected: "v=spf1 mx -all"
dig TXT _dmarc.yourdomain.com +short
# Expected: "v=DMARC1; p=none; ..."
System Preparation
5.1 Set FQDN
sudo hostnamectl set-hostname mail.yourdomain.com
Edit /etc/hosts — replace the old hostname line:
# Replace this:
127.0.1.1 vmi3038401.contaboserver.net vmi3038401
# With this:
127.0.1.1 mail.yourdomain.com mail
hostname -f
# Expected: mail.yourdomain.com
# Note: shell prompt shows user@mail:~$ — that's normal, short hostname only
5.2 Add swap
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -h
5.3 UFW firewall
sudo ufw allow 22/tcp # SSH
sudo ufw allow 25/tcp # SMTP inbound
sudo ufw allow 465/tcp # Submissions SSL wrapper
sudo ufw allow 587/tcp # Submission STARTTLS
sudo ufw allow 143/tcp # IMAP
sudo ufw allow 993/tcp # IMAPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
TLS Certificate
DNS-01 is the right approach when ports 80/443 are used by other services (Caddy, Nginx, etc.). It authenticates via Cloudflare API — no port coordination needed.
6.1 Install certbot
sudo apt install certbot python3-certbot-dns-cloudflare -y
6.2 Create Cloudflare API token
- Cloudflare Dashboard → My Profile → API Tokens → Create Token
- Use the "Edit zone DNS" template
- Scope it to your specific domain only
- Copy the token
6.3 Store credentials
sudo mkdir -p /etc/letsencrypt/cloudflare
sudo nano /etc/letsencrypt/cloudflare/yourdomain.ini
dns_cloudflare_api_token = YOUR_TOKEN_HERE
sudo chmod 600 /etc/letsencrypt/cloudflare/yourdomain.ini
6.4 Issue the certificate
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare/yourdomain.ini \
-d mail.yourdomain.com \
--email [email protected] \
--agree-tos \
--no-eff-email
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/mail.yourdomain.com/privkey.pem
Certbot sets up automatic renewal via systemd timer. Add a deploy hook in the Hardening section so Postfix/Dovecot reload after renewal.
Postfix (SMTP)
7.1 Install
sudo apt update
sudo apt install postfix -y
# Do NOT install postfix-mysql or postfix-pgsql
# Flat files are simpler with zero database dependencies
During the interactive dialog: select Internet Site. Enter yourdomain.com (bare domain, not mail.yourdomain.com) as the system mail name.
7.2 Configure /etc/postfix/main.cf
mydestinationmust NOT includeyourdomain.com— it conflicts withvirtual_mailbox_domains. Postfix prefersmydestinationand skips virtual delivery entirely.virtual_uid_mapsandvirtual_gid_mapsare mandatory — without them, mail sits deferred withnot found in virtual_uid_maps.virtual_mailbox_limit = 0is required — the default (~51MB) is smaller thanmessage_size_limit, causing a fatal startup error.
# Debian defaults
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
append_dot_mydomain = no
readme_directory = no
compatibility_level = 3.6
# Identity
myhostname = mail.yourdomain.com
mydomain = yourdomain.com
myorigin = $mydomain
# Network — use ipv4 only to avoid IPv6 SPF failures
inet_interfaces = all
inet_protocols = ipv4
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
# yourdomain.com intentionally OMITTED from mydestination
mydestination = $myhostname, localhost.$mydomain, localhost
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
# Virtual mailboxes (flat file, no database)
virtual_mailbox_domains = yourdomain.com
virtual_mailbox_base = /var/vmail
virtual_mailbox_maps = hash:/etc/postfix/vmailbox
virtual_alias_maps = hash:/etc/postfix/virtual
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
virtual_mailbox_limit = 0
# TLS
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.yourdomain.com/privkey.pem
smtpd_tls_security_level = may
smtp_tls_CApath = /etc/ssl/certs
smtp_tls_security_level = may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_loglevel = 1
# SMTP auth via Dovecot SASL
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
# Restrictions
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination
disable_vrfy_command = yes
smtpd_helo_required = yes
# Limits
mailbox_size_limit = 0
message_size_limit = 52428800
recipient_delimiter = +
relayhost =
# DKIM milter (inet socket — unix socket fails inside chroot)
milter_default_action = accept
milter_protocol = 6
smtpd_milters = inet:localhost:12301
non_smtpd_milters = inet:localhost:12301
7.3 Create vmail user and storage
sudo groupadd -g 5000 vmail
sudo useradd -g vmail -u 5000 vmail -d /var/vmail -m
sudo mkdir -p /var/vmail
sudo chown vmail:vmail /var/vmail
sudo chmod 700 /var/vmail
7.4 Create virtual mailbox map
[email protected] yourdomain.com/user/
sudo touch /etc/postfix/virtual
sudo postmap /etc/postfix/vmailbox
sudo postmap /etc/postfix/virtual
7.5 Enable submission ports in master.cf
master.cf uses a column format — never put key = value lines there. That's main.cf syntax. Mixing them causes: file /etc/postfix/master.cf: line N: bad field count
Find and uncomment the submission and submissions blocks, adding the milter override:
submission inet n - y - - 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_recipient_restrictions=permit_sasl_authenticated,reject
-o smtpd_milters=inet:localhost:12301
-o milter_macro_daemon_name=ORIGINATING
submissions inet n - y - - smtpd
-o syslog_name=postfix/submissions
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_reject_unlisted_recipient=no
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
-o smtpd_milters=inet:localhost:12301
-o milter_macro_daemon_name=ORIGINATING
7.6 Verify
sudo postfix check # silence = good
sudo postfix reload
sudo ss -tlnp | grep master
# Expected: ports 25, 465, 587 all LISTEN
Dovecot (IMAP)
8.1 Install
sudo apt install dovecot-core dovecot-imapd -y
8.2 dovecot.conf — protocols
protocols = imap
8.3 10-mail.conf — mail location
mail_location = maildir:/var/vmail/%d/%n/
# %d = domain, %n = user
mail_privileged_group = vmail
8.4 10-auth.conf — authentication
disable_plaintext_auth = yes
auth_mechanisms = plain login
# Comment out system auth, enable passwd-file
#!include auth-system.conf.ext
!include auth-passwdfile.conf.ext
8.5 auth-passwdfile.conf.ext
passwd-file for userdb fails with user not found from userdb because the file has passwords only, not UID/GID/home info. Use static driver — it assigns fixed values to every authenticated user.
passdb {
driver = passwd-file
args = scheme=SHA512-CRYPT username_format=%u /etc/dovecot/users
}
userdb {
driver = static
args = uid=vmail gid=vmail home=/var/vmail/%d/%n/
}
8.6 10-ssl.conf — TLS
ssl = required
ssl_cert = </etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.yourdomain.com/privkey.pem
ssl_min_protocol = TLSv1.2
8.7 10-master.conf — Postfix auth socket
service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
}
8.8 Create users file and add mailbox
sudo touch /etc/dovecot/users
sudo chmod 640 /etc/dovecot/users
sudo chown root:dovecot /etc/dovecot/users
# Generate password hash
sudo doveadm pw -s SHA512-CRYPT
# Outputs: {SHA512-CRYPT}$6$abc...
[email protected]:{SHA512-CRYPT}$6$yourhashhere
8.9 Restart and test auth
sudo systemctl restart dovecot
sudo doveadm auth test [email protected]
# Expected:
# passdb: [email protected] auth succeeded
# userdb: [email protected] auth succeeded
OpenDKIM (DKIM Signing)
9.1 Install
sudo apt install opendkim opendkim-tools -y
9.2 Configure /etc/opendkim.conf
Socket local:/run/opendkim/opendkim.sock. This will not work with Postfix. Postfix runs smtpd in a chroot at /var/spool/postfix — the path /run/opendkim/ doesn't exist inside the chroot. Use an inet socket instead.
Syslog yes
SyslogSuccess yes
LogWhy yes
Canonicalization relaxed/simple
Mode sv
SubDomains no
OversignHeaders From
KeyTable /etc/opendkim/KeyTable
SigningTable refile:/etc/opendkim/SigningTable
ExternalIgnoreList /etc/opendkim/TrustedHosts
InternalHosts /etc/opendkim/TrustedHosts
Socket inet:12301@localhost
PidFile /run/opendkim/opendkim.pid
UserID opendkim
UMask 007
TrustAnchorFile /usr/share/dns/root.key
TemporaryDirectory /var/tmp
9.3 Generate DKIM key
key data is not secure: key is in group N which has multiple users. Keep group as root (single user), mode 600. Do NOT add postfix to the opendkim group — it's unnecessary with inet socket and triggers this error.
sudo mkdir -p /etc/opendkim/keys/yourdomain.com
sudo opendkim-genkey -b 2048 -d yourdomain.com \
-D /etc/opendkim/keys/yourdomain.com -s mail -v
sudo chown opendkim:root /etc/opendkim/keys/yourdomain.com/mail.private
sudo chmod 600 /etc/opendkim/keys/yourdomain.com/mail.private
9.4 Configure key tables
mail._domainkey.yourdomain.com yourdomain.com:mail:/etc/opendkim/keys/yourdomain.com/mail.private
*@yourdomain.com mail._domainkey.yourdomain.com
127.0.0.1
::1
localhost
yourdomain.com
mail.yourdomain.com
9.5 Start OpenDKIM
sudo systemctl enable opendkim
sudo systemctl restart opendkim
sudo ss -tlnp | grep 12301
# Expected: LISTEN on 127.0.0.1:12301
9.6 Add DKIM public key to Cloudflare DNS
sudo cat /etc/opendkim/keys/yourdomain.com/mail.txt
# Outputs multi-line quoted strings — concatenate ALL parts
# into a single string with no spaces between segments
| Type | Name | Content | Proxy |
|---|---|---|---|
| TXT | mail._domainkey | v=DKIM1; h=sha256; k=rsa; p=MIIB...IDAQAB (one line) | DNS only |
9.7 Verify DKIM key
sudo opendkim-testkey -d yourdomain.com -s mail -vvv
# "key not secure" = normal (no DNSSEC) — not a problem
# "key OK" = this is what matters
Logging (rsyslog)
/var/log/mail.log doesn't exist. You get no visibility into incoming mail or delivery failures. Install rsyslog before you test anything.
sudo apt install rsyslog -y
sudo systemctl enable rsyslog
sudo systemctl start rsyslog
# If /var/log/mail.log still doesn't exist:
sudo touch /var/log/mail.log
sudo chown syslog:adm /var/log/mail.log
sudo chmod 640 /var/log/mail.log
sudo systemctl restart rsyslog
sudo systemctl restart postfix
Live log monitoring
sudo tail -f /var/log/mail.log
# Or via journalctl:
sudo journalctl -u postfix -f
sudo journalctl -u dovecot -f
sudo journalctl -u opendkim -f
Testing & Verification
11.1 Install swaks
sudo apt install swaks -y
11.2 Local delivery test
swaks \
--to [email protected] \
--from [email protected] \
--server 127.0.0.1 \
--port 25
# Check: /var/vmail/yourdomain.com/user/new/
11.3 Authenticated send — port 587 (STARTTLS)
swaks \
--to [email protected] \
--from [email protected] \
--server mail.yourdomain.com \
--port 587 \
--tls \
--auth LOGIN \
--auth-user [email protected] \
--auth-password 'your_password'
# Note: the flag is --tls, NOT --starttls
11.4 Authenticated send — port 465 (SSL wrapper)
swaks \
--to [email protected] \
--from [email protected] \
--server mail.yourdomain.com \
--port 465 \
--tlsc \
--auth LOGIN \
--auth-user [email protected] \
--auth-password 'your_password'
# --tlsc = TLS-on-connect (wrapper mode), required for 465
11.5 IMAP test
curl -v \
--url "imaps://mail.yourdomain.com:993/INBOX" \
--user "[email protected]:your_password"
11.6 Mail client settings
| Setting | Value |
|---|---|
| Incoming (IMAP) | |
| Server | mail.yourdomain.com |
| Port | 993 |
| Security | SSL/TLS |
| Auth method | Normal password (not Kerberos, not OAuth) |
| Username | [email protected] (full address) |
| Outgoing (SMTP) | |
| Server | mail.yourdomain.com |
| Port | 587 |
| Security | STARTTLS |
| Auth method | Normal password |
| Username | [email protected] (full address) |
11.7 Verify DKIM/SPF/DMARC via Gmail
Send to Gmail → open message → three-dot menu → Show original. Look for:
Authentication-Results: mx.google.com;
dkim=pass [email protected] header.s=mail
spf=pass (google.com: domain of [email protected] designates
YOUR_IP as permitted sender)
dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=yourdomain.com
Issues We Hit & How We Fixed Them
These are the real-world errors encountered during this setup, in the order they appeared. Save yourself hours by recognizing them early.
DKIM key = value lines were appended to master.cf instead of main.cf. master.cf uses a strict column format, not key=value syntax.
Remove the DKIM milter lines from master.cf and put them in main.cf: milter_default_action, milter_protocol, smtpd_milters, non_smtpd_milters.
virtual_mailbox_limit defaults to ~51MB. If message_size_limit is larger, Postfix refuses to start the virtual delivery agent — all incoming mail sits deferred.
virtual_mailbox_limit = 0Postfix doesn't know which system UID/GID to write maildir files as. Mail accepts, enters the queue, then defers indefinitely.
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000Ubuntu 24.04 does not install rsyslog by default. Postfix logs are silently dropped — you have zero visibility into delivery failures.
sudo apt install rsyslog -y
sudo systemctl enable rsyslog
sudo systemctl start rsyslogPassword lookup succeeds but userdb lookup fails. passwd-file driver for userdb expects a full passwd-format file with UID/GID/home — but the file only has passwords.
Switch userdb to static driver — it assigns fixed values without looking up anything.
userdb {
driver = static
args = uid=vmail gid=vmail home=/var/vmail/%d/%n/
}Thunderbird auth method was not set to Normal password, or it cached a previous failed session. Postfix sees an unauthenticated client and rejects relay.
- Set auth method to Normal password (not Kerberos, not OAuth2)
- Use full email as username:
[email protected], not justuser - Restart Thunderbird (clears cached state)
Postfix's smtpd runs inside a chroot at /var/spool/postfix. The Unix socket path /run/opendkim/ doesn't exist inside the chroot. The socket is unreachable even if OpenDKIM is running.
Switch to an inet socket — it bypasses the chroot entirely.
Socket inet:12301@localhostsmtpd_milters = inet:localhost:12301
non_smtpd_milters = inet:localhost:12301When debugging Issue 7, postfix was added to the opendkim group. This made the private key's group have multiple users, which OpenDKIM considers insecure. It refuses to sign.
sudo chown opendkim:root /etc/opendkim/keys/yourdomain.com/mail.private
sudo chmod 600 /etc/opendkim/keys/yourdomain.com/mail.private
sudo gpasswd -d postfix opendkim
sudo systemctl restart opendkiminet_protocols = all caused Postfix to send over IPv6. The SPF record only covered IPv4 via mx. Cheap VPS IPv6 PTR often points to the provider's generic hostname, not your mail FQDN.
inet_protocols = ipv4The mail A record was proxied (orange cloud). Cloudflare proxies only HTTP/HTTPS. SMTP and IMAP connections are silently dropped or return wrong IPs.
Set mail A record to DNS only (grey cloud). Verify: dig A mail.yourdomain.com +short must return your actual VPS IP, not 104.x or 172.x.
The default Postfix install adds yourdomain.com to mydestination. Postfix prefers mydestination over virtual delivery — it tries local delivery, finds no system user user, and bounces with unknown user: "[email protected]".
Remove yourdomain.com from mydestination entirely. Keep only:
mydestination = $myhostname, localhost.$mydomain, localhostHardening
Core setup is operational. These aren't optional for production — prioritise by threat model.
13.1 Certbot renewal hook (do this today)
Let's Encrypt renews in ~60–89 days. Postfix and Dovecot must reload the new cert or TLS breaks.
sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-mail.sh
#!/bin/bash
systemctl reload postfix
systemctl reload dovecot
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-mail.sh
13.2 Fail2ban (within 24h of going live)
Public mail ports will be probed within hours. The logs already showed scanner IPs hitting port 465 during setup.
sudo apt install fail2ban -y
[postfix]
enabled = true
port = smtp,465,submission
logpath = /var/log/mail.log
[postfix-sasl]
enabled = true
port = smtp,465,submission,imap,imaps,pop3,pop3s
logpath = /var/log/mail.log
[dovecot]
enabled = true
port = pop3,pop3s,imap,imaps,submission,465,sieve
logpath = /var/log/mail.log
sudo systemctl restart fail2ban
sudo fail2ban-client status
13.3 Tighten DMARC policy
After 2–3 weeks of clean sending history, update the _dmarc TXT record:
v=DMARC1; p=quarantine; rua=mailto:[email protected]; pct=100
# Eventually: p=reject
13.4 Spam filtering (optional)
sudo apt install rspamd -y
# Configure as milter alongside OpenDKIM
# Skip for personal use — rspamd is non-trivial to tune
13.5 Monitor blacklists monthly
13.6 Disk space
- Set disk alerts at 70%, 85%, 95%
- Consider a dedicated volume mounted at
/var/vmailif sharing disk with other services - Log rotation is handled by
/etc/logrotate.d/rsyslog
Final Verdict
Time investment
What to do next
- Add the certbot deploy hook today (Section 13.1)
- Install fail2ban within 24 hours (Section 13.2)
- Monitor first 2 weeks — check Gmail "Show original" on every test send
- Tighten DMARC from
p=nonetop=quarantineafter clean sends