Setup Email Server From Scratch On FreeBSD #2 - 01 Server Setup

 Intro -> 02 LAMP Install

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.

 Intro -> 02 LAMP Install