Here is the list of the different steps to configure a web server with Ubuntu 16.04. The host of our VPS is Linode. This tutorial covers the basic configuration of the server.

1. Create a Linode

Choose “Deploy an image” (or “Rebuild” if the server was already existing), then these options:

  • Image: Ubuntu 16.04 TLS
  • Deployment Disk Size:  24320 MB
  • Swap Disk:  256 MB

Choose a root password, click on “Deploy” and boot the server.

2. Connect via SSH to the Server

In the command line, type:

ssh [email protected]

When prompted, indicate the password chosen for root during the creation of the linode.

3. Update the Server

Run this command:

apt-get update && apt-get upgrade -y

4. Set the Hostname

Run this command to set the hostname (replacing the last “hostname” by the actual hostname for the server):

hostnamectl set-hostname hostname

5. Configure the Domain DNS Zone

  • Click on the DNS Manager tab
  • Click on “Add a domain zone”
  • Select “Yes, insert a few records to get me started”, choosing the Linode previously created.
  • Click on “Add a Master Zone”

6. Configure the FQDN (Fully Qualified Domain Name)

In order to get a functional FQDN (Fully Qualified Domain Name), add the hostname as a A / AAAA record in the DNS Manager zone:

A Record:
hostname.domain.com (pointing to the IPv4)

AAAA Record:
hostname.domain.com (pointing to the IPv6)

7. Update /etc/hosts

nano /etc/hosts

Under the 2 IP addresses already there, add the IPv4 and the IPv6:

[IP ADDRESS IPv4] hostname.domain.com hostname
[IP ADDRESS IPv6] hostname.domain.com hostname

For example:

102.104.105.106 hostname.domain.com hostname
2600:3c01::a123:b456:c789:d012 hostname.domain.com hostname

Separate each part of the IP – FQDN – Hostname by a Tab spacing for better formatting.

Reminder (cf. point n°6): The value you assign as your system’s FQDN should have an “A” record in DNS pointing to your Linode’s IPv4 address. For Linodes with IPv6 enabled, you should also set up a “AAAA” record in DNS pointing to your Linode’s IPv6 address.

8. Setting the Timezone

To set up the timezone, execute:

dpkg-reconfigure tzdata

9. Set the Reverse DNS

  1. On the Linode dashboard, click on “Remote Access”
  2. Click on “Reverse DNS”
  3. Look up the hostname and add it as reverse DNS for both IPv4 and IPv6

10. Install Unattended-Upgrades

Install this package, which will take care of security updates automatically:

apt install unattended-upgrades

To make a more customize install and get emails with summaries of the available updates & upgrades available, check this page.

11. Add A Limited User Account

To avoid mistakes, hacking, etc. it’s preferable to create a limited user who will be able to sudo to accomplish administrative tasks.

To create a user, run this command:

adduser example_user

To give him admin privileges:

adduser example_user sudo

Exit the terminal.

exit

Log in as the limited user to follow the next steps.

ssh [email protected]

12. Harden SSH Access

Once logged-in as the limited user, upload your RSA key to the server. The process described below is for a user who already has a private key, and is using Mac OS.

On your Linode (while signed in as your limited user):

mkdir -p ~/.ssh && sudo chmod -R 700 ~/.ssh/

From your local computer, you’ll send the SSH public key to the server.

scp ~/.ssh/id_rsa.pub [email protected]:~/.ssh/authorized_keys

Finally, back on your linode, you’ll want to set permissions for the public key directory and the key file itself:

sudo chmod 700 -R ~/.ssh && chmod 600 ~/.ssh/authorized_keys

Log out and try reconnecting with the limited user. You should get connected without entering your password.

13. Disallow Root Login Over SSH

This requires all SSH connections be by non-root users. Once a limited user account is connected, administrative privileges are accessible either by using sudo or changing to a root shell using su -.

nano /etc/ssh/sshd_config

Change:

PermitRootLogin no

14. Disable SSH Password Authentication

This requires all users connecting via SSH to use key authentication. Depending on the Linux distribution, the line PasswordAuthentication may need to be added, or uncommented.

nano /etc/ssh/sshd_config

Change:

PasswordAuthentication no

15. Listen On Only One Internet Protocol

The SSH daemon listens for incoming connections over both IPv4 and IPv6 by default. Unless you need to SSH into your Linode using both protocols, disable whichever you do not need.

Use the option:

  • AddressFamily inet to listen only on IPv4.
  • AddressFamily inet6 to listen only on IPv6.

The AddressFamily option is usually not in the sshd_config file by default. Add it to the end of the file:

echo 'AddressFamily inet' | sudo tee -a /etc/ssh/sshd_config

16. Restart The SSH Service

To load the new configuration, type:

sudo systemctl restart sshd

17. Install Fail2Ban

Install this package, which is designed to limit brute-force attacks:

apt-get install fail2ban

Fail2ban reads its configuration files so that all .conf files are read first and .local files override any settings. Because of this, all changes to the configuration are generally done in .local files, leaving the .conf files untouched.

Navigate to the directory with all the configuration files:

cd /etc/fail2ban

Copy fail2ban.conf to fail2ban.local, commenting out all variables, and then uncomment only the options you want to modify:

sed 's/\(^[[:alpha:]]\)/# \1/' fail2ban.conf | sudo tee fail2ban.local 1&> /dev/null

Do the same with the jail configuration file:

sed 's/\(^[[:alpha:]]\)/# \1/' jail.conf | sudo tee jail.local 1&> /dev/null

This will create a copy of jail.conf with all the directives commented out. To overwrite a setting, uncomment and modify it in jail.local.

For more details and advanced customization of fail2ban, check this page.

18. Configure A Firewall

A best practice is to allow only the traffic you need, and deny everything else.

Iptables is the controller for netfilter, the Linux kernel’s packet filtering framework. FirewallD for the Fedora distribution family and UFW for the Debian family are two common iptables controllers, but this section will focus exclusively on iptables.

To view you current iptables rules, run the following command lines:

For IPv4:

sudo iptables -L -nv

For IPv6:

sudo ip6tables -L -nv

Iptables has no rules by default for both IPv4 and IPv6. This means that all incoming, forwarded and outgoing traffic is allowed. Here is how to set appropriate rules for a web server.

Add the rules for IPv4:

nano /tmp/v4

Copy, paste and save the following rules:

*filter

# Allow all loopback (lo0) traffic and reject traffic
# to localhost that does not originate from lo0.
-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -s 127.0.0.0/8 -j REJECT

# Allow ping.
-A INPUT -p icmp -m state --state NEW --icmp-type 8 -j ACCEPT

# Allow SSH connections.
-A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT

# Allow HTTP and HTTPS connections from anywhere
# (the normal ports for web servers).
-A INPUT -p tcp --dport 80 -m state --state NEW -j ACCEPT
-A INPUT -p tcp --dport 443 -m state --state NEW -j ACCEPT

# Allow inbound traffic from established connections.
# This includes ICMP error returns.
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Log what was incoming but denied (optional but useful).
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables_INPUT_denied: " --log-level 7

# Reject all other inbound.
-A INPUT -j REJECT

# Log any traffic that was sent to you
# for forwarding (optional but useful).
-A FORWARD -m limit --limit 5/min -j LOG --log-prefix "iptables_FORWARD_denied: " --log-level 7

# Reject all traffic forwarding.
-A FORWARD -j REJECT

COMMIT

Add the rules for IPv6 :

nano /tmp/v6

Copy, paste and save:

*filter

# Allow all loopback (lo0) traffic and reject traffic
# to localhost that does not originate from lo0.
-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -s ::1/128 -j REJECT

# Allow ICMP
-A INPUT -p icmpv6 -j ACCEPT

# Allow HTTP and HTTPS connections from anywhere
# (the normal ports for web servers).
-A INPUT -p tcp --dport 80 -m state --state NEW -j ACCEPT
-A INPUT -p tcp --dport 443 -m state --state NEW -j ACCEPT

# Allow inbound traffic from established connections.
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Log what was incoming but denied (optional but useful).
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "ip6tables_INPUT_denied: " --log-level 7

# Reject all other inbound.
-A INPUT -j REJECT

# Log any traffic that was sent to you
# for forwarding (optional but useful).
-A FORWARD -m limit --limit 5/min -j LOG --log-prefix "ip6tables_FORWARD_denied: " --log-level 7

# Reject all traffic forwarding.
-A FORWARD -j REJECT

COMMIT

Import the rulesets into immediate use:

sudo iptables-restore < /tmp/v4
sudo ip6tables-restore < /tmp/v6

Check your Linode’s firewall rules with the v option for a verbose output:

sudo iptables -vL
sudo ip6tables -vL

That’s it: your web server is now up and running and has implemented basic security rules. You’ll then need to complete the web stack, either with NGINX or Apache.

To continue with a LEMP web stack (Ubuntu 16.04, Nginx, MySQL, PHP), check this guide: Installing nginx, MySQL and PHP on an Ubuntu 16.04 Web Server.