Setup Email Server From Scratch On FreeBSD #2 - 01 Server Setup
We believe in data independence, and support others who want data independence.
Debian Email From Scratch version 2 finished 2025-07-30.
We are still adding to it but it all works!
################ # 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 okdeb.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 and Hillsboro. For my location on
the west coast I found better ping times for Hillsboro .. and they also had
slightly better ping times to Singapore.
An important issue is that sometimes hosting providers have IP address ranges
which are on a blacklist and some email servers may reject mail from servers
using these ranges. In particular check with a dns checker like dnschecker.org
to to see if the IP you get assigned is on UCEPROTECTL3. It is pretty difficult
to get off a IP range blacklist and the best solution is to ask the hosting
provider to give you a different IP ... and ask them to monitor their customers
email habits more rigorously. That being said even if you are on one of these IP
range spam lists it may not be a big issue. I tested with okdeb.com which was on
such one such list and google accepted the email while outlook accepted it but
put it in the junk folder. Listing it as not junk will improve your domain's
reputation as long as you're not doing mass mailing campaigns or sending spam.
I recently reinstalled a virtual private server with OVHCloud in Oregon with 4
cores 4GB RAM and 80GB disk space and network speed and performance are good for
$11 a month. This is cheaper than my previous hosting with more cores, disk space,
and an IPV6 address. Installing the defaults Debian 12 OS's is straightforward so
I won't explain it here.
Login will be initially: debian with the password sent by email. Once in setup
ssh keys for secure simple login.
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 in /etc/ssh/sshd_config. So if
ssh login fails, use the user account created during install, in this case debian.
ssh debian@[ipaddress_from_ovh_control_panel]
<password_from_ovh_email>
SV - Change debian user password
debian@vps-XXX:~$ passwd
<newpassword>
<newpassword>
SV - Generate user debian ssh keys
debian@vps-XXX:~$ ssh-keygen
<enter>
<enter>
<enter>
SV - Change root password if needed
debian@vps-XXX:~$ sudo passwd root
<newrootpassword>
<newrootpassword>
su - root
<newrootpassword>
root@vps-XXX:~# ssh-keygen
<enter>
<enter>
<enter>
Set ssh PermitRootLogin to yes, later we'll change to prohibit-password
nano /etc/ssh/sshd_config
PermitRootLogin yes
systemctl restart sshd
Check /etc/apt/sources.list
Depending on your install you may need to commend out the cdrom line
I switched everything the the mirror that is fastest for me rather than the
default ovh settings. You can leave these alone just make sure to comment the
cdrom: line if needed.
nano /etc/apt/sources.list
# See /etc/apt/sources.list.d/debian.sources
# deb cdrom:[Debian GNU/Linux 12.8.0 _Bookworm_ - Official amd64 DVD Binary-1 with firmware 20241109-11:05]/ bookworm contrib main non-free-firmware
deb http://mirrors.ocf.berkeley.edu/debian/ bookworm main non-free-firmware contrib non-free
deb-src http://mirrors.ocf.berkeley.edu/debian/ bookworm main non-free-firmware contrib non-free
deb http://security.debian.org/debian-security bookworm-security main non-free-firmware contrib non-free
deb-src http://security.debian.org/debian-security bookworm-security main non-free-firmware contrib non-free
# bookworm-updates, to get updates before a point release is made;
# see https://www.debian.org/doc/manuals/debian-reference/ch02.en.html#_updates_and_backports
deb http://mirrors.ocf.berkeley.edu/debian/ bookworm-updates main non-free-firmware contrib non-free
deb-src http://mirrors.ocf.berkeley.edu/debian/ bookworm-updates main non-free-firmware contrib non-free
apt update
apt upgrade
apt install net-tools dnsutils
Confirm the IP_ADDR of the new server look for inet <your_address>
ifconfig -a
inet 15.204.113.148 netmask 0xffffff00 broadcast 15.204.113.255
PC - Open a terminal or command shell and login as root with ssh
ssh root@15.204.113.148
<rootpassword>
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 debian@15.204.113.148:/home/debian/importedkey.pub
scp id_rsa.pub root@15.204.113.148:/root/importedkey.pub
ssh debian@15.204.113.148
cat ~/importedkey.pub >> ~/.ssh/authorized_keys
exit
ssh root@15.204.113.148
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 ~/.ssh/id_rsa.pub
PC - Copy from Windows
cd C:\Users\YourProfile\.ssh
more id_rsa.pub
SV - Paste to Debian server
ssh root@15.204.113.148
nano ~/.ssh/authorized_keys
<paste local pc output all as one line>
add the same key for debian user, or create a new user and delete the debian user
cat /root/.ssh/authorized_keys >> /home/debian/.ssh/authorized_keys
chown -R debian:debian /home/debian/.ssh
SV - Restart ssh and exit to test passwordless access...
systemctl restart sshd
PC - Use another terminal window to test access using ssh keys no passwords
ssh root@15.204.113.148
<no_password_required>
root@vpsXXXX:~#
SV - Remove the copy of the public key
rm /root/importedkey.pub /home/debian/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 prohibit-password
systemctl restart sshd
Open a separate terminal leaving the first one open to test passwordless
access to root and debian user still works. Test that user passwordless also
works, though password is still a fallback for the regular user.
ssh debian@15.204.113.148
<no password required>
debian@okdeb.com:~$
ssh root@15.204.113.148
<no password required>
root@okdeb.com:~#
If you are concerned about security and don't like having a known username
'debian' with sudo privileges, remove the debian user and optionally create
another regular user, not covered here. You may need to change the new user's
group to be able to use su and sudo.
############################################## # Setup Hostname and Networking and TimeZone # ##############################################
hostnamectl set-hostname okdeb.com
nano /etc/hosts
15.204.113.148 okdeb.com mx.okdeb.com mail.okdeb.com
2604:2dc0:202:300::3645 okdeb.com mx.okdeb.com mail.okdeb.com
Configure Timezone
dpkg-reconfigure tzdata
With Debian on OVH the server uses netplan, don't change anything here unless you
really need to but the file is under /etc/netplan/50-cloud-init.yaml. Changing
over to ifupdown with /etc/network/interfaces might make the network unusuable, but
there is always the KVM interface in OVH if you broke the networking.
It's under Your VPS -> Name -> KVM
Note I changed the default nameservers ... netplan is very picky about
whitespace in yaml files. I don't particularly like netplan and prefer ifupdown
/etc/network/interfaces but we'll leave this alone for now.
cat /etc/netplan/50-cloud-init.yaml
nano /etc/netplan/50-cloud-init.yaml
nameservers:
addresses:
- 1.1.1.1
- 9.9.9.9
- 213.186.33.99
try the new configuration
netplan try
test it
ping 8.8.8.8
if ping works apply the new config to make it permanent
netplan apply
If you plan to use spamassassin with real time blacklist you will need a more
robust DNS server than systemd-resolved. Spamhaus RBL will reject multiple DNS
requests. Bind caches the DNS results solving the problem. Bind installation
instructions are included in this howto 11_SpamAssassin and in 80_Bind_DNS.
reload the system resolver...
systemctl restart systemd-resolved
nano /etc/resolv.conf
--- check your resolver ---
search okdeb.com
nameserver 127.0.0.1
nameserver 9.9.9.9
Debian by default comes with systemd-resolved, check to see if dnssec is enabled
resolvectl query cloudflare.net
-- Data is authenticated: yes
If dnssec is not enabled add and restart it
mkdir -p /etc/systemd/resolved.conf.d
nano /etc/systemd/resolved.conf.d/dnssec.conf
[Resolve]
DNSSEC=true
systemctl restart systemd-resolved
other commands
dig @127.0.0.53 okbiz.net +dnssec +multiline
Look for 'ad' flag to confirm dnssec is enabled, you will have to enable this with your DNS provider
cat /run/systemd/resolve/resolv.conf
root@okdeb.com:~# nslookup okdeb.com 127.0.0.53
Server: 127.0.0.53
Address: 127.0.0.53#53
Name: okdeb.com
Address: 15.204.113.148
Server: localhost
Address: ::1#53
This isn't showing the IPV4 address so let's fix that
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 okdeb.com and
the IP ADDRESS for my OVH Cloud Dedicated Servers so change to your own domain
name and IP addresses. The second A and AAA record 'mail' is an alternate name
and set as a placeholder for a future backup mx server.
Namecheap -> Account -> Domains -> DNS -> Advanced DNS
Remove the redirect record.
Remove the www CNAME record.
Add New Records
A @ 15.204.113.148
AAAA @ 2604:2dc0:202:300::3645
A mx 15.204.113.148
AAAA mx 2604:2dc0:202:300::3645
A mail 15.204.113.148
AAAA mail 2604:2dc0:202:300::3645
Add the www CNAME record ...
CNAME www okdeb.com
Add the autoconfig and autodiscover CNAME records ...
CNAME autoconfig mx.okdeb.com
CNAME autodiscover mx.okdeb.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@15.204.113.148 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.okdeb.com 0
MX @ mail.okdeb.com 10
ADD DMARC and SPF TXT Records
TXT @ v=spf1 ip4:15.204.113.148 ip6:2604:2dc0:202:300::3645/64 mx ~all
TXT _dmarc v=DMARC1; p=quarantine; rua=mailto:postmaster@okdeb.com; ruf=mailto:postmaster@okdeb.com; sp=quarantine
You will need to add a SV records for Autodiscover
Type Service Protocol Priority Weight Port Target
SRV Record _autodiscover _tcp 5 0 443 mx.okdeb.com
Modify bash shell settings for FreeBSD or Debian and create a personal bin directory
ssh debian@15.204.113.148
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 ~/.bashrc
--- ADD ONLY ONE - ONLY 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\$ '
Debian
nano ~/.bashrc
...
export PATH="$PATH:~/bin"
export EDITOR=/usr/bin/nano
export PS1='\u@$(hostname -f):\w\$ '
export XTERM_LOCALE="en_US.UTF-8"
export LANG="en_US.UTF-8"
# You may uncomment the following lines if you want `ls' to be colorized:
export LS_OPTIONS='--color=auto'
eval "$(dircolors)"
alias ls='ls $LS_OPTIONS'
# alias ll='ls $LS_OPTIONS -l'
# alias l='ls $LS_OPTIONS -lA'
#
# Some more alias to avoid making mistakes:
alias rm='rm -i'
# alias cp='cp -i'
# alias mv='mv -i'
Some Debian versions - 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
Test your colors. Some terminals render the default colors poorly so
directories are hard to read so I usually use Xterm.
cd ~
mkdir Downloads
ls
Repeat the above .bashrc setup as the debian user, comment out the fortune line
su - user
nano .bashrc
<do same as above>
Firewall Setup - Once you start enabing services and adding DNS entries random addresses will start hitting your server
trying to find a way in for nefarious purposes, so you will need a firewall. On Debian I use ufw. When making changes
copy the script to a temporary file and test that first before copying it to the active script used at boot.
apt install ufw
I put the rules here and so might reveal some vulnerabilities but I'll eventually scrap this machine and
setup a new one for tutorial version 2.
nano /root/ufw.rules
#!/usr/bin/sh
# 4190 - sieve management port
# 110 995 - POP POPS
ufw disable
ufw reset
# next will log allow ports 25,80,143,443,465,587,993
ufw allow in log 25,80,143,443,465,587,993/tcp
ufw allow in 53/tcp
ufw allow in 53/udp
# ufw allow from ::/0 to 25,80,143,443,465,587,993/tcp
# ipv4 allow trusted access to all services
ufw allow from <sometrusted ipv4 host>
ufw allow from <sometrusted ipv4 network>
# ipv6 allow trusted access to all services
ufw allow from <sometrusted ipv6 host>
ufw allow from <sometrusted ipv6 network>
# ipv4 port 22 allow trusted access to ssh
ufw allow from <sometrusted ipv4 host> to any port 22
# ipv6 port 22 allow trusted access to ssh
ufw allow from <sometrusted ipv6 host> to any port 22
# log deny any other ssh to port 22
ufw deny in log proto tcp from any to any port 22
# deny other ports without log ignore random connection attempts
ufw deny in on eth0 to any
# allow outgoing
ufw default allow outgoing
ufw enable
# other ufw commands
# ufw status
# ufw status numbered
# ufw default deny incoming
# ufw default allow outgoing
# ufw default xxxxxx routed
# ufw status verbose
chmod 750 ufw.rules
sh ./ufw.rules
To test your rules be cautious. I'd first put an allow all to port 22 rule as the
first real rule.
ufw allow in 22/tcp
Then run sh ./ufw.rules, if there are no errors comment out the allow all rule
and run the command again. If you have an error and get locked out, use KVM to
get in and fix the script.
<reboot>
Check your active rules with ...
ufw status numbered
If you get a locale error with the .bashrc changes make sure install your
chosen locale, in my case en_US.UTF-8 UTF-8
dpkg-reconfigure locales
The default debian partitioning for virutal machine on ovhcloud doesn't have a swap
partition and there is only 4G RAM, so add a 4G swap. The easiest way to do this is
with a swapfile.
dd if=/dev/zero of=/swapfile bs=1M count=4096
chmod 600 /swapfile
mkswap /swapfile
echo '/swapfile swap swap defaults 0 0' >> /etc/fstab
systemctl daemon-reload
swapon /swapfile
free
total used free shared buff/cache available
Mem: 3923244 957476 115480 15484 3127592 2965768
Swap: 4194300 0 4194300
We will continue with LAMP setup on the next page.