Setup Email Server From Scratch Debian #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

# --- check your resolver
nano /etc/resolv.conf
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\$ '

# --- FreeBSD
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 .profile
.....

# 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