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:
shellmail.example.com. IN A 203.0.113.10
MX record:
shellexample.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:
shellexample.com. IN TXT "v=spf1 mx a:mail.example.com -all"
DKIM record (generated after OpenDKIM setup -- covered below).
DMARC record:
shell_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:
shdig 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:
shsysrc 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:
shservice sendmail stop
Install Postfix:
shpkg 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:
shsysrc postfix_enable="YES"
Configure the system to use Postfix for local mail delivery by editing /etc/mail/mailer.conf:
shellsendmail /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.
shpkg 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 = </usr/local/etc/letsencrypt/live/mail.example.com/fullchain.pem ssl_key = </usr/local/etc/letsencrypt/live/mail.example.com/privkey.pem 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 queries.
Create the vmail system user that owns all mailboxes:
shpw 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:
shpostmap /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:
shdoveadm pw -s BLF-CRYPT
Enter the password when prompted, then add the user to /usr/local/etc/dovecot/users:
shelluser@example.com:{BLF-CRYPT}$2b$12$hashhere::::::: admin@example.com:{BLF-CRYPT}$2b$12$hashhere:::::::
Set strict permissions:
shchmod 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 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 to obtain free certificates.
Install Certbot:
shpkg install py311-certbot
Obtain a certificate for the mail hostname. If you already have NGINX running on the server, use the webroot method:
shcertbot certonly --webroot -w /usr/local/www/certbot \ -d mail.example.com
If no web server is running on port 80, use standalone mode:
shcertbot 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:
shpkg install opendkim sysrc milteropendkim_enable="YES"
Generate a DKIM key pair:
shmkdir -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:
shmkdir -p /var/run/opendkim chown opendkim:opendkim /var/run/opendkim pw groupmod opendkim -m postfix
Start OpenDKIM:
shservice milter-opendkim start
Now publish the DKIM DNS record. View the generated record:
shcat /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:
shellmail._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBg..."
Verify the record propagated:
shopendkim-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:
shdig 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:
shdig 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):
shpkg install rspamd redis sysrc rspamd_enable="YES" sysrc redis_enable="YES"
Start Redis:
shservice 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:
shrspamadm 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:
shservice rspamd start
The Postfix main.cf already includes the Rspamd milter on inet:localhost:11332. Verify integration:
shservice postfix reload
Rspamd Web UI
Rspamd includes a built-in web dashboard on port 11334. To expose it securely, proxy it through NGINX:
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:
shpkg 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:
shcd /usr/local/www/roundcube/config cp config.inc.php.sample config.inc.php
Edit /usr/local/www/roundcube/config/config.inc.php:
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:
shopenssl rand -base64 24
Initialize the Roundcube database:
shmkdir -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 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:
shnginx -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 must allow mail traffic. Add these rules to /etc/pf.conf:
shell# 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:
shpfctl -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.
shpkg install swaks
Test inbound delivery:
shswaks --to user@example.com --from test@gmail.com \ --server mail.example.com --port 25
Test authenticated submission:
shswaks --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:
shtail -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 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:
shopenssl 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:
shell/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:
shpostqueue -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:
shzfs 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:
shopenssl 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:
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.