Skip to main content
โšก Calmops

Email Server Administration: Postfix, Dovecot, and TLS

Introduction

Running your own email server gives you full control over your mail infrastructure. This guide covers the operational side of a Postfix (SMTP) + Dovecot (IMAP/POP3) + PostgreSQL stack โ€” the configuration files, service management, TLS setup, and debugging techniques you’ll use day-to-day.

Stack: Postfix (send/receive mail) + Dovecot (client access) + PostgreSQL (user/domain storage)

Architecture Overview

Internet โ†’ Postfix (port 25/465) โ†’ Mailbox storage
                                         โ†‘
Client (Thunderbird, Apple Mail) โ†’ Dovecot (port 143/993) โ†’ Mailbox storage
  • Postfix handles SMTP: receiving mail from the internet and sending outbound mail
  • Dovecot handles IMAP/POP3: letting mail clients read stored mail
  • PostgreSQL stores virtual users, domains, and aliases

Key Configuration Files

# Postfix
/etc/postfix/main.cf          # main configuration
/etc/postfix/master.cf        # service definitions (ports, protocols)
/etc/postfix/virtual          # virtual alias maps
/etc/postfix/vmailbox         # virtual mailbox maps

# Dovecot
/etc/dovecot/dovecot.conf     # main config (includes conf.d/)
/etc/dovecot/conf.d/          # modular config directory
/etc/dovecot/conf.d/10-auth.conf
/etc/dovecot/conf.d/10-mail.conf
/etc/dovecot/conf.d/10-ssl.conf
/etc/dovecot/conf.d/20-imap.conf

Service Management

# Start / stop / restart
sudo service postfix start
sudo service postfix stop
sudo service postfix restart
sudo service postfix reload    # reload config without dropping connections

sudo service dovecot start
sudo service dovecot restart

sudo service postgresql start

# Check status
sudo service postfix status
sudo service dovecot status
sudo systemctl status postfix  # systemd equivalent

Monitoring and Logs

# Real-time mail log (most useful for debugging)
sudo tail -f /var/log/mail.log

# Error log
sudo tail -f /var/log/mail.err

# systemd journal (alternative)
sudo journalctl -u postfix -f
sudo journalctl -u dovecot -f
sudo journalctl -f              # all services

# Check what's listening
sudo ss -ntlp
# Expected ports:
# 25   - SMTP (unencrypted, server-to-server)
# 465  - SMTPS (TLS, client submission)
# 587  - Submission (STARTTLS, client submission)
# 143  - IMAP (unencrypted)
# 993  - IMAPS (TLS)
# 110  - POP3 (unencrypted)
# 995  - POP3S (TLS)

Port Reference

Port Protocol Encryption Use
25 SMTP None Server-to-server mail transfer
465 SMTPS TLS (implicit) Client mail submission (legacy)
587 Submission STARTTLS Client mail submission (modern)
143 IMAP None Client mail access
993 IMAPS TLS (implicit) Client mail access (secure)
110 POP3 None Client mail download
995 POP3S TLS (implicit) Client mail download (secure)

Recommendation: Use ports 993 (IMAPS) and 587 (Submission with STARTTLS) for clients. Never expose 143, 110, or 25 to clients without TLS.

IMAP vs POP3

Feature IMAP POP3
Mail storage Server (synced) Downloaded to client
Multiple devices Yes โ€” all devices see same state No โ€” mail removed from server
Offline access Partial (cached) Full (downloaded)
Server storage Higher Lower
Recommended Yes Only for single-device, low-storage scenarios

Use IMAP. POP3 is only useful if you have very limited server storage and access mail from a single device.

Testing SMTP Connectivity

# Test SMTP connection with netcat
nc mail.example.com 25

# You should see the SMTP banner:
# 220 mail.example.com ESMTP Postfix

# Manual SMTP session
EHLO testclient.example.com
# Server responds with capabilities:
# 250-mail.example.com
# 250-PIPELINING
# 250-SIZE 10240000
# 250-STARTTLS
# 250-AUTH PLAIN LOGIN
# 250 8BITMIME

# Test with openssl (for TLS)
openssl s_client -connect mail.example.com:465
openssl s_client -starttls smtp -connect mail.example.com:587

Sending Test Email from Command Line

# Using mail command
echo "Test body" | mail -s "Test subject" [email protected]

# Using sendmail directly
sendmail -v [email protected] << EOF
Subject: Test
From: [email protected]

Test message body
EOF

# Using swaks (Swiss Army Knife for SMTP โ€” install: apt install swaks)
swaks --to [email protected] --from [email protected] --server localhost
swaks --to [email protected] --server mail.example.com --port 587 \
      --auth LOGIN --auth-user [email protected] --auth-password secret \
      --tls

Enabling TLS with Let’s Encrypt

Postfix TLS Configuration

sudo nano /etc/postfix/main.cf
# TLS for incoming connections (smtpd = server)
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_security_level = may    # offer TLS but don't require it for port 25
smtpd_tls_loglevel  = 1
smtpd_tls_received_header = yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache

# TLS for outgoing connections (smtp = client)
smtp_use_tls        = yes
smtp_tls_security_level = may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# Modern TLS settings
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_protocols  = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

Dovecot TLS Configuration

sudo nano /etc/dovecot/conf.d/10-ssl.conf
# Require TLS for all connections
ssl = required

# Certificate files
ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_key  = </etc/letsencrypt/live/mail.example.com/privkey.pem

# Modern TLS settings
ssl_min_protocol = TLSv1.2
ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
ssl_prefer_server_ciphers = yes

Reload After TLS Changes

sudo service postfix reload
sudo service dovecot reload

# Verify TLS is working
openssl s_client -connect mail.example.com:993 -quiet

Renewing Let’s Encrypt Certificates

# Test renewal
sudo certbot renew --dry-run

# Actual renewal (runs automatically via cron/systemd timer)
sudo certbot renew

# Reload mail services after renewal
# Add to /etc/letsencrypt/renewal-hooks/deploy/reload-mail.sh:
#!/bin/bash
service postfix reload
service dovecot reload

Common Postfix main.cf Settings

# Identity
myhostname = mail.example.com
mydomain   = example.com
myorigin   = $mydomain

# Networks allowed to relay
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128

# Virtual domains and mailboxes
virtual_mailbox_domains = pgsql:/etc/postfix/pgsql-virtual-mailbox-domains.cf
virtual_mailbox_maps    = pgsql:/etc/postfix/pgsql-virtual-mailbox-maps.cf
virtual_alias_maps      = pgsql:/etc/postfix/pgsql-virtual-alias-maps.cf

# Mailbox storage
virtual_transport = lmtp:unix:private/dovecot-lmtp

# Size limits
message_size_limit = 52428800    # 50MB max message size
mailbox_size_limit = 0           # no mailbox size limit

# Anti-spam
smtpd_recipient_restrictions =
    permit_mynetworks,
    permit_sasl_authenticated,
    reject_unauth_destination

Debugging Common Issues

# Mail not being delivered โ€” check the queue
mailq                           # show mail queue
postqueue -p                    # same
postqueue -f                    # flush queue (retry delivery)
postsuper -d ALL                # delete all queued mail (careful!)

# Check if a specific domain resolves
dig MX example.com
dig A mail.example.com

# Test authentication
doveadm auth test [email protected] password

# Check Postfix config for errors
postfix check
postconf -n                     # show non-default settings

# Verify DKIM/SPF/DMARC (use external tools)
# https://mxtoolbox.com/
# https://mail-tester.com/

Resources

Comments