Setup Email Server From Scratch On FreeBSD - 01 Server Setup

 Intro -> 02 FAMP Install

This tutorial is partially complete 2025-05-14
Postfix, Dovecot, & PostfixAdmin work with MySQL and virtual accounts
can be created with PostfixAdmin and used with an email client. Incoming SPF
milter and outgoing OpenDKIM milter signing tested and working.


################
# Server Setup #
################

Go to your prefered registrar or use an existing domainname and choose a
hostname something like mail.domain.com or mx.domain.com. DNS and MX records
setup will be done after determining the new mail servers IP addresses.

The following assumes the domain name is okbsd.com so change it to your domain
name and change the IPV4 and IPV6 addresses to your own IP addresses. Also
change 'user' to your username where needed.

Setup a server with a hosting provider of choice. I have used Kamatera, One 
Provider, MS Azure, and OVHcloud - Vint Hill.

I recently setup a Debian 12 server on Kamatera in Dallas with 2 availability 
cores 4GB RAM and 40GB disk space and network speed and performance are good for 
$19 a month.

For this FreeBSD mail stack setup, I decided to try OVH Cloud in Vint Hill SYS-1 
with 6 cores 12 threads 32GB RAM 512 GB Raid 1 SSD's. This has more than enough 
resources to run mail and other services. There is a noticable 2-3 second lag 
logging in with ssh, and ping times to 8.8.8.8 and some other sites are 
significantly slower than my other hosts. Hosting cost is $33 for setup, plus 
$33 plus tax per month. Before continuing this guide I decided to re-install
FreeBDD, but the reinstall failed. I submitted a ticket in the evening and by
morning they had reset and the IMPI/KVM console was at the first page of
FreeBSD installation again.

Install on OVH was a little more tricky since they don't support FreeBSD by 
default and the IPMI Java applet wasn't available in my case. 

The other option is to use the OVH installer and with the bring your own custom 
image option. Usually I use the DVD image since includes ports but download
would be faster if using the disk1 image.

Installation from an OVHcloud template
Type of OS: Custom
CUSTOM(2): Bring Your Own Image
-> Next

Image URL: https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/14.2/FreeBSD-14.2-RELEASE-amd64-disc1.iso 
Image Type: raw
Path of the EFI bootloader from the OS installed on the server: /boot/efi/efi/boot/bootx64.efi

A few minutes later the machine was provisioned and I switched to the IPMI/KVM 
tabs and one of the options there gave me console access with the FreeBSD 
installer start screen.

Select Install
Continue with Default Keymap
Set Hostname : okbsd.com
Distribution Select: Use the defaults plus src and ports.

If you need kernel sources and ports can be installed later.

Manual paritioning isn't needed since with ZFS you can change the partitions
later but I bumped the swap size to 4g. Another option is to choose UFS with
or without separate partitions if you never plan to try and increase the disk
size.

Partitioning: Auto (ZFS)
Pool Type: default stripe
ZFS Configuration: you can change swap to 4g if you want
>>> Install
Change password for root
New Password:
IPv4: DHCP
Search: okbsd.com
DNS1: 9.9.9.9
DNS2: 1.1.1.1
Select Timezone ...
Enable: unbound sshd ntp ntp_sync_on_start
System Hardening: I skip all of these though random pid might be an idea
Add User Accounts: user
Full Name: Regular User
If needed to use su - root add the user to group wheel
Invite user into other groups? wheel
Use defaults then set a password of your choosing

It is very important to confirm you know the root password before leaving the 
installer. The IPMI/KVM access is a poor interface as it is almost impossible to 
cut and paste any command into the IPMI console. While you can get in later if 
you know the root password with IPMI it is better to setup a user account with 
sudo or confirm root access via ssh before finishing the installer.

Choose EXIT THE INSTALLER

YOU WILL HAVE ONE LAST CHANCE TO OPEN A SHELL - OPEN A SHELL
Manual Configuration : YES

The shell at the end of the FreeBSD install is a chroot'ed shell with root privs 
so it is a good time to run some commands to make sure you can get in with ssh 
and know your passwords.

# SV - To change root password if needed
root@okbsd.com:~# passwd

# SV - To reset user password if needed
root@okbsd.com:~# passwd user

# SV - Install some programs
pkg update
pkg upgrade
pkg install bash bind-tools sudo nano dnsmasq

# Some commands can't be run in this chrooted environment so if you get
# something like ...

# nslookup google.com
/bin/sh nslookup: not found

# and you'll need to exit the installer and login as root again with IPMI/KVM console.

exit

or

shutdown -r now

# Get as list of shells
cat /etc/shells

# Change your shell with chsh but this is a vi editor so move around with the
# arrow keys, use x to delete in command mode, use i to insert before the cursor,
# x to delete a character
# i insert text before the cursor
# a append text after the cursor
# Escape to exit insert or append mode
# Escape Escape :wq to write quit
# Excape Escape :q! to quit without saving and start over

chsh
Shell: /usr/local/bin/bash

# Change shell for user also
chsh user
Shell: /usr/local/bin/bash

# open a bash shell
# /usr/local/bin/bash
# SV - Create sudo group and setup sudo access for user

pw group add sudo

# To create a use account use ... add to group wheel if you want the user to be
# able to su - root ...

adduser

# Then add your username to either group sudo or wheel

pw group mod sudo -m user

- or -

# If you want to su to root you will need to set user in group wheel as well
pw group mod wheel -m user

# You will most likely need to exit the installer and reboot before the next
# set of commands, if you haven't done it already exit the installer now.

exit
[ Reboot ]

# Edit sudoers file - this is the vi editor use x to delete and 'Esc Esc :wq'
# to write and quit. If you get an error '/bin/sh visudo not found' <reboot>.
# and log back in with IPMI/KVM shell as root.

visudo

# Find the first line starting with # %wheel and type x twice, the line
# should look like this ...

%wheel ALL=(ALL:ALL) ALL

# Find the line starting with # %sudo and type x twice

%sudo ALL=(ALL:ALL) ALL

# Hit Escape Escape :wq


# Connecting with ssh to the server

Connecting with ssh to root may not be allowed and will say failed password if 
the password is wrong or AllowRootLogin is not set to yes. So if ssh login 
fails, use the user account created during install or create a user and set the 
user password from the temporary install shell.

# Set ssh PermitRootLogin to yes, later we'll change to prohibit-password
nano /etc/ssh/sshd_config
# PermitRootLogin no
PermitRootLogin yes
# PermitRootLogin prohibit-password

service sshd restart

# Confirm the IP_ADDR of the new server look for inet <your_address>
ifconfig -a
inet 147.135.65.97 netmask 0xffffff00 broadcast 147.135.65.255

# PC - Open a terminal or command shell and login as root with ssh

ssh root@147.135.65.97
<rootpassword>

# Setup root ssh keys on the server
[root@okbsd ~]# ssh-keygen
<enter><enter><enter>

[root@okbsd ~]# exit

# SSH allows cut and paste between terminal windows so IPMI/KVM can be closed. 

# PC - Attempt to reconnect with username and password and confirm user and root access

ssh user@147.135.65.97
<userpassword>

# Setup user ssh keys on the server
user@okbsd.com:~$ ssh-keygen
<enter><enter><enter>

# Confirm sudo works. Sudo allows privileged commands to be run without using sudo.

user@okbsd.com:~$ sudo <cmd>
<userpassword>

# Confirm su works. Su allows login as root so commands can be run without sudo.

user@okbsd.com:~$ su - root
<rootpassword>
root@okbsd.com:~#

# Setup local ssh keys on *nix, Mac, or Windows computer.
# PC - For *nix and Mac use terminal and run ...

ssh-keygen
<enter><enter><enter>
cat ~/.ssh/id_rsa.pub
cd ~/.ssh

# PC - For Windows use cmd.exe and run (profile is your windows user profile) ...

ssh-keygen -t rsa -b 2048
<enter><enter><enter>
cd C:\Users\profile\.ssh\
more id_rsa.pub 

# PC - For *nix, Mac, and Windows continue with...
scp id_rsa.pub user@147.135.65.97 /home/user/importedkey.pub
ssh user@147.135.65.97
cat ~/importedkey.pub >> ~/.ssh/authorized_keys

# If scp doesn't work copy and paste the key from the local machine ~/.ssh/id_rsa.pub
# to the new server and paste it with nano as a single line into ~/.ssh/authorized_keys

# PC - Copy from *nix
cat id_rsa.pub

# PC - Copy from Windows
more id_rsa.pub

# SV - Paste to FreeBSD server
nano ~/.ssh/authorized_keys
<paste local pc output all as one line>

# SV - Add the key to user and roots authorized_keys file as well
su - root
cat /home/user/importedkey.pub >> /root/.ssh/authorized_keys
cat /home/user/importedkey.pub >> /home/user/.ssh/authorized_keys
chown -R user:user /home/user/.ssh

# SV - Restart ssh and exit to test passwordless access...

service sshd restart

# PC - Test access using ssh keys no passwords

ssh user@147.135.65.97
<no_password_required>

user@okbsd.com:~$ su - root
<rootpassword>
root@okbsd.com:~# exit
user@okbsd.com:~$ exit

ssh root@147.135.65.97
<no_password_required>
root@okbsd.com:~#

# SV - Remove the copy of the public key
rm /home/user/importedkey.pub

# Once you can login as root without a password, secure root so password login
# is denied and only the ssh key is allowed.

nano /etc/ssh/sshd_config
#PermitRootLogin no
#PermitRootLogin yes
PermitRootLogin prohibit-password

service sshd restart

# Open a separate terminal leaving the first one open to test passwordless access
# to root still works. Test that user passwordless also works, though password is
# still a fallback for the regular user.

ssh root@147.135.65.97
<no password required>
root@okbsd.com:~#

ssh user@147.135.65.97
<no password required>
user@okbsd.com:~$

#################################
# Setup Hostname and Networking #
#################################

# Change autoboot delay - default is 10 seconds

nano /boot/loader.conf
autoboot_delay="2"

# Edit rc.conf and setup hostname and network interfaces

nano /etc/rc.conf
hostname="okbsd.com"

# comment out the default inet6 line
# ifconfig_ix0_ipv6="inet6 accept_rtadv"

# Added the static IPV6 from OVH but I had to modify the prefixlen to get it to work
# since OVH suggested prefixlen 64 but that would put the router on a different subnet
# ipv6 is 128 bits with 16 bits per set, so 2 hexidecimal is 8 bits and 64 - 8 = 56

ifconfig_ix0_ipv6="inet6 2604:2dc0:100:1261::10 prefixlen 56"
ipv6_defaultrouter="2604:2dc0:100:12ff:ff:ff:ff:ff"

#local_unbound_enable="YES"
unbound_enable="YES"
# dnsmasq_enable="YES"

# Setup hosts file
nano /etc/hosts
147.135.65.97           okbsd.com mx.okbsd.com mail.okbsd.com okbsd
2604:2dc0:100:1261::10  okbsd.com mx.okbsd.com mail.okbsd.com okbsd

# --- check your resolver
nano /etc/resolv.conf
search okbsd.com
nameserver 127.0.0.1
nameserver 9.9.9.9

# Setup dnsmasq
cd /etc
ln -s /usr/local/etc/dnsmasq.conf

# --- make the following changes to dnsmasq.conf
nano /etc/dnsmasq.conf
no-resolv
expand-hosts
domain=okbsd.com
server=9.9.9.9
server=8.8.4.4

service dnsmasq restart

[root@okbsd /etc]# nslookup okbsd.com localhost
Server:         localhost
Address:        ::1#53

Non-authoritative answer:
Name:   okbsd.com
Address: 147.135.65.97
Name:   okbsd.com
Address: 2604:2dc0:100:1261::10

# Check google's answer
nslookup okbsd.com 8.8.8.8
Server:         8.8.8.8
Address:        8.8.8.8#53

Non-authoritative answer:
Name:   okbsd.com
Address: 162.255.119.169

# You can see that this is not my IP address so we need to update the real NS 
# servers for the domain, I use Namecheap as my registar and DNS is free so I 
# don't run my own public name servers. We need to add some A records and mx 
# records to Namecheap's DNS. The following assumes the domain is okbsd.com and 
# the IP ADDRESS for my OVH Cloud Dedicated Servers so change to your own domain 
# name and IP addresses.

Namecheap -> Account -> Domains -> DNS -> Advanced DNS

# Remove the redirect record.
# Remove the www CNAME record.

# Add New Records
A	@	147.135.65.97
AAAA	@	2604:2dc0:100:1261::10
A	mail	147.135.65.97
A	mx	147.135.65.97
AAAA	mail	2604:2dc0:100:1261::10
AAAA	mx	2604:2dc0:100:1261::10

# Add the www CNAME record ...
CNAME	www	okbsd.com	

# Add the autoconfig and autodiscover CNAME records ...
CNAME	autoconfig	mx.okbsd.com
CNAME	autodiscover	mx.okbsd.com


# Do not use CNAME, use A and AAAA records for mx, mail exchange hosts. You 
# could have added another ipv6 address in rc.conf as an alias and then use one 
# for mx and one for mail but this isn't necessary. We can add dmarc and spf and 
# dmarc records now and will create admin@147.135.65.97 later.

# Please do not add the following 4 records unless you are setting up a mail 
# server with the mailbox specified. Use any valid email address for the rua and 
# ruf records but RFC guidelines require a postmaster@domain.tld mailbox or 
# alias so it is a logical choice.

# ADD MX Records - scroll down in namecheap - change mx forwarding to custom mx
MX	@	mx.okbsd.com	0
MX	@	mail.okbsd.com	10

# ADD DMARC and SPF TXT Records
TXT	@	v=spf1 ip4:147.135.65.97 ip6:2604:2dc0:100:1261::10/64 mx ~all
TXT	_dmarc	v=DMARC1; p=quarantine; rua=mailto:postmaster@okbsd.com; ruf=mailto:postmaster@okbsd.com; sp=quarantine

# Modify bash shell settings for FreeBSD or Debian and create a personal bin directory
ssh user@147.135.65.97
mkdir ~/bin
echo $PATH

# If PATH doesn't have ~/bin eg /root/bin or /home/user/bin add ONE of the following
# lines to .profile on FreeBSD or to .bashrc on Debian as below, change user as
# appropriate. If your PATH already includes ~/bin don't add it again!


nano ~/.profile
# ADD ONLY ONE IF NEEDED
export PATH="$PATH:$HOME/bin"
export PATH="$PATH:/home/user/bin"
export PATH="$PATH:/root/bin"
export PATH="$PATH:~/bin"

# LSCOLORS is almost unreadable with some terminal programs but comes out nicely 
# with Xterm or Xterm-color.
 
# If you manage many servers which may by default have same short hostname like 
# 'pbx.domain.com' change PS1 so as to easily identify the server with the full 
# hostname, PS1='\u@$(hostname -f):\w\$ '

# --- FreeBSD
nano ~/.profile

export PATH="$PATH:~/bin"
EDITOR=/usr/local/bin/nano
export EDITOR
export PS1='\u@$(hostname -f):\w\$ '
export LSCOLORS="ExGxFxdxCxegDxabagacad"
export XTERM_LOCALE="en_US.UTF-8"
export LANG="en_US.UTF-8"

# my aliases
alias rm='rm -i'
alias ls-a='/bin/ls -aGF'
alias ls-l='/bin/ls -lhGF'
alias lsl='/bin/ls -lhIGF'
alias ls='/bin/ls -IGF'

# Debian - root doesn't read ~/.bashrc so this is a work around

nano /etc/bash.bashrc
# load roots bash and aliases on Debian
if [ $EUID -eq 0 ]; then
        source "${HOME}/.bashrc"
fi

# Debian - add color to .bashrc
nano .bashrc
export PATH="$PATH:~/bin"
export LS_OPTIONS='--color=auto'
eval "$(dircolors)"

# aliases
alias ls='ls $LS_OPTIONS'
alias rm='rm -i'

# Test your colors

cd ~
mkdir Downloads
ls

# Repeat the above .profile setup as the user and I comment out the fortune line

su - user
nano .profile
.....

We will continue with FAMP setup on the next page. 

 Intro -> 02 FAMP Install