A mail server is a software system that sends, receives, and stores email using protocols like SMTP, IMAP, and POP3. It acts as the backbone of email communication, routing messages between clients such as Outlook, Gmail, or Apple Mail.
A practical command reference for building and troubleshooting a Linux mail server using Postfix, Dovecot, MySQL/MariaDB, Rspamd, Redis, Nginx and TLS.
Examples use:
Mail host: mail.example.com
Mail domain: example.com
Replace placeholders before running commands.
Safety: Read destructive commands carefully. Commands such as
rm,postsuper,redis-cli FLUSHALL, databaseDROP, firewall changes, and service removal can cause data loss or downtime.
cat /etc/os-release
# Shows Ubuntu/Debian release information.
uname -a
# Shows kernel, architecture and build information.hostnamectl
# Shows the configured system hostname.
hostname -f
# Shows the fully qualified hostname.A mail server should normally have a stable FQDN such as:
mail.example.com
uptime
# Shows uptime, load averages and logged-in users.
free -h
# Shows RAM and swap usage.
df -h
# Shows filesystem capacity and free space.
top
# Interactive process/resource view.sudo du -xhd1 / | sort -h
# Shows which top-level directories use the most disk.
sudo du -xhd1 /var | sort -h
# Useful for finding mail/log/database growth.systemctl --failed
# Lists failed systemd units.
systemctl is-enabled postfix dovecot rspamd redis-server nginx
# Confirms whether important services will start after reboot.
systemctl is-active postfix dovecot rspamd redis-server nginx
# Confirms whether important services are currently running.
dig +short A mail.example.com
# Shows the IPv4 address for the mail host.dig +short MX example.com
# Shows where mail for the domain is delivered.dig +short TXT example.com
# Shows TXT records, including SPF.dig +short TXT mail._domainkey.example.com
# Queries the public DKIM key for selector "mail".dig +short TXT _dmarc.example.com
# Shows the DMARC policy.dig -x <SERVER_PUBLIC_IP> +short
# Shows the PTR/reverse-DNS hostname for the server IP.dig TXT example.com @1.1.1.1
# Query Cloudflare DNS.
dig TXT example.com @8.8.8.8
# Query Google DNS.Useful when a DNS change appears correct locally but is not visible everywhere.
sudo ss -lntup
# Lists TCP/UDP sockets and the processes listening on them.sudo ss -lntp | grep ':25'
# Shows what is listening on SMTP port 25.
sudo ss -lntp | grep ':587'
# Shows submission service.
sudo ss -lntp | grep ':993'
# Shows IMAPS if enabled.nc -vz 127.0.0.1 11334
# Tests whether Rspamd controller is reachable locally.
nc -vz 127.0.0.1 3306
# Tests whether MariaDB is listening locally.
postconf -n
# Shows non-default Postfix configuration.postconf -n | grep -iE 'sasl|tls|relay|recipient|virtual|milter|rate'
# Quickly reviews authentication, TLS, relay, virtual-mailbox, milter and rate settings.sudo postfix check
# Validates Postfix configuration, permissions and required files.sudo postfix reload
# Reloads configuration without a full service restart.systemctl status postfix --no-pager
# Shows service state and recent logs.postconf -M
# Lists Postfix master.cf services.
postconf -P
# Shows service-specific master.cf parameter overrides.postconf
# Shows all Postfix parameters, including defaults.postqueue -p
# Shows the current Postfix queue.
mailq
# Equivalent queue inspection command.sudo postqueue -f
# Requests delivery retries for queued mail.Do not use destructive queue deletion commands unless you have identified the affected messages.
sudo postcat -q <QUEUE_ID>
# Displays the contents/metadata of a queued message.If the queue ID no longer exists, the message has already left the queue.
postqueue -p | grep -E '^[A-F0-9]{10,}'
# Extracts queue IDs from the queue listing.
postmap -q user@example.com mysql:/path/to/mysql-virtual-users.cf
# Asks Postfix's MySQL map whether the mailbox exists.postmap -q alias@example.com mysql:/path/to/mysql-virtual-aliases.cf
# Shows the destination returned for the alias.postmap -q example.com mysql:/path/to/mysql-virtual-domains.cf
# Shows whether the domain lookup returns a valid result.These are among the most useful commands when Postfix says a recipient/domain cannot be found.
sudo journalctl -u postfix --since "1 hour ago" --no-pager
# Shows recent Postfix systemd logs.sudo journalctl -u postfix -f
# Follows Postfix events as they happen.sudo tail -f /var/log/mail.log
# Useful on systems that write mail events to mail.log.sudo grep 'QUEUE_ID' /var/log/mail.log
# Tracks one message through Postfix using its queue ID.sudo grep -iE 'deferred|bounced|reject|warning|error' /var/log/mail.log | tail -100
# Quickly finds common delivery/failure events.
sudo doveconf -n
# Shows the effective Dovecot configuration.doveconf -n | grep auth_username_format
# Shows username transformation rules.
doveconf -n | grep recipient_delimiter
# Shows address-extension delimiter settings.doveadm user user@example.com
# Queries userdb for a mailbox.
doveadm user user@example.com -x protocol=imap
# Tests the userdb lookup in IMAP context.
doveadm user user@example.com -x protocol=lmtp
# Tests the userdb lookup in LMTP context.This comparison is extremely useful when IMAP works but LMTP says:
User doesn't exist
doveadm auth test user@example.com
# Tests authentication. Enter the password only when prompted.doveadm user '*'
# Lists users if the configured userdb supports iteration.doveadm user -f home user@example.com
# Shows the resolved home/mailbox path.doveadm mailbox status -u user@example.com INBOX
# Shows INBOX status information.doveadm search -u user@example.com mailbox INBOX ALL
# Lists matching message identifiers in the mailbox.sudo journalctl -u dovecot --since "1 hour ago" --no-pager
# Shows recent Dovecot logs.sudo journalctl -u dovecot -f
# Follows Dovecot logs live.nano /etc/dovecot/conf.d/10-logging.conf
mail_debug = yes
auth_debug = yes
auth_verbose = yes
# Add these lines, you can see all the mail and auth logs
postconf virtual_transport
# Shows the virtual delivery transport.
postconf | grep dovecot-lmtp
# Searches for Dovecot LMTP-related settings.A typical setup uses:
virtual_transport = lmtp:unix:private/lmtp
sudo ls -l /var/spool/postfix/private/
# Look for the LMTP/auth sockets used by the installation.doveadm exec lmtp
# Starts a local LMTP test session.If Postfix reports:
550 5.1.1 User doesn't exist
compare:
doveadm user user@example.com
doveadm user user@example.com -x protocol=imap
doveadm user user@example.com -x protocol=lmtpIf only LMTP fails, check protocol-specific username formatting.
systemctl status mariadb --no-pager
# Shows database service state.sudo mariadb
# Opens a local MariaDB administrative shell when socket authentication is configured.Or:
mariadb -u <DB_USER> -p
# Prompts for a database password.SHOW DATABASES;USE mailserver;SHOW TABLES;DESCRIBE virtual_users;
DESCRIBE virtual_aliases;
DESCRIBE virtual_domains;SELECT COUNT(*) FROM virtual_users;SELECT COUNT(*) FROM virtual_aliases;SELECT email, active
FROM virtual_users
WHERE email = 'user@example.com';SELECT source, destination, active
FROM virtual_aliases
WHERE source = 'alias@example.com';SHOW GRANTS FOR 'mailuser'@'localhost';Use a least-privileged application account. Do not use database root credentials in Postfix/Dovecot.
systemctl status opendkim --no-pager
# Shows OpenDKIM service state.sudo journalctl -u opendkim --since "1 hour ago" --no-pager
# Shows DKIM signing/verification events.sudo opendkim-testkey -d example.com -s mail -vvv
# Verifies selector/DNS/key configuration for the selected domain.sudo grep -RniE 'KeyTable|SigningTable|Selector|Domain' /etc/opendkim 2>/dev/null
# Finds common OpenDKIM configuration references.sudo ss -lxnp | grep opendkim
# Shows whether an OpenDKIM UNIX socket is listening.postconf -n | grep -i milter
# Shows the active milter integration.
rspamadm --version
# Shows installed Rspamd version.sudo rspamadm configtest
# Checks configuration syntax.rspamadm configdump classifier
# Shows Bayes/classifier settings.rspamadm configdump dkim_signing
# Shows DKIM signing configuration.rspamadm configdump ratelimit
# Shows current Rspamd rate-limit configuration.systemctl status rspamd --no-pager
# Shows Rspamd service state.sudo journalctl -u rspamd --since "1 hour ago" --no-pager
# Shows Rspamd logs.sudo journalctl -u rspamd -f
# Follows Rspamd logs live.rspamc stat
# Shows scan, spam/ham and Bayes learning statistics.
sudo ss -ltnp | grep 11334
# Confirms the controller is listening; ideally it should be localhost-only.curl -s http://127.0.0.1:11334/ | head
# Tests whether the controller serves the WebUI locally.Do not use curl -I as the sole test for Rspamd controller resources because the controller may respond differently to HEAD requests.
curl -s -o /dev/null -w '%{http_code}\n' \
https://mail.example.com/rspamd/js/main.js
# Checks whether a reverse-proxy static asset is reachable.Unauthenticated API requests may return:
401 Unauthorized
That is expected for protected endpoints.
Keep controller ports private and expose the UI through authenticated HTTPS if needed.
redis-cli PING
# Basic connectivity test.Expected:
PONG
redis-cli DBSIZE
# Shows the number of keys in the current Redis database.redis-cli INFO keyspace
# Shows keys, expirations and TTL information.redis-cli --scan | head -50
# Iterates keys without blocking Redis like KEYS * can.Do not use destructive commands on production Redis unless you are intentionally deleting the stored state.
sudo nginx -t
# Validates syntax before reloading.sudo nginx -T
# Prints the complete merged Nginx configuration, including included files.This is useful when a configuration appears not to be in /etc/nginx/nginx.conf because it may be included from sites-enabled.
sudo grep -Rni 'server_name mail.example.com' /etc/nginx 2>/dev/null
# Locates the server block.sudo systemctl reload nginx
# Reloads Nginx without dropping established connections.systemctl status nginx --no-pager
# Shows Nginx service state.sudo tail -n 100 /var/log/nginx/error.log
# Shows recent errors.
sudo tail -n 100 /var/log/nginx/access.log
# Shows recent requests.
openssl s_client -connect mail.example.com:587 -starttls smtp
# Tests STARTTLS and shows certificate details.openssl s_client -connect mail.example.com:465
# Tests implicit TLS.openssl s_client -connect mail.example.com:993
# Tests IMAP over TLS.echo | openssl s_client -connect mail.example.com:443 2>/dev/null \
| openssl x509 -noout -dates -subject -issuer
# Shows certificate validity and identity.
sudo fail2ban-client status
# Lists active jails.sudo fail2ban-client status sshd
# Shows status, failed attempts and banned IPs for the SSH jail.sudo fail2ban-client set sshd unbanip <IP>
# Removes an IP from the specified jail.Only unban an address you recognize.
sudo ufw status verbose
# Shows firewall policy and allowed ports.sudo ss -lntup
# Cross-checks actual listeners against firewall rules.Do not open administrative services to the Internet unless needed.
sudo postfix reload
# Preferred after configuration-only changes.sudo systemctl reload dovecot
# Reloads Dovecot configuration.sudo systemctl reload rspamd
# Reloads Rspamd configuration.sudo nginx -t && sudo systemctl reload nginx
# Validates before reloading.Use full restarts when a reload is insufficient or when the service documentation explicitly requires one.
gzip -t mailserver.sql.gz
# Verifies the gzip archive is readable.sha256sum -c SHA256SUMS
# Verifies backup-file integrity.Do not include the checksum file itself in SHA256SUMS.
tar --zstd -tf mail-vhosts.tar.zst | head -50
# Lists files in a zstd-compressed tar archive.systemctl status example-mail-backup.service --no-pager
# Shows an example systemd backup service.
systemctl list-timers --all | grep backup
# Shows scheduled backup timers.
systemctl is-active postfix dovecot rspamd redis-server nginx
# Quickly checks whether the main services are active.postqueue -p
# Check mail queue.rspamc stat
# Check Rspamd statistics.redis-cli PING
# Check Redis connectivity.sudo postfix check && sudo rspamadm configtest && sudo nginx -t
# Validate several major configuration layers at once.
When a message fails, do not change configuration immediately.
sudo tail -f /var/log/mail.logSend a test message.
Example:
2A3BC4D5EF
sudo grep '2A3BC4D5EF' /var/log/mail.logTypical stages:
SMTP reception
-> cleanup
-> milter/filter
-> queue manager
-> transport
-> remote SMTP server
-> delivery response
Examples:
550 5.1.1
Often means recipient/user does not exist.
550 5.7.26
Often points toward authentication/policy rejection.
451 / 421
Often indicates temporary failure/defer/retry behavior.
Do not infer the exact cause from a status code alone; read the complete server response.
For almost every mail problem, use this order:
1. DNS
2. Network/port
3. TLS
4. SMTP authentication
5. Postfix configuration
6. SQL lookup
7. Dovecot userdb/authentication
8. LMTP
9. Rspamd/OpenDKIM
10. Remote-provider rejection
This prevents changing several unrelated services at once.
Change one layer at a time, validate it, then move to the next layer.
Always keep a known-good backup before major configuration changes.