1.0 Introduction

Like most people, I expect, I have long had my email accounts with the likes of Yahoo!, Hotmail, Gmail and -lately- Outlook. They are robust, accessible from anywhere with access to a browser -and in the case of some of them, at least, their spam control is superb. But your email is at the mercy of others: companies with privacy policies you probably didn’t read in the first place and known links to the likes of the NSA. I don’t have anything to hide, but privacy isn’t the same as ‘hiding’ something -and I have a historical right to privacy that doesn’t need justifying!

So I’ve long wanted to not use those sorts of services. And, once or twice, I’ve actually managed to build a personal web server that seemed to send stuff places and, occasionally, receive it in return… but, by God is it hard work! I have never dabbled in an IT field that is riddled with such arcane stuff, apparent complexity and possibility for bamboozlement!

Having just upgraded the web server that brings you these pages to a 16GB RAM monster, I thought I had enough spare horsepower to give it another try. But I quickly realised that was silly: my web server hosts several websites, so its Apache and MySQL configuration is quite complicated enough without suddenly blasting the likes of Postfix, Dovecot, Spamassassin and Roundcube all over the top of it. Additionally, it’s really not a great idea to make one server do everything, especially if one of those ‘everythings’ involves receiving a tidal wave of spam and virus-infested emails. One bad day at the email and Dizwell Informatics goes offline? Not ideal, is it?!

So I bought another dedicated server from the nice folks at online.net (for a not-too-bad €9 a month), though I first experimented with an even cheaper cloudy, virtual private server from their subsidiary Skyway (for a paltry €3 a month). Different providers will have different ways of configuring these things, but I’m going to document what worked for me using the online.net dedicated server approach.

From the outset, I should identify the very specific rules and requirements that my new server had to meet:

  • It needs to be secure
  • It needs to have a competent, usable, reasonably-attractive webmail component
  • It needs to do good spam detection, but still deliver it so I can make the final call on if it’s spam or not
  • It needs to be able to do robust antivirus blocking
  • The webmail component must be able to do filtering and mail handling rules processing (i.e., if sender is Aunt Mabel, move email automatically to ‘Distant Family’ folder, etc).

Apart from that, nothing was really fixed. But my first experiments (actually, my first half-dozen attempts) convinced me that my familiarity with CentOS/Red Hat compared to Ubuntu meant it had to be a CentOS server. I decided to use CentOS 7 rather than 6.8, just because it’s a bit newer and fresher. I also similarly discovered that Squirrelmail is pretty basic as a web front end to a mail server; Roundcube is a lot more sophisticated. So it had to be Roundcube. Spam detection has always been a job for Spamassassin and if there’s anything other than clamav for antivirus duties on a Linux plaform, I’m not aware of what it might be (in conjunction with Amavis on a mail server, of course).

All of which is a long way of saying that I’m going to document the building of a CentOS 7.2 server running Postfix, Dovecot, Spamassassin, Amavis, Roundcube and Clamav.

What follows is as exhaustive as I can make it, simply because there are so many moving parts and I’d hate to have to do this again without a very comprehensive set of instructions… and I figure if that’s true for me, it’s probably true for any future reader, too! It’s also incremental, in the sense that each part of the story builds on what went before: no doubt, you could re-write it as a single ‘do everything at once’ piece, but this is the way I built my server (getting Postfix right before daring to tackle Dovecot, for example), so it’s the way I’ve documented it.

Accordingly, this is a six-part series and you’re just reading the first of the six parts!

2.0 Acquiring a suitable Server

As I mentioned, I didn’t want to use my web server as my email server… and I suggest you keep things strictly dedicated, too. An email server for personal use doesn’t have to be extremely powerful, but it can’t get by on just 512MB RAM either… Spamassassin and Clamav are notorious for chewing through resources. So I needed to acquire a server with a minimum of about 2GB RAM and (say) 50GB hard disk space.

I turned to the guys at Online, whose ‘Dedibox SC SSD 2016’ product offering provides you with 120GB of solid state hard disk, 4GB RAM and gigabit, unlimited network traffic for €9 a month (around US$10). I don’t have shares in the company, but in more than 2 years of hosting this website with them, I’ve never had cause for complaint. So they seemed like a reasonable choice to me -and their being based in France means that the NSA and FBI privacy-invading techniques are (slightly) less likely to work there than most other places I could be hosted!

Once your new server has been provisioned, you have to install an O/S on it. The specifics of how you do that vary depending on your provider, but here’s how Online do it. First, pick an operating system to install. I’m chosing to run with a ‘server distribution’:


Next, pick a specific O/S to use. The default choice seems to be Debian, but CentOS is certainly do-able:


A default disk partitioning scheme is then proposed -and, in my case, accepted by clicking the ‘Validate’ button:


Then you fill in some basic details about the server name, the root account’s password and a non-root user and password:


I’m going to call this server “mail.dizwell.com”, and I suggest you similarly stick to a simple ‘mail.some-domain.com’ convention too. For the rest of all these articles, by the way, when you see my type ‘dizwell.com’, that will need editing to suit your specific circumstances. I’ll try and remember to mark in bold anything I tell you to type or paste which requires such editing before it can actually be used!

Click the ‘Delete all my disks and install the system’ button to make things actually happen:


As the dialog then says, be a little patient:


I’ve not found it take an hour myself, but it can take maybe 20 minutes. But time for a cup of tea anyway. Online also display a prominent warning when done that ‘although it’s now installed, please wait an hour before trying to connect’. I never have and the sky hasn’t fallen in as yet!

3.0 Connecting to your Server

Once your operating system has been installed, you can connect to it from a PC using ssh (on Windows, you’d probably use putty or Mobaxterm; on Linux, any old console will do!):

ssh root@

(I’m making up the IP addresses in this article as I go; it’s not hard for anyone to find out what the ‘real’ one is that I use, but I don’t share the information willingly!) You’ll be prompted to supply the administrator password you supplied when first building the server.

It’s not good to connect to servers as root, of course. But I’m going to assume you have in everything that follows. Some articles of this type insist on sticking ‘sudo’ in front of every command; I prefer not to type so much, so just keep in mind that the unspoken deal here is that you do everything as root, unless otherwise stated.

4.0 Hardening the Server

Our first job is to harden the server a little, so that it isn’t a total sitting duck out in the Internet Badlands. In episode 6 of this compelling mini-series, I’ll harden things up significantly more, but we’ll do some useful work now even so.

4.1 Preliminaries

We kick things off by getting a few of the basics in place:

hostnamectl set-hostname mail.dizwell.com
nano /etc/hosts

You specified a hostname when building your server, but some providers may not let you do so and it never hurts to double-check things anyway. So here I’m using Systemd’s ‘hostnamectl’ to explicitly set my hostname. It’s in bold here, of course, because you’ll want to supply your own host name, not re-use mine!

The last command above was to start editing your hosts file. You leave most of the existing content of the file as it currently is, but you need to add your public IP and hostname to the file, leaving other lines unaltered. You should end up with something like this: localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 mail.dizwell.com mail

The top lines were already there for me, but the last line of this is the one I had to add from scratch: it maps the public IP address of the new server to the correct domain name.

Moving on:

timedatectl list-timezones

That will list you all the valid timezones. Page up and down through it (or up-arrow and down-arrow to go a bit slower) until you find the right name for your particular time zone. Once you’ve found what you’re after, press q to quit. Finish off with:

timedatectl set-timezone 'Australia/Sydney'

I’m using the time zone I found that suitable for me; you obviously replace that with something suitable for you.

Now the next step might seem to be unhardening the server, but SELinux can be a real pain and rather than faff around trying to get it to play nice with webmail capabilities, it’s a lot easier just to disable it entirely. In which case:

nano /etc/selinux/config

Find the line that says ‘SELINUX=<something>’ and make sure it is set (or change it) to ‘SELINUX=disabled’. Make the new setting by typing one further command:


When your server comes back up, it will be up-to-date, properly named, properly time-zoned and has a way of resolving the right names into the right IP addresses. Now it’s time to really start to harden things up a bit.

4.2 Create a non-root, but powerful user

It’s very bad form to be logging on as root to any server, of course.

useradd dizwell && passwd dizwell
usermod -aG wheel dizwell

That creates a user called “dizwell” and then prompts you to supply a password for him: make it long, strong and fiendishly complicated to guess. It also adds the new user to the ‘wheel’ group, which is CentOS’ way of allowing him to type ‘sudo’ in front of commands to have them run with root privileges.

4.3 Harden SSH

‘Security through obscurity’ is actually no security at all: merely running ssh on a non-default port doesn’t enhance your security at all, especially since scanning ports in bulk is a piece of cake. Equally, however, there’s no point in erecting big neon signs saying ‘hack me!’ outside your house unless you are a bit of a masochist. So, I would suggest altering ssh to run on a non-default port. I would also suggest banning root from ever being able to ssh to your server and, for good measure, I’d stop ssh being contactable via IPv6.

Here we go then:

systemctl stop firewalld.service && sudo systemctl disable firewalld.service
nano /etc/ssh/sshd_config

The first line switches off the existing firewall. We’re about to change the SSH port: we don’t want a running firewall blocking our attempts to connect to it after we’ve changed it!

The second line allows us to edit the ssh configuration file. Find the lines:

  • Port …and set it to have a value of 2345 (or whatever port number makes sense to you)
  • AddressFamily inet  …it switches off IPv6 for the ssh protocol only
  • PermitRootLogin  …and set it to have a value of no

With the modified file saved, you can restart the ssh service:

systemctl restart sshd

Once you’ve done all that, disconnect from your server (type exit a lot, basically!). Then try to make a new connection as the non-root user you created earlier. For me, on my Linux PC, this means typing the following:

ssh -p 2345 dizwell@

The ‘-p’ switch forces this particular SSH session to try to connect to the server using port 2345 (the non-default port I specified on the server earlier).

4.4 Build a Firewall

We previously disabled all firewalling on the server, so it’s currently sitting there with all its front doors open to the Internet -and that’s not very sensible.

The primary requirement we have for this new server is that it should block everything except ports 25, 65, 80, 587, 110, 995, 143, and 993, plus the non-standard SSH port (which I’ve just set to 2345 in our previous hardening effort). You need 2345 (or whatever non-standard port you chose) to be open, otherwise you’ll never be able to connect to your server again! You need all the other ports I mentioned open because those are the stock-standard email-related ports; block them and your mail server won’t work properly.

I’ll mention here, too, that CentOS 7 uses the firewalld wrapper on top of iptables, so you’re not supposed to interact with iptables directly. However, I find this a bit clunky and prefer to do iptables work hands-on, which is still supported. Additionally, I have old iptables scripts which I trust, so I’d prefer to keep using those if at all possible!

Therefore, here’s how I built the great firewall of Dizwell:

yum -y install iptables-services
nano /root/create-iptables.sh

That’s me installing iptables and ensuring that it starts automatically at every subsequent server reboot. I’m also creating a completely new shell script in root’s home directory. You need to paste into that new file the following text:


# Wipe the tables clean
iptables -F

# Accept all loopback input
iptables -A INPUT -i lo -p all -j ACCEPT

# Allow the three way handshake
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Reject spoofed packets
iptables -A INPUT -s -j DROP 
iptables -A INPUT -s -j DROP
iptables -A INPUT -s -j DROP
iptables -A INPUT -s -j DROP

iptables -A INPUT -s -j DROP
iptables -A INPUT -d -j DROP
iptables -A INPUT -s -j DROP
iptables -A INPUT -d -j DROP
iptables -A INPUT -s -j DROP
iptables -A INPUT -d -j DROP
iptables -A INPUT -d -j DROP
iptables -A INPUT -d -j DROP

# Stop smurf attacks
iptables -A INPUT -p icmp -m icmp --icmp-type address-mask-request -j DROP
iptables -A INPUT -p icmp -m icmp --icmp-type timestamp-request -j DROP
iptables -A INPUT -p icmp -m icmp --icmp-type 8 -m limit --limit 1/second -j ACCEPT

# Drop all invalid packets
iptables -A INPUT -m state --state INVALID -j DROP
iptables -A FORWARD -m state --state INVALID -j DROP
iptables -A OUTPUT -m state --state INVALID -j DROP

# Drop excessive RST packets to avoid smurf attacks
iptables -A INPUT -p tcp -m tcp --tcp-flags RST RST -m limit --limit 2/second --limit-burst 2 -j ACCEPT

# Attempt to block portscans
# Anyone who tried to portscan us is locked out for an entire day.
iptables -A INPUT   -m recent --name portscan --rcheck --seconds 86400 -j DROP
iptables -A FORWARD -m recent --name portscan --rcheck --seconds 86400 -j DROP

# Once the day has passed, remove them from the portscan list
iptables -A INPUT   -m recent --name portscan --remove
iptables -A FORWARD -m recent --name portscan --remove

# These rules add scanners to the portscan list, and log the attempt.
iptables -A INPUT   -p tcp -m tcp --dport 139 -m recent --name portscan --set -j LOG --log-prefix "Portscan:"
iptables -A INPUT   -p tcp -m tcp --dport 139 -m recent --name portscan --set -j DROP

iptables -A FORWARD -p tcp -m tcp --dport 139 -m recent --name portscan --set -j LOG --log-prefix "Portscan:"
iptables -A FORWARD -p tcp -m tcp --dport 139 -m recent --name portscan --set -j DROP

# Allow the following ports through from outside
# smtp (both standard and 'secure')
iptables -A INPUT -p tcp -m tcp --dport 25 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 587 -j ACCEPT
# http 
iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
# pop3 
iptables -A INPUT -p tcp -m tcp --dport 110 -j ACCEPT
# imap
iptables -A INPUT -p tcp -m tcp --dport 143 -j ACCEPT
# https
iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
# imaps
iptables -A INPUT -p tcp -m tcp --dport 993 -j ACCEPT
# pop3s
iptables -A INPUT -p tcp -m tcp --dport 995 -j ACCEPT
# ssh & sftp
iptables -A INPUT -p tcp -m tcp --dport 2345 -j ACCEPT

# Allow pings through
iptables -A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT

# Kill all other input 
iptables -A INPUT -j REJECT

# Output side
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow the following ports through from outside
# smtp
iptables -A OUTPUT -p tcp -m tcp --dport 25 -j ACCEPT
# DNS requests
iptables -A OUTPUT -p udp -m udp --dport 53 -j ACCEPT
# DHCP/Bootstrap Protocol Server
iptables -A OUTPUT -p udp -m udp --dport 67 -j ACCEPT
# http 
iptables -A OUTPUT -p tcp -m tcp --dport 80 -j ACCEPT
# pop3 
iptables -A OUTPUT -p tcp -m tcp --dport 110 -j ACCEPT
# imap
iptables -A OUTPUT -p tcp -m tcp --dport 143 -j ACCEPT
# https
iptables -A OUTPUT -p tcp -m tcp --dport 443 -j ACCEPT
# imaps
iptables -A OUTPUT -p tcp -m tcp --dport 993 -j ACCEPT
# pop3s
iptables -A OUTPUT -p tcp -m tcp --dport 995 -j ACCEPT
# ssh & sftp
iptables -A OUTPUT -p tcp -m tcp --dport 2345 -j ACCEPT

# Allout pings out
iptables -A OUTPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT

# Kill all other output
iptables -A OUTPUT -j REJECT

iptables -A FORWARD -j REJECT

It’s long and tedious and I don’t propose to explain it all in detail here. Take it on trust that it does what I said it should do: block all ports except those needed for email and ssh. Note that you should edit the script to specify the actual non-default ssh port you chose, not the ‘2345’ one I’ve assumed here. There are two places where that port is mentioned, which I’ve marked in bold.

Once that shell script is saved, run it to cause its rules to be applied to the firewall that we’ll also start up and ensure starts up at every subsequent reboot:

systemctl enable iptables 
systemctl start iptables
chmod 700 /root/create-iptables.sh

Finally, make sure that same set of rules is re-loaded every time the server reboots:

service iptables save
service iptables restart

To check that our firewall really is working as intended:

iptables -L

…which will return you a pile of information some of which tells you exactly what you need to know:

ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:http
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:pop3
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:imap
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:https
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:imaps
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:pop3s

That lot indicates we are accepting packets from all the ports associated with http, imap, pop3 and their encrypted cousins… which is just what we need from a mail server with webmail component.

You should at this point probably log out from the server and attempt to create a new connection to the server as before (i.e. ssh -p 2345 etc etc etc). You want to be sure that new connections are getting through the new firewall. If they don’t, you’ve effectively just locked yourself out from your own server: you either need to rescue it somehow (lots of providers provide web-based consoles that allow you to log on and issue commands as root to do that), or rebuild it and try again.

Whatever it takes, you want to be able to log on as a non-root user who is capable of sudo-ing to become root when needed onto a server that is running a firewall and a slightly-hardened SSH implementation.

5.0 Conclusion

I shall end this particular article at this point. By this stage, you should have a well-resourced server to which you can securely connect; to which email and webmail traffic could connect when we’re ready for it to do so; but to which no-one and nothing else can make easy connections. It is a solid start to building an effective mail server… but of course, you will need to read on to action the various next steps required to make that all happen.