FreeBSD.software
Home/Guides/FreeBSD Mail Server Administration Guide
guide·2026-04-09·10 min read

FreeBSD Mail Server Administration Guide

FreeBSD mail server setup with Postfix, Dovecot, and rspamd: virtual domains, TLS encryption, DKIM/SPF/DMARC authentication, webmail, and production hardening.

FreeBSD Mail Server Administration Guide

Running your own mail server on FreeBSD gives you complete control over your email infrastructure -- no third-party scanning your messages, no storage limits, no monthly fees per mailbox. The trade-off is operational complexity. Email has more moving parts than almost any other server role: MTA, IMAP, spam filtering, TLS, DNS records, authentication protocols, and deliverability reputation.

This guide builds a production mail server on FreeBSD using Postfix (MTA), Dovecot (IMAP/POP3), and rspamd (spam filtering and DKIM signing). It covers virtual domain hosting, TLS encryption, DKIM/SPF/DMARC, and webmail.

Architecture Overview

The mail stack:

| Component | Software | Role |

|---|---|---|

| MTA | Postfix | Sends and receives email via SMTP |

| IMAP/POP3 | Dovecot | Serves mailboxes to clients |

| Spam filter | rspamd | Filters spam, signs DKIM, checks SPF |

| Webmail | Roundcube | Browser-based email access |

| TLS | Let's Encrypt | Certificates for all services |

| DNS | Your DNS | MX, SPF, DKIM, DMARC records |

Mail flow:

  1. Incoming: Internet -> Postfix (port 25) -> rspamd -> Dovecot (delivery)
  2. Outgoing: Client -> Postfix (port 587) -> rspamd (DKIM sign) -> Internet
  3. Reading: Client -> Dovecot (port 993 IMAP / 995 POP3)

Prerequisites

Before installing anything, set up DNS records for your mail domain. These are essential for deliverability.

DNS Records

shell
; MX record -- tells the world where to deliver mail example.com. IN MX 10 mail.example.com. ; A record for the mail server mail.example.com. IN A 203.0.113.10 ; AAAA record (if you have IPv6) mail.example.com. IN AAAA 2001:db8::10 ; SPF record -- authorizes your server to send mail example.com. IN TXT "v=spf1 mx -all" ; DMARC record -- policy for failed authentication _dmarc.example.com. IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@example.com" ; PTR record (reverse DNS) -- set this with your hosting provider ; 10.113.0.203.in-addr.arpa. IN PTR mail.example.com.

The PTR record is critical. Many mail servers reject or flag email from IPs without a matching PTR record. Contact your hosting provider to set this.

DKIM records will be added after generating keys.

Installing the Stack

sh
pkg install postfix dovecot rspamd redis roundcube-php83 \ php83-imap php83-mbstring php83-intl php83-pdo_pgsql \ nginx acme.sh

Disable sendmail (FreeBSD's default MTA):

sh
sysrc sendmail_enable="NONE" sysrc sendmail_submit_enable="NO" sysrc sendmail_outbound_enable="NO" sysrc sendmail_msp_queue_enable="NO" service sendmail onestop

Enable services:

sh
sysrc postfix_enable="YES" sysrc dovecot_enable="YES" sysrc rspamd_enable="YES" sysrc redis_enable="YES" sysrc nginx_enable="YES" sysrc php_fpm_enable="YES"

Set Postfix as the system mailer:

sh
cat >> /etc/mail/mailer.conf << 'EOF' sendmail /usr/local/sbin/sendmail mailq /usr/local/bin/mailq newaliases /usr/local/bin/newaliases EOF

TLS Certificates

Get certificates from Let's Encrypt before configuring services:

sh
acme.sh --issue -d mail.example.com --standalone # Install certificates where services can find them mkdir -p /usr/local/etc/ssl/mail acme.sh --install-cert -d mail.example.com \ --key-file /usr/local/etc/ssl/mail/key.pem \ --fullchain-file /usr/local/etc/ssl/mail/fullchain.pem \ --reloadcmd "service postfix reload; service dovecot reload; service nginx reload"

Postfix Configuration

Main Configuration

sh
cat > /usr/local/etc/postfix/main.cf << 'EOF' # Basic settings myhostname = mail.example.com mydomain = example.com myorigin = $mydomain mydestination = localhost mynetworks = 127.0.0.0/8 [::1]/128 # Virtual domains virtual_mailbox_domains = example.com, example.org virtual_mailbox_base = /var/mail/vhosts virtual_mailbox_maps = hash:/usr/local/etc/postfix/vmailbox virtual_alias_maps = hash:/usr/local/etc/postfix/valias virtual_minimum_uid = 1000 virtual_uid_maps = static:1000 virtual_gid_maps = static:1000 # TLS (incoming) smtpd_tls_cert_file = /usr/local/etc/ssl/mail/fullchain.pem smtpd_tls_key_file = /usr/local/etc/ssl/mail/key.pem smtpd_tls_security_level = may smtpd_tls_auth_only = yes smtpd_tls_mandatory_protocols = >=TLSv1.2 smtpd_tls_mandatory_ciphers = high smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache # TLS (outgoing) smtp_tls_security_level = may smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache smtp_tls_mandatory_protocols = >=TLSv1.2 # 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 = $myhostname # Restrictions smtpd_helo_required = yes smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, reject_invalid_hostname, reject_non_fqdn_sender, reject_non_fqdn_recipient, reject_unknown_sender_domain, reject_unknown_recipient_domain # rspamd milter smtpd_milters = inet:127.0.0.1:11332 non_smtpd_milters = inet:127.0.0.1:11332 milter_protocol = 6 milter_default_action = accept milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen} # Limits message_size_limit = 52428800 mailbox_size_limit = 0 recipient_delimiter = + # Delivery via Dovecot LMTP virtual_transport = lmtp:unix:private/dovecot-lmtp EOF

Virtual Mailbox Map

sh
cat > /usr/local/etc/postfix/vmailbox << 'EOF' user@example.com example.com/user/ admin@example.com example.com/admin/ info@example.com example.com/info/ user@example.org example.org/user/ EOF postmap /usr/local/etc/postfix/vmailbox

Virtual Alias Map

sh
cat > /usr/local/etc/postfix/valias << 'EOF' postmaster@example.com admin@example.com abuse@example.com admin@example.com webmaster@example.com admin@example.com @example.com admin@example.com EOF postmap /usr/local/etc/postfix/valias

Submission Port (587)

Edit /usr/local/etc/postfix/master.cf to enable the submission port for authenticated sending:

sh
cat >> /usr/local/etc/postfix/master.cf << 'EOF' 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_recipient_restrictions=permit_sasl_authenticated,reject -o milter_macro_daemon_name=ORIGINATING EOF

Create Mail Directory Structure

sh
mkdir -p /var/mail/vhosts/example.com mkdir -p /var/mail/vhosts/example.org pw groupadd vmail -g 1000 pw useradd vmail -u 1000 -g vmail -d /var/mail/vhosts -s /usr/sbin/nologin chown -R vmail:vmail /var/mail/vhosts

Dovecot Configuration

Main Configuration

sh
cat > /usr/local/etc/dovecot/dovecot.conf << 'EOF' protocols = imap lmtp # Listen listen = *, :: # Mail location mail_location = maildir:/var/mail/vhosts/%d/%n/Maildir mail_uid = vmail mail_gid = vmail mail_privileged_group = vmail first_valid_uid = 1000 # Namespace namespace inbox { inbox = yes separator = / mailbox Drafts { auto = subscribe special_use = \Drafts } mailbox Sent { auto = subscribe special_use = \Sent } mailbox Trash { auto = subscribe special_use = \Trash } mailbox Junk { auto = subscribe special_use = \Junk } mailbox Archive { auto = subscribe special_use = \Archive } } # SSL/TLS ssl = required ssl_cert = </usr/local/etc/ssl/mail/fullchain.pem ssl_key = </usr/local/etc/ssl/mail/key.pem ssl_min_protocol = TLSv1.2 # Authentication auth_mechanisms = plain login disable_plaintext_auth = yes passdb { driver = passwd-file args = scheme=BLF-CRYPT username_format=%u /usr/local/etc/dovecot/users } userdb { driver = static args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n } # LMTP for Postfix delivery service lmtp { unix_listener /var/spool/postfix/private/dovecot-lmtp { mode = 0600 user = postfix group = postfix } } # SASL for Postfix authentication service auth { unix_listener /var/spool/postfix/private/auth { mode = 0660 user = postfix group = postfix } } # Logging log_path = /var/log/dovecot.log info_log_path = /var/log/dovecot-info.log # Sieve filtering (optional) protocol lmtp { mail_plugins = $mail_plugins sieve } plugin { sieve = /var/mail/vhosts/%d/%n/.dovecot.sieve sieve_dir = /var/mail/vhosts/%d/%n/sieve } EOF

Creating User Accounts

Dovecot uses a passwd-file for virtual users:

sh
# Generate password hash doveadm pw -s BLF-CRYPT # Add user to password file cat > /usr/local/etc/dovecot/users << 'EOF' user@example.com:{BLF-CRYPT}$2b$05$abcdef...hash...here admin@example.com:{BLF-CRYPT}$2b$05$abcdef...hash...here EOF chmod 600 /usr/local/etc/dovecot/users chown dovecot:dovecot /usr/local/etc/dovecot/users

To add a new user:

sh
echo "newuser@example.com:$(doveadm pw -s BLF-CRYPT)" >> /usr/local/etc/dovecot/users

rspamd Configuration

rspamd handles spam filtering, DKIM signing, SPF checking, and DMARC enforcement.

Basic Configuration

sh
# rspamd default configuration is in /usr/local/etc/rspamd/ # Override with local files in /usr/local/etc/rspamd/local.d/ mkdir -p /usr/local/etc/rspamd/local.d

Enable the Web UI

sh
cat > /usr/local/etc/rspamd/local.d/worker-controller.inc << 'EOF' password = "$2$xyz..."; # Generate with: rspamadm pw bind_socket = "127.0.0.1:11334"; EOF

Generate the password:

sh
rspamadm pw

DKIM Signing

Generate DKIM keys:

sh
mkdir -p /var/db/rspamd/dkim rspamadm dkim_keygen -d example.com -s 20260409 \ -k /var/db/rspamd/dkim/example.com.20260409.key > /var/db/rspamd/dkim/example.com.20260409.txt chown -R rspamd:rspamd /var/db/rspamd/dkim chmod 600 /var/db/rspamd/dkim/*.key

The output file contains the DNS TXT record. Add it to your DNS:

shell
20260409._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3..."

Configure rspamd to sign outgoing mail:

sh
cat > /usr/local/etc/rspamd/local.d/dkim_signing.conf << 'EOF' path = "/var/db/rspamd/dkim/$domain.$selector.key"; selector = "20260409"; sign_authenticated = true; sign_local = true; domain { example.com { selector = "20260409"; path = "/var/db/rspamd/dkim/example.com.20260409.key"; } } EOF

Milter Configuration

sh
cat > /usr/local/etc/rspamd/local.d/worker-proxy.inc << 'EOF' bind_socket = "127.0.0.1:11332"; milter = yes; timeout = 120s; upstream "local" { default = yes; self_scan = yes; } EOF

Redis Backend

rspamd uses Redis for statistics and rate limiting:

sh
cat > /usr/local/etc/rspamd/local.d/redis.conf << 'EOF' servers = "127.0.0.1"; EOF service redis start

Start rspamd

sh
rspamadm configtest service rspamd start

Test DKIM:

sh
# Send a test email and check headers # The rspamd web UI at http://127.0.0.1:11334 shows scanning results

Starting the Mail Stack

sh
service redis start service rspamd start service dovecot start service postfix start

Testing

sh
# Test SMTP connection openssl s_client -connect mail.example.com:587 -starttls smtp # Test IMAP connection openssl s_client -connect mail.example.com:993 # Send a test email echo "Test body" | mail -s "Test subject" user@example.com # Check mail queue mailq # Check Postfix logs tail -f /var/log/maillog

Verify DKIM/SPF/DMARC

Send an email to a Gmail or other major provider. Check the received headers for:

shell
Authentication-Results: mx.google.com; dkim=pass header.d=example.com; spf=pass; dmarc=pass

Or use online tools like mail-tester.com to verify your setup.

Webmail with Roundcube

Configure Roundcube

Create a database for Roundcube:

sh
service postgresql start su - postgres -c "createuser -P roundcube" su - postgres -c "createdb -O roundcube roundcube"

Initialize the database:

sh
psql -U roundcube roundcube < /usr/local/www/roundcube/SQL/postgres.initial.sql

Configure Roundcube:

sh
cat > /usr/local/www/roundcube/config/config.inc.php << 'RCEOF' <?php $config['db_dsnw'] = 'pgsql://roundcube:password@localhost/roundcube'; $config['imap_host'] = 'ssl://mail.example.com:993'; $config['smtp_host'] = 'tls://mail.example.com:587'; $config['smtp_user'] = '%u'; $config['smtp_pass'] = '%p'; $config['support_url'] = ''; $config['product_name'] = 'Webmail'; $config['des_key'] = 'CHANGE_THIS_TO_24_CHARS!'; $config['plugins'] = ['archive', 'zipdownload', 'managesieve']; $config['language'] = 'en_US'; $config['skin'] = 'elastic'; RCEOF

NGINX for Webmail

sh
cat > /usr/local/etc/nginx/nginx.conf << 'EOF' worker_processes auto; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; server { listen 443 ssl; server_name mail.example.com; ssl_certificate /usr/local/etc/ssl/mail/fullchain.pem; ssl_certificate_key /usr/local/etc/ssl/mail/key.pem; ssl_protocols TLSv1.2 TLSv1.3; 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; } location ~ /\. { deny all; } } server { listen 80; server_name mail.example.com; return 301 https://$host$request_uri; } } EOF service nginx start

Production Hardening

Postfix Hardening

Add to /usr/local/etc/postfix/main.cf:

shell
# Rate limiting smtpd_client_connection_rate_limit = 30 smtpd_client_message_rate_limit = 60 smtpd_client_recipient_rate_limit = 120 anvil_rate_time_unit = 60s # Header checks (remove internal IPs from outgoing headers) header_checks = regexp:/usr/local/etc/postfix/header_checks
sh
cat > /usr/local/etc/postfix/header_checks << 'EOF' /^Received:.*\[10\./ IGNORE /^Received:.*\[192\.168\./ IGNORE /^Received:.*\[172\.(1[6-9]|2[0-9]|3[01])\./ IGNORE EOF

Firewall Rules

sh
cat > /etc/pf.conf << 'EOF' ext_if = "em0" # Mail ports mail_ports = "{ 25, 587, 993, 995 }" web_ports = "{ 80, 443 }" set block-policy drop set skip on lo0 block in all pass out all keep state # SSH pass in on $ext_if proto tcp to port 22 # Mail pass in on $ext_if proto tcp to port $mail_ports pass in on $ext_if proto tcp to port $web_ports EOF sysrc pf_enable="YES" service pf start

Monitoring

Check mail queue regularly:

sh
# Add to crontab echo '*/5 * * * * root mailq | tail -1 | grep -q "Mail queue is empty" || echo "Mail queue has messages" | mail -s "Queue Alert" admin@example.com' >> /etc/crontab

Monitor logs for authentication failures:

sh
grep "authentication failed" /var/log/maillog | tail -20

FAQ

Is running your own mail server worth the effort?

It depends on your priorities. If you value privacy, control, and independence from Big Tech mail providers, yes. If you want zero maintenance and guaranteed deliverability, use a hosted service. Self-hosting email is more complex than any other server role.

Why does my mail go to spam?

The most common causes: missing or incorrect PTR record, no DKIM signature, no SPF record, sending from a shared IP with bad reputation, or a new IP with no sending history. Verify all authentication records, then send small volumes and gradually increase to build reputation.

Can I host mail for multiple domains?

Yes. Add domains to virtual_mailbox_domains in Postfix, add mailbox mappings in vmailbox, generate DKIM keys for each domain, and add DNS records for each domain. The configuration scales to hundreds of domains.

How do I back up the mail server?

Back up these directories: /var/mail/vhosts/ (all mail), /usr/local/etc/postfix/ (Postfix config), /usr/local/etc/dovecot/ (Dovecot config and user database), /var/db/rspamd/dkim/ (DKIM keys), /usr/local/etc/rspamd/ (rspamd config). For ZFS, take snapshots: zfs snapshot zroot/var/mail@daily.

How do I add a new email account?

sh
# Generate password hash doveadm pw -s BLF-CRYPT # Add to Dovecot users file echo "newuser@example.com:{BLF-CRYPT}$2b$05$hash" >> /usr/local/etc/dovecot/users # Add to Postfix virtual mailbox map echo "newuser@example.com example.com/newuser/" >> /usr/local/etc/postfix/vmailbox postmap /usr/local/etc/postfix/vmailbox # Create maildir mkdir -p /var/mail/vhosts/example.com/newuser chown -R vmail:vmail /var/mail/vhosts/example.com/newuser

Do I need IPv6 for a mail server?

Not strictly, but it helps deliverability. Some mail providers prefer servers with proper IPv6 connectivity. If you enable IPv6, make sure your AAAA record, PTR record, and SPF record all include the IPv6 address.

How do I migrate mail from another server?

Use imapsync to migrate mail between IMAP servers:

sh
pkg install p5-Mail-IMAPClient p5-IO-Socket-SSL imapsync --host1 old-server.example.com --user1 user@example.com --password1 'oldpass' \ --host2 mail.example.com --user2 user@example.com --password2 'newpass' --ssl1 --ssl2

Get more FreeBSD guides

Weekly tutorials, security advisories, and package updates. No spam.