FreeBSD.software
Home/Blog/How to Set Up a FreeBSD Mail Server with Postfix and Dovecot
tutorial2026-03-29

How to Set Up a FreeBSD Mail Server with Postfix and Dovecot

Complete guide to building a mail server on FreeBSD with Postfix, Dovecot, and Rspamd. Covers installation, TLS, DKIM/SPF/DMARC, virtual domains, spam filtering, and Roundcube webmail.

# How to Set Up a FreeBSD Mail Server with Postfix and Dovecot

Running your own mail server gives you full control over email delivery, storage, privacy, and compliance. FreeBSD is an excellent platform for this -- its stable networking stack, jails for isolation, and ZFS for reliable mailbox storage provide a solid foundation that commercial mail providers build on daily. This guide walks through every component of a production mail stack: Postfix as the MTA, Dovecot for IMAP, Rspamd for spam filtering, OpenDKIM for message signing, and Roundcube for webmail.

This is not a quick-start tutorial. By the end, you will have a fully functional mail server that passes all major deliverability checks, encrypts connections with TLS, filters spam effectively, and provides webmail access for your users.

How Email Works: The Components

Before installing anything, understand the four components that make up a mail system:

**MTA (Mail Transfer Agent).** This is Postfix. It handles SMTP -- receiving inbound mail from the internet and relaying outbound mail to other servers. The MTA is responsible for routing, queuing, and delivering messages between servers.

**MDA (Mail Delivery Agent).** This is Dovecot's LDA or Postfix's virtual transport. It takes a message that the MTA has accepted and writes it to the correct user's mailbox on disk.

**MUA (Mail User Agent).** This is what users interact with -- Thunderbird, Apple Mail, Outlook, or a webmail interface like Roundcube. The MUA connects via IMAP (to read mail) and SMTP (to send mail).

**Spam filter.** Rspamd sits between the MTA and MDA, scoring messages and rejecting or tagging spam before it reaches the mailbox.

The flow for an inbound message: remote server connects to your Postfix on port 25, Postfix checks the recipient and passes the message through Rspamd via milter, Rspamd scores it and returns a verdict, Postfix hands the accepted message to Dovecot LDA which writes it to the user's Maildir, and the user reads it via IMAP or Roundcube.

The flow for an outbound message: user connects to Postfix on port 587 (submission) with SASL authentication provided by Dovecot, Postfix signs the message with DKIM via OpenDKIM milter, and delivers it to the recipient's MX server.

DNS Prerequisites

DNS configuration must be in place before you install anything. Incorrect DNS is the number one cause of deliverability failures.

You need these records, assuming your mail server is mail.example.com with IP 203.0.113.10:

**A record:**

mail.example.com. IN A 203.0.113.10

**MX record:**


example.com. IN MX 10 mail.example.com.

**PTR record (reverse DNS).** Contact your hosting provider to set the PTR record for 203.0.113.10 to resolve to mail.example.com. This is critical -- many mail servers reject connections where the PTR does not match the HELO hostname.

**SPF record:**


example.com. IN TXT "v=spf1 mx a:mail.example.com -all"

**DKIM record** (generated after OpenDKIM setup -- covered below).

**DMARC record:**


_dmarc.example.com. IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@example.com; ruf=mailto:dmarc-reports@example.com; adkim=s; aspf=s"

Start with p=none during testing, move to p=quarantine, then p=reject once you confirm everything works. The rua address receives aggregate reports that show who is sending mail using your domain.

Verify your DNS propagation before proceeding:

sh

dig MX example.com +short

dig A mail.example.com +short

dig TXT example.com +short

Installing Postfix

FreeBSD ships with Sendmail enabled by default. Disable it first, then install Postfix.

Disable Sendmail in /etc/rc.conf:

sh

sysrc sendmail_enable="NO"

sysrc sendmail_submit_enable="NO"

sysrc sendmail_outbound_enable="NO"

sysrc sendmail_msp_queue_enable="NO"

Stop Sendmail if it is running:

sh

service sendmail stop

Install Postfix:

sh

pkg install postfix

During installation, the package may ask if you want to set Postfix as the default MTA. Accept. You can also set it manually:

sh

sysrc postfix_enable="YES"

Configure the system to use Postfix for local mail delivery by editing /etc/mail/mailer.conf:


sendmail /usr/local/sbin/sendmail

mailq /usr/local/sbin/sendmail

newaliases /usr/local/sbin/sendmail

This points the system sendmail command to Postfix's compatibility binary, so cron jobs and other system tools that rely on sendmail will work correctly.

Postfix main.cf Configuration

The main configuration file is /usr/local/etc/postfix/main.cf. Here is a complete, production-ready configuration:

ini

# /usr/local/etc/postfix/main.cf

# Identity

myhostname = mail.example.com

mydomain = example.com

myorigin = $mydomain

mydestination = $myhostname, localhost.$mydomain, localhost

mynetworks = 127.0.0.0/8, [::1]/128

# Disable open relay

smtpd_relay_restrictions =

permit_mynetworks,

permit_sasl_authenticated,

defer_unauth_destination

# Virtual mailbox configuration

virtual_mailbox_domains = hash:/usr/local/etc/postfix/virtual_domains

virtual_mailbox_maps = hash:/usr/local/etc/postfix/virtual_mailboxes

virtual_alias_maps = hash:/usr/local/etc/postfix/virtual_aliases

virtual_transport = lmtp:unix:private/dovecot-lmtp

virtual_mailbox_base = /var/mail/vhosts

# Mailbox ownership

virtual_minimum_uid = 1000

virtual_uid_maps = static:1000

virtual_gid_maps = static:1000

# TLS — inbound connections

smtpd_tls_cert_file = /usr/local/etc/letsencrypt/live/mail.example.com/fullchain.pem

smtpd_tls_key_file = /usr/local/etc/letsencrypt/live/mail.example.com/privkey.pem

smtpd_tls_security_level = may

smtpd_tls_auth_only = yes

smtpd_tls_protocols = >=TLSv1.2

smtpd_tls_mandatory_protocols = >=TLSv1.2

smtpd_tls_mandatory_ciphers = high

smtpd_tls_loglevel = 1

# TLS — outbound connections

smtp_tls_security_level = may

smtp_tls_protocols = >=TLSv1.2

smtp_tls_loglevel = 1

# SASL authentication via Dovecot

smtpd_sasl_type = dovecot

smtpd_sasl_path = private/auth

smtpd_sasl_auth_enable = yes

smtpd_sasl_security_options = noanonymous

smtpd_sasl_local_domain = $mydomain

# Milters (OpenDKIM + Rspamd)

smtpd_milters = unix:/var/run/opendkim/opendkim.sock, inet:localhost:11332

non_smtpd_milters = unix:/var/run/opendkim/opendkim.sock, inet:localhost:11332

milter_default_action = accept

milter_protocol = 6

# Message size and queue limits

message_size_limit = 52428800

mailbox_size_limit = 0

# Submission port restrictions (set in master.cf)

# Logging

smtpd_helo_required = yes

disable_vrfy_command = yes

Now configure the submission port in /usr/local/etc/postfix/master.cf. Uncomment and modify the submission entry:

ini

# /usr/local/etc/postfix/master.cf (submission section)

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_relay_restrictions=permit_sasl_authenticated,reject

-o milter_macro_daemon_name=ORIGINATING

This enables port 587 for authenticated users to send mail, requiring TLS encryption.

Installing Dovecot

Dovecot provides IMAP access and SASL authentication for Postfix.

sh

pkg install dovecot

sysrc dovecot_enable="YES"

Dovecot Configuration

Dovecot's configuration on FreeBSD lives in /usr/local/etc/dovecot/. The main file includes everything from conf.d/. Here is the essential configuration.

Create /usr/local/etc/dovecot/dovecot.conf:

ini

# /usr/local/etc/dovecot/dovecot.conf

protocols = imap lmtp

listen = *, ::

# Logging

log_path = /var/log/dovecot.log

info_log_path = /var/log/dovecot-info.log

# SSL/TLS

ssl = required

ssl_cert =

ssl_key =

ssl_min_protocol = TLSv1.2

ssl_prefer_server_ciphers = yes

# Mail storage

mail_location = maildir:/var/mail/vhosts/%d/%n

mail_privileged_group = mail

first_valid_uid = 1000

# Authentication

auth_mechanisms = plain login

auth_verbose = yes

# Password database — file-based

passdb {

driver = passwd-file

args = scheme=BLF-CRYPT /usr/local/etc/dovecot/users

}

# User database

userdb {

driver = static

args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n

}

# LMTP service for Postfix delivery

service lmtp {

unix_listener /var/spool/postfix/private/dovecot-lmtp {

mode = 0600

user = postfix

group = postfix

}

}

# SASL auth service for Postfix

service auth {

unix_listener /var/spool/postfix/private/auth {

mode = 0660

user = postfix

group = postfix

}

}

# IMAP service

service imap-login {

inet_listener imap {

port = 143

}

inet_listener imaps {

port = 993

ssl = yes

}

}

# Mailbox auto-creation

namespace inbox {

inbox = yes

mailbox Sent {

auto = subscribe

special_use = \Sent

}

mailbox Drafts {

auto = subscribe

special_use = \Drafts

}

mailbox Trash {

auto = subscribe

special_use = \Trash

}

mailbox Junk {

auto = subscribe

special_use = \Junk

}

mailbox Archive {

auto = subscribe

special_use = \Archive

}

}

The < before the certificate path is intentional Dovecot syntax -- it tells Dovecot to read the file contents inline.

Virtual Domains and Users

This setup uses hash files for simplicity. For larger deployments with many domains and users, replace these with [PostgreSQL](/blog/postgresql-freebsd-setup/) queries.

Create the vmail system user that owns all mailboxes:

sh

pw groupadd -n vmail -g 1000

pw useradd -n vmail -u 1000 -g vmail -d /var/mail/vhosts -s /usr/sbin/nologin

mkdir -p /var/mail/vhosts/example.com

chown -R vmail:vmail /var/mail/vhosts

Create the virtual domains file:

sh

# /usr/local/etc/postfix/virtual_domains

example.com OK

Create the virtual mailboxes file:

sh

# /usr/local/etc/postfix/virtual_mailboxes

user@example.com example.com/user/

admin@example.com example.com/admin/

Create the virtual aliases file:

sh

# /usr/local/etc/postfix/virtual_aliases

postmaster@example.com admin@example.com

abuse@example.com admin@example.com

Generate the hash databases:

sh

postmap /usr/local/etc/postfix/virtual_domains

postmap /usr/local/etc/postfix/virtual_mailboxes

postmap /usr/local/etc/postfix/virtual_aliases

Create the Dovecot password file. Generate a hashed password with doveadm:

sh

doveadm pw -s BLF-CRYPT

Enter the password when prompted, then add the user to /usr/local/etc/dovecot/users:


user@example.com:{BLF-CRYPT}$2b$12$hashhere:::::::

admin@example.com:{BLF-CRYPT}$2b$12$hashhere:::::::

Set strict permissions:

sh

chmod 600 /usr/local/etc/dovecot/users

chown dovecot:dovecot /usr/local/etc/dovecot/users

PostgreSQL Backend (Alternative)

For deployments with many users or domains, configure Dovecot and Postfix to query [PostgreSQL](/blog/postgresql-freebsd-setup/) directly. In Dovecot, replace the passdb and userdb blocks with SQL drivers pointing to a mailboxes table. In Postfix, use virtual_mailbox_maps = pgsql:/usr/local/etc/postfix/pgsql-virtual-mailboxes.cf with connection credentials. This approach scales better and allows user management through a web interface or API.

TLS Certificates with Let's Encrypt

TLS is mandatory for any production mail server. Use [Let's Encrypt](/blog/lets-encrypt-freebsd/) to obtain free certificates.

Install Certbot:

sh

pkg install py311-certbot

Obtain a certificate for the mail hostname. If you already have [NGINX](/blog/nginx-freebsd-production-setup/) running on the server, use the webroot method:

sh

certbot certonly --webroot -w /usr/local/www/certbot \

-d mail.example.com

If no web server is running on port 80, use standalone mode:

sh

certbot certonly --standalone -d mail.example.com

Certificates are stored in /usr/local/etc/letsencrypt/live/mail.example.com/. The paths in the Postfix and Dovecot configurations above already point there.

Set up automatic renewal with a cron entry:

sh

# /etc/crontab

0 3 * * * root certbot renew --quiet --deploy-hook "service postfix reload && service dovecot reload"

The deploy hook reloads both services after renewal so they pick up the new certificates without downtime.

DKIM Signing with OpenDKIM

DKIM (DomainKeys Identified Mail) cryptographically signs outgoing messages so receiving servers can verify they were sent by your domain.

Install OpenDKIM:

sh

pkg install opendkim

sysrc milteropendkim_enable="YES"

Generate a DKIM key pair:

sh

mkdir -p /usr/local/etc/opendkim/keys/example.com

opendkim-genkey -b 2048 -d example.com -s mail \

-D /usr/local/etc/opendkim/keys/example.com

chown -R opendkim:opendkim /usr/local/etc/opendkim

This creates two files: mail.private (the signing key) and mail.txt (the DNS record).

Configure OpenDKIM in /usr/local/etc/opendkim.conf:

ini

# /usr/local/etc/opendkim.conf

Syslog yes

SyslogSuccess yes

LogWhy yes

Canonicalization relaxed/simple

Mode sv

SubDomains no

AutoRestart yes

AutoRestartRate 10/1M

Background yes

DNSTimeout 5

SignatureAlgorithm rsa-sha256

KeyTable /usr/local/etc/opendkim/key.table

SigningTable refile:/usr/local/etc/opendkim/signing.table

InternalHosts /usr/local/etc/opendkim/trusted.hosts

Socket local:/var/run/opendkim/opendkim.sock

PidFile /var/run/opendkim/opendkim.pid

UMask 007

UserID opendkim:opendkim

Create the supporting files:

sh

# /usr/local/etc/opendkim/key.table

mail._domainkey.example.com example.com:mail:/usr/local/etc/opendkim/keys/example.com/mail.private

# /usr/local/etc/opendkim/signing.table

*@example.com mail._domainkey.example.com

# /usr/local/etc/opendkim/trusted.hosts

127.0.0.1

::1

mail.example.com

example.com

Ensure the Postfix user can access the OpenDKIM socket:

sh

mkdir -p /var/run/opendkim

chown opendkim:opendkim /var/run/opendkim

pw groupmod opendkim -m postfix

Start OpenDKIM:

sh

service milter-opendkim start

Now publish the DKIM DNS record. View the generated record:

sh

cat /usr/local/etc/opendkim/keys/example.com/mail.txt

Add this as a TXT record for mail._domainkey.example.com in your DNS. The record looks like:


mail._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBg..."

Verify the record propagated:

sh

opendkim-testkey -d example.com -s mail -vvv

You should see key OK in the output.

SPF and DMARC Setup

SPF and DMARC records were covered in the DNS section above. Here is the verification workflow.

Test SPF:

sh

dig TXT example.com +short

You should see your SPF record: "v=spf1 mx a:mail.example.com -all". The -all means hard-fail -- any server not listed is unauthorized. During initial testing, use ~all (soft-fail) and move to -all once you confirm all legitimate sending sources are covered.

Test DMARC:

sh

dig TXT _dmarc.example.com +short

You should see your DMARC record. The p=reject policy tells receiving servers to reject messages that fail both SPF and DKIM. Start with p=none to collect reports, review them, then tighten the policy.

DMARC aggregate reports arrive as XML attachments. Use a tool like parsedmarc or a free service like dmarcian.com to parse and visualize them.

Spam Filtering with Rspamd

Rspamd is a fast, modern spam filter that replaces SpamAssassin. It integrates with Postfix via the milter protocol.

Install Rspamd and Redis (required for Rspamd's statistical classifier):

sh

pkg install rspamd redis

sysrc rspamd_enable="YES"

sysrc redis_enable="YES"

Start Redis:

sh

service redis start

Rspamd's configuration lives in /usr/local/etc/rspamd/. The default configuration is sensible. Customize with local overrides in /usr/local/etc/rspamd/local.d/.

Configure the milter proxy for Postfix integration. Create /usr/local/etc/rspamd/local.d/worker-proxy.inc:

ini

# /usr/local/etc/rspamd/local.d/worker-proxy.inc

bind_socket = "localhost:11332";

milter = yes;

timeout = 120s;

upstream "local" {

default = yes;

self_scan = yes;

}

Configure Redis as the backend for Bayes classification. Create /usr/local/etc/rspamd/local.d/classifier-bayes.conf:

ini

# /usr/local/etc/rspamd/local.d/classifier-bayes.conf

servers = "127.0.0.1";

backend = "redis";

autolearn = true;

Enable DKIM verification by Rspamd (it verifies inbound DKIM signatures by default). Create /usr/local/etc/rspamd/local.d/dkim_signing.conf if you want Rspamd to handle DKIM signing instead of OpenDKIM:

ini

# /usr/local/etc/rspamd/local.d/dkim_signing.conf

# Uncomment below if you want Rspamd to sign instead of OpenDKIM

# path = "/usr/local/etc/opendkim/keys/$domain/mail.private";

# selector = "mail";

# In that case, remove the opendkim milter from main.cf

Configure greylisting. Create /usr/local/etc/rspamd/local.d/greylist.conf:

ini

# /usr/local/etc/rspamd/local.d/greylist.conf

servers = "127.0.0.1";

expire = 86400;

timeout = 300;

Set the Rspamd web UI password:

sh

rspamadm pw

Enter a password. Copy the hash and create /usr/local/etc/rspamd/local.d/worker-controller.inc:

ini

# /usr/local/etc/rspamd/local.d/worker-controller.inc

password = "$2$hashfromrspamadmpw";

bind_socket = "127.0.0.1:11334";

Start Rspamd:

sh

service rspamd start

The Postfix main.cf already includes the Rspamd milter on inet:localhost:11332. Verify integration:

sh

service postfix reload

Rspamd Web UI

Rspamd includes a built-in web dashboard on port 11334. To expose it securely, proxy it through [NGINX](/blog/nginx-freebsd-production-setup/):

nginx

# /usr/local/etc/nginx/conf.d/rspamd.conf

server {

listen 443 ssl;

server_name rspamd.example.com;

ssl_certificate /usr/local/etc/letsencrypt/live/mail.example.com/fullchain.pem;

ssl_certificate_key /usr/local/etc/letsencrypt/live/mail.example.com/privkey.pem;

location / {

proxy_pass http://127.0.0.1:11334;

proxy_set_header Host $host;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

}

}

Access the dashboard at https://rspamd.example.com to view message statistics, train the Bayes classifier, and review quarantined messages.

Roundcube Webmail

Roundcube provides a clean, responsive webmail interface. It connects to Dovecot via IMAP and to Postfix via SMTP.

Install Roundcube and its PHP dependencies:

sh

pkg install roundcube-php83

pkg install php83-pdo_pgsql # if using PostgreSQL

pkg install php83-pdo_sqlite # if using SQLite for Roundcube's internal DB

Roundcube installs to /usr/local/www/roundcube/. Copy the sample configuration:

sh

cd /usr/local/www/roundcube/config

cp config.inc.php.sample config.inc.php

Edit /usr/local/www/roundcube/config/config.inc.php:

php
// /usr/local/www/roundcube/config/config.inc.php

$config['db_dsnw'] = 'sqlite:////var/db/roundcube/roundcube.db?mode=0640';

$config['imap_host'] = 'ssl://127.0.0.1:993';

$config['smtp_host'] = 'tls://127.0.0.1:587';

$config['smtp_user'] = '%u';

$config['smtp_pass'] = '%p';

$config['support_url'] = '';

$config['product_name'] = 'Webmail';

$config['des_key'] = 'generate-a-random-24-char-key!';

$config['plugins'] = ['archive', 'zipdownload', 'managesieve'];

$config['skin'] = 'elastic';

$config['language'] = 'en_US';

$config['imap_conn_options'] = [

'ssl' => [

'verify_peer' => true,

'verify_peer_name' => true,

],

];

Generate a random des_key:

sh

openssl rand -base64 24

Initialize the Roundcube database:

sh

mkdir -p /var/db/roundcube

chown www:www /var/db/roundcube

cd /usr/local/www/roundcube

php bin/initdb.sh --dir=SQL/sqlite

chown www:www /var/db/roundcube/roundcube.db

NGINX Configuration for Roundcube

Add a server block to serve Roundcube. This assumes you have [NGINX set up](/blog/nginx-freebsd-production-setup/) already:

nginx

# /usr/local/etc/nginx/conf.d/roundcube.conf

server {

listen 443 ssl;

server_name webmail.example.com;

ssl_certificate /usr/local/etc/letsencrypt/live/mail.example.com/fullchain.pem;

ssl_certificate_key /usr/local/etc/letsencrypt/live/mail.example.com/privkey.pem;

root /usr/local/www/roundcube;

index index.php;

location / {

try_files $uri $uri/ /index.php?$args;

}

location ~ \.php$ {

fastcgi_pass unix:/var/run/php-fpm.sock;

fastcgi_index index.php;

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

include fastcgi_params;

}

# Block access to sensitive files

location ~ ^/(config|temp|logs)/ {

deny all;

}

location ~ /\. {

deny all;

}

}

server {

listen 80;

server_name webmail.example.com;

return 301 https://$server_name$request_uri;

}

Reload NGINX:

sh

nginx -t && service nginx reload

Access Roundcube at https://webmail.example.com and log in with the email address and password you configured in the Dovecot users file.

Firewall Rules

Your [PF firewall](/blog/pf-firewall-freebsd/) must allow mail traffic. Add these rules to /etc/pf.conf:


# Mail server ports

pass in on egress proto tcp to port { 25, 143, 465, 587, 993 }

pass out on egress proto tcp to port { 25, 53, 443 }

pass out on egress proto udp to port 53

Port 25 is inbound SMTP, 143 is IMAP (STARTTLS), 465 is implicit TLS SMTP (legacy but still used), 587 is submission, and 993 is IMAPS. Outbound 25 allows Postfix to deliver mail to other servers, 53 for DNS lookups, and 443 for certificate renewal.

Reload PF:

sh

pfctl -f /etc/pf.conf

Testing

Send a Test Email with swaks

swaks (Swiss Army Knife for SMTP) is the best tool for testing mail server functionality.

sh

pkg install swaks

Test inbound delivery:

sh

swaks --to user@example.com --from test@gmail.com \

--server mail.example.com --port 25

Test authenticated submission:

sh

swaks --to external@gmail.com --from user@example.com \

--server mail.example.com --port 587 \

--auth LOGIN --auth-user user@example.com \

--auth-password 'yourpassword' --tls

Check Mail Logs

Postfix logs to /var/log/maillog by default. Watch it in real time during testing:

sh

tail -f /var/log/maillog

Look for status=sent for successful deliveries and status=bounced or status=deferred for problems.

Mail-Tester.com Checklist

Send a test message to the address provided by [mail-tester.com](https://www.mail-tester.com/) and verify your score. A production-ready server should score 9/10 or higher. Common deductions:

- Missing PTR record (-1 to -2 points)

- Missing DKIM signature (-1 to -2 points)

- Missing DMARC record (-0.5 to -1 point)

- SPF soft-fail instead of hard-fail (-0.5 points)

- HTML-only email without text/plain part (-0.5 points)

Verify TLS

Test that TLS is working on all services:

sh

openssl s_client -connect mail.example.com:993 -quiet

openssl s_client -connect mail.example.com:587 -starttls smtp -quiet

openssl s_client -connect mail.example.com:25 -starttls smtp -quiet

Each should show the certificate chain and Verify return code: 0 (ok).

Maintenance and Monitoring

Log Rotation

FreeBSD's newsyslog handles log rotation. Add entries for mail logs in /etc/newsyslog.conf:


/var/log/maillog 640 7 1000 * JC

/var/log/dovecot.log 640 7 1000 * JC

/var/log/dovecot-info.log 640 7 1000 * JC

Queue Monitoring

Check the Postfix mail queue regularly:

sh

postqueue -p # show queue contents

postqueue -f # flush the queue (retry all deferred)

postsuper -d ALL # delete all queued messages (use with caution)

A healthy server should have an empty or near-empty queue. If messages are piling up, check /var/log/maillog for the root cause -- usually DNS resolution failures, remote server rejections, or TLS handshake errors.

Backup

Back up these critical files and directories:

- /usr/local/etc/postfix/ -- all Postfix configuration

- /usr/local/etc/dovecot/ -- Dovecot configuration and user database

- /usr/local/etc/opendkim/ -- DKIM keys (losing these means regenerating keys and updating DNS)

- /usr/local/etc/rspamd/local.d/ -- Rspamd customizations

- /var/mail/vhosts/ -- all user mailboxes

- /usr/local/etc/letsencrypt/ -- TLS certificates and account keys

If you are using ZFS (and you should be), create a dataset for /var/mail/vhosts and snapshot it regularly:

sh

zfs create zroot/var/mail

zfs set compression=lz4 zroot/var/mail

zfs snapshot zroot/var/mail@$(date +%Y%m%d)

Certificate Renewal Monitoring

Certbot handles automatic renewal, but verify it is working. Check certificate expiry:

sh

openssl s_client -connect mail.example.com:993 -servername mail.example.com 2>/dev/null | \

openssl x509 -noout -dates

Set up a cron job or monitoring check that alerts if the certificate expires within 14 days.

Service Health Checks

Add these to your [monitoring system](/blog/freebsd-server-monitoring-guide/):

sh

# Check Postfix is listening

nc -z mail.example.com 25 && echo "SMTP OK" || echo "SMTP FAIL"

# Check Dovecot IMAP

nc -z mail.example.com 993 && echo "IMAPS OK" || echo "IMAPS FAIL"

# Check Rspamd

curl -s http://127.0.0.1:11334/stat | head -1

Frequently Asked Questions

Can I run a mail server on a shared VPS?

It depends on the provider. Many VPS providers block outbound port 25 by default to prevent spam. Check with your provider -- some will unblock it on request after verifying your use case. Vultr, Hetzner, and OVH generally allow mail servers. Cheap providers that oversell their IP ranges often have IPs on blocklists already, which will cause deliverability problems regardless of how well you configure your server.

How do I add a new email user?

Three steps. First, add the address to /usr/local/etc/postfix/virtual_mailboxes and run postmap on it. Second, generate a password hash with doveadm pw -s BLF-CRYPT and add the entry to /usr/local/etc/dovecot/users. Third, create the maildir: mkdir -p /var/mail/vhosts/example.com/newuser && chown -R vmail:vmail /var/mail/vhosts/example.com/newuser. No service restart required -- Dovecot and Postfix pick up the changes from the hash files immediately.

Why are my emails going to spam at Gmail/Outlook?

Check these in order: (1) PTR record must match your HELO hostname, (2) SPF must pass, (3) DKIM must pass, (4) DMARC must pass, (5) your IP must not be on any blocklists (check at mxtoolbox.com/blacklists.aspx), (6) send a test to mail-tester.com and fix everything it flags. New mail server IPs have no reputation -- start by sending low volume to contacts who will engage with (open, reply to) your messages. Building IP reputation takes weeks to months.

Should I use Rspamd or SpamAssassin?

Rspamd. It is faster, uses less memory, has a built-in web interface, handles DKIM/SPF/DMARC checks natively, supports neural network classification, integrates cleanly with Postfix via the milter protocol, and is actively developed. SpamAssassin is still functional but has higher resource requirements and a more complex integration path.

How do I handle multiple domains?

Add each domain to /usr/local/etc/postfix/virtual_domains, add the mailboxes for that domain to /usr/local/etc/postfix/virtual_mailboxes, run postmap on both files, create the maildir directory under /var/mail/vhosts/newdomain.com/, and add Dovecot users for the new domain. For DKIM, generate a separate key pair for each domain and add entries to the OpenDKIM key.table and signing.table files. Add separate SPF, DKIM, and DMARC DNS records for each domain.

What is the difference between ports 25, 465, and 587?

Port 25 is for server-to-server SMTP (MTA to MTA). Your server listens on port 25 to receive inbound mail from the internet. Port 587 (submission) is for authenticated clients (MUA to MTA) -- your users connect here to send mail, using STARTTLS for encryption. Port 465 (SMTPS) is the implicit TLS variant of submission -- the connection is encrypted from the start without STARTTLS negotiation. Modern best practice is to support both 587 and 465 for client submission and keep port 25 for server-to-server traffic only.

Conclusion

You now have a complete, production-grade mail server on FreeBSD: Postfix handling SMTP, Dovecot serving IMAP, OpenDKIM signing outbound messages, Rspamd filtering spam, TLS encrypting all connections, and Roundcube providing webmail access. The DNS records -- SPF, DKIM, DMARC -- protect your domain from spoofing and establish deliverability trust.

The most common mistake with self-hosted mail is neglecting ongoing maintenance. Monitor your mail queue daily, keep your blocklist status clean, review Rspamd statistics weekly, rotate your DKIM keys annually, and keep all packages updated. A well-maintained FreeBSD mail server will run for years with minimal intervention -- which is exactly what FreeBSD was built for.