Building Your Own Mail Server on FreeBSD

05.05.2021 1,178 0

Having your own email server looks like a very challenging job for many, but don’t worry. We are here to show you how to build your own mail server on FreeBSD

What do you need for your mail server? 

A server with open port 25. You can get a Cloud server or a Dedicated server. You can get: 

OpenSMTPd – the MTA (message transfer agent), which we will use for the tutorial. It will execute the communication process (via the SMTP protocol) with the other mail servers and deliver the incoming emails to the inboxes of the users in our FreeBSD

Dovecot – This will be the MDA (mail delivery agent) for this guide. This one will be using IMAP or POP3 for delivering the emails to the users. 

Spamd – A spam filter. You can use different criteria to filter the emails and organize them in lists. 

The idea is: 

Outside world ➡️Firewall ➡️ spamd ➡️ OpenSMTPD ➡️ Mail boxes of users

Outside world ➡️ Firewall (spamd-allow list) ➡️ OpenSMTPD ➡️ Mail boxes of users

Outside world ➡️Firewall (IMAP/POP3)➡️Dovecot

Outside world ➡️ Firewall (SMTPD submission)

Installing the software for the mail server

The first step will be installing all the necessary software. For that purpose, you need to be an admin (have sudo permissions). You will need to use the following command: 

sudo pkg install opensmtpd dovecot spamd

We will need to edit the configuration by adding a few lines to the /etc/rc.conf:









FreeBSD firewall configuration

We will be using the PF firewall, so our next step will be to create our configuration in  /usr/local/etc/pf.conf. Use the following default configuration:

## Set public interface ##


## set and drop IP ranges on the public interface ##

martians = “{,,, \

,,, \

, }”

table <spamd> persist

table <spamd-allow> persist

# Allowed webmail services

table <webmail> persist file “/usr/local/etc/pf.webmail.ip.conf”

## Skip loop back interface – Skip all PF processing on interface ##

set skip on lo

## Set the interface for which PF should gather statistics such as bytes in/out and packets passed/blocked ##

set loginterface $ext_if

# Deal with attacks based on incorrect handling of packet fragments 

scrub in all

# Pass spamd allow list

pass quick log on $ext_if inet proto tcp from <spamd-allow> to $ext_if port smtp \

    -> port 25

# Pass webmail servers

rdr pass quick log on $ext_if inet proto tcp from <gmail> to $ext_if port smtp \

    -> port 25

# pass submission messages.

pass quick log on $ext_if inet proto tcp from any to $ext_if port submission modulate state

# Pass unknown mail to spamd

rdr pass log on $ext_if inet proto tcp from {!<spamd-allow> <spamd>} to $ext_if port smtp \

    -> port 8025 

## Blocking spoofed packets

antispoof quick for $ext_if

## Set default policy ##

block return in log all

block out all

# Drop all Non-Routable Addresses 

block drop in quick on $ext_if from $martians to any

block drop out quick on $ext_if from any to $martians

pass in inet proto tcp to $ext_if port ssh

# Allow Ping-Pong stuff. Be a good sysadmin 

pass inet proto icmp icmp-type echoreq

# Open up imap/pop3 support

pass quick on $ext_if proto tcp from any to any port {imap, imaps, pop3, pop3s} modulate state

# Allow outgoing traffic

pass out on $ext_if proto tcp from any to any modulate state

pass out on $ext_if proto udp from any to any keep state

Here we define a $ext_if variable for the vtnet0 device to use later on. We also define invalid IP addresses that should not be allowed on the external interface.

There are two tables spamd and spamd-allow that appear as default from the spamd. We also have another table webmailHere we will add big webmail providers.

To view a table, you can use the command:

pfctl -t tablename -T show 

The idea behind it is to skip processing on the local interface, enable statistics on the external interface and scrub incoming packets.

The next step is to manage the outgoing traffic to spamd or OpenSMTPd.

The syntax of the redirect rules is the old PF syntax. You can find some things strange. All the traffic that we receive on SMTP from hosts in the spamd table or not in the spamd-allow table will be redirected through the spamd deamon which will manage it. There are a few pass-through rules for receiving emails. We allow messages from the IP addresses listed in the spam-allow and the webmail tables to the OpenSMTPd. And we will accept messages on port 587 (submission port).

The next steps are a few housekeeping rules, for establishing default policy, and accepting SSH and ICMP messages.

Then pass IMAP and POP3 on our external interface to access Dovecot.

The following step is to allow all outgoing traffic. 

Start firewall PF:

sudo service pf start

Done with the firewall. 


Configuring the OpenSMTPd is easy. It is just 14 lines of configuration: 

#This is the smtpd server system-wide configuration file.

# See smtpd.conf(5) for more information.


# If you edit the file, you have to run “smtpctl update table aliases”

table aliases file:/etc/mail/aliases

table domains file:/etc/mail/domains

# Keys

pki key “/usr/local/etc/letsencrypt/live/”

pki certificate “/usr/local/etc/letsencrypt/live/”

# If you want to listen on multiple subdomains (e.g. mail.davidlenfesty) you have to add more lines

# of keys, and more lines of listeners

# Listen for local SMTP connections

listen on localhost hostname

# listen for filtered spamd connections

listen on lo0 port 10026

# Listen for submissions

listen on $ext_if port 587 tls-require auth pki tag SUBMITTED

# Accept mail from external sources.

accept from any for domain <domains> alias <aliases> deliver to maildir “~/mail”

accept for local alias <aliases> deliver to maildir “~/mail”

accept from local for any relay tls

accept tagged SUBMITTED for any relay tls

First, we define the external interface, tables, aliases, and domains. After that, we go to the SSL certificate for the domain. 

We listen to the localhost for the domain in the example – We then listen to the filtered spamd messages and these from the external interface. 

Last, we listen to these on port 587 and check their authentication. 

We then go to the accept setting where we are able to accept any messages from the domains in the domain table to be delivered to the maildir (home directory). We accept all local connections for the local mailboxes and relay the messages in order to send emails.


FreeBSD ships with a default alias file /etc/mail/aliases in the following format:

vuser1: user1

vuser2: user1

vuser3: user1

vuser4: user2

These will be the different mailboxes. You can create local users who will have mailboxes on the server. 


Creating a domain file on FreeBSD is not hard:

# Domains

It is a simple plain text file that you want to listen to on a new line. The “#” symbol is for you to make a comment. 

SSL Certificates

You can use self-signed or signed certificates. You can use Let’s Encrypt to get a free one. 

Let’s install the certbot program.

sudo pkg install py-certbot

After that, go to your certificate, but make sure that you have port 80 opened. In your filtering rules add the following lines in /usr/local/etc/pf.conf:

pass quick on $ext_if from any to any port http

Then run pfctl -f /usr/local/etc/pf.conf to reload the set of rules.

Now run the command for any domain that you wish to get a certificate for:

certbot certonly --standalone -d

You can make crontab run certbot renew every 6 months so you always have a certificate. 

Then for every relevant domain, you can modify the lines to point to the correct key file:

pki key “/usr/local/etc/letsencrypt/live/”

pki certificate “/usr/local/etc/letsencrypt/live/”

Edit the securities:

sudo chmod 700 /usr/local/etc/letsencrypt/archive/*

You will have to do this for each original key file or else OpenSMTPd won’t open them.

After that, we can start the service:

sudo service smtpd start

Configuring spamd

We’ll use spamd to block spam from known sources. We will greylist certain connections. Spammers won’t try to resend the mail after an error while normal people will. 

Run the following to mount fdescfs:

mount -t fdescfs null /dev/fd

Add this line to /etc/fstab:

fdescfs /dev/fd fdescfs rw 0 0

The default configuration file (/usr/local/etc/spamd/spamd.conf.sample) will work fine. You can edit it to add new sources:

sudo cp /usr/local/etc/spamd/spamd.conf.sample /usr/local/etc/spamd/spamd.conf

Now start the service with the following:

sudo service obspamd start

Spamd is now ready to work.

Enabling Webmail Services

One problem with greylisting is that big mail services (like Google) will send mails from different servers for different messages. We will need to allow the IP address ranges for these big webmail services. This is the part of the webmail able in the PF configuration. 

To add an IP address range in the webmail table you can run the following command:

pfctl -t webmail -T add


If you want users to have a more comfortable experience you will need an MDA with IPAM/POP3 support. We have chosen Dovecot in this case because it is easy to use and configure.  Copy the default configuration:

cd /usr/local/etc/dovecot
cp -R example-config/* ./

The configuration is made up of quite a few different files. To see the differences between your configuration and the dovecot defaults, run the command below:

sudo doveconf -n

Here’s an example of a working configuration:

# (0719df592): /usr/local/etc/dovecot/dovecot.conf

# OS: FreeBSD 11.2-RELEASE amd64  

# Hostname:

hostname =

mail_location = maildir:~/mail

namespace inbox {

  inbox = yes

  location = 

  mailbox Archive {

    auto = create

    special_use = \Archive


  mailbox Archives {

    auto = create

    special_use = \Archive


  mailbox Drafts {

    auto = subscribe

    special_use = \Drafts


  mailbox Junk {

    auto = create

    autoexpunge = 60 days

    special_use = \Junk


  mailbox Sent {

    auto = subscribe

    special_use = \Sent


  mailbox “Sent Mail” {

    auto = no

    special_use = \Sent


  mailbox “Sent Messages” {

    auto = no

    special_use = \Sent


  mailbox Spam {

    auto = no

    special_use = \Junk


  mailbox Trash {

    auto = no

    autoexpunge = 90 days

    special_use = \Trash


  prefix = 

  separator = /


passdb {

  args = imap

  driver = pam


ssl = required

ssl_cert = </usr/local/etc/letsencrypt/live/

ssl_dh = </usr/local/etc/dovecot/dh.pem

ssl_key = </usr/local/etc/letsencrypt/live/

userdb {

  driver = passwd


Most config files will be in conf.d.

The important ones are 10-auth.conf, 10-mail.conf, and 10-ssl.conf.

You can configure the different mailboxes for different users in 15-mailboxes.conf. This configuration works fine for most people, but you can modify it for your particular needs. 


Most default settings will be correct. If you want the system users to authenticate, you will have to edit 10-auth.conf.

Remove the comment on the following line:

!include auth-system.conf.ext


We have to generate Diffie-Hellman parameters:

sudo nohup openssl dhparam -out /usr/local/etc/dovecot/dh.pem

NoteThis will take a long time to run. Much longer than you might expect.

Now that it is done, start Dovecot:

sudo service dovecot start


Now we have a fully functional, safe, and spam-protected email server on a FreeBSD OS. You can further reduce the spam with additional software, but this configuration will be enough for most organizations like this. 

If you need another type of mail server, see the following article:

Leave a Reply

Your email address will not be published.