Prerequisites

You will need a domain name to follow along in this guide. A subdomain is perfectly fine, in fact we will be using pluto.turnipjuice.media for this tutorial. You will also need access to update your domain’s DNS. We highly recommend using Cloudflare’s DNS service and they’re a pretty good place to buy domains too.

Create a New DigitalOcean Droplet Web Server

In this tutorial I’m not going to go into detail on the initial VPS creation process, as DigitalOcean has their own doc. However, here are some things you should keep in mind when creating your new DigitalOcean Droplet:

  • Select a Region and Datacenter that’s close to the majority of your audience so that the requests are fast for them. If your audience is evenly distributed across the globe, select a Region and Datacenter close to you.
  • Choose Ubuntu 24.04 (LTS) x64 as your OS.
  • A Regular CPU will work fine for this tutorial. No need to go Premium.
  • Select a server size with at least 2 GB of memory.
  • Click the Password option under Choose Authentication Method.
    Normally we would strongly encourage the use of an SSH key instead of a password, but we’ll be disabling the root login shortly, so using a secure password here for a short time is ok and makes things a little easier.
  • Check the box next to Add improved metrics monitoring and alerting (free).
  • For the Hostname enter the domain or subdomain we discussed in the Prerequisites section above.

DigitalOcean create Droplet screen

First SSH Login

Before we can install the web server software (e.g., PHP, MySQL database, etc) required for a WordPress installation, we first need to configure a few things on the server. We’ll start by logging into the server via SSH. If you’ve never SSH’ed into a server before, you may want to check out our Beginner’s Guide to SSH before proceeding.

ssh root@178.62.70.190

You’ll be asked to enter a password. Enter the password you provided when creating the Droplet in the previous step.

abe@Abes-MBP:~$ ssh root@178.62.70.190
Welcome to Ubuntu 24.04 LTS (GNU/Linux 6.8.0-36-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Thu Jun 27 12:26:22 EDT 2024

  System load:  0.0               Processes:             110
  Usage of /:   3.9% of 47.39GB   Users logged in:       1
  Memory usage: 10%               IPv4 address for eth0: 104.236.70.190
  Swap usage:   0%                IPv4 address for eth0: 10.17.0.5

Expanded Security Maintenance for Applications is not enabled.

17 updates can be applied immediately.
13 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status

Set a Hostname

Now that you’re logged into the server, let’s set the hostname and fully qualified domain name (FQDN). The hostname should be unique but doesn’t require any relationship to the sites that will be hosted, for example, some people opt to name their servers after astronomical objects.

Correctly setting the hostname and FQDN will make connecting to your server much easier in the future as you won’t have to remember the IP address each time. To set the hostname, issue the following commands (altered for your chosen domain name):

hostnamectl hostname pluto.turnipjuice.media

In order to connect to the server using your hostname you need to update your domain name’s DNS settings. Log into your DNS control panel and create a new A record:

Make sure that the A record matches the hostname you configured on your web server and that the IP address of the web server is associated with your domain name. You may need to wait a while for the DNS settings to propagate.

If you’re using Cloudflare for your DNS, make sure to toggle OFF the proxy switch.

Once the DNS settings have propagated, if you exit out of the current SSH session you should be able to connect to the server using the new hostname.

ssh root@pluto.turnipjuice.media

Set the Timezone

DigitalOcean will default the new server setup to the same timezone as the data center region. To set the server timezone you must configure the tzdata package. This will ensure that the system log files show the correct date and time. The following command will allow you to configure the tzdata package:

dpkg-reconfigure tzdata

A simple GUI will be displayed, allowing you to select your geographic area and time zone:

Once completed, the newly selected timezone will be displayed along with the current time and date:

root@pluto:~# dpkg-reconfigure tzdata

Current default time zone: 'America/New_York'
Local time is now:      Fri Apr  5 13:03:04 EDT 2024.
Universal Time is now:  Fri Apr  5 17:03:04 UTC 2024.

Install Software Updates

Although you have only just provisioned your new server, it is likely that some software packages are out of date. Let’s ensure you are using the latest software by pulling in updated package lists:

apt update

Once completed, let’s update all of the currently installed packages.

apt dist-upgrade

It is recommended to use apt dist-upgrade vs. apt upgrade because it will intelligently handle dependencies.

You will be shown a list of the packages that will be updated, how much disk space will be used, and a prompt asking if you’d like to continue with the updates. Hit Enter to continue with the updates.

When the upgrades have completed you will be shown which packages have been installed, and also which packages are no longer required by the system.

You can remove the outdated packages by issuing the following command:

apt autoremove

It’s a good idea to reboot the server at this point. Run the following command:

reboot now

This will disconnect you from the server. You will need to wait until the server reboots before you can connect again via SSH:

ssh root@pluto.turnipjuice.media

Automatic Security Updates

It’s vitally important that you keep your server software updated so that software vulnerabilities are patched. Thankfully, Ubuntu can automatically perform software updates, keeping your server secure. It’s important to remember that this convenience can be quite dangerous and it’s recommended that you only enable security updates. This will automatically patch new vulnerabilities as they are discovered.

Non-security software updates should be tested on a staging server before installing them so as not to introduce breaking changes, which could inadvertently take your WordPress websites offline.

On some systems, this feature may automatically be enabled. If not, or you’re unsure, follow the steps below:

Install the unattended-upgrades package:

apt install unattended-upgrades

Create the required configuration files:

dpkg-reconfigure unattended-upgrades

You should see the following screen:

Choose “Yes” and hit Enter. Then, edit the configuration file:

nano /etc/apt/apt.conf.d/50unattended-upgrades

If you’re not familiar with editing files on the command line with nano, you might want to check out our tutorial How to Easily Edit Files Over SSH with Nano. If you’re already proficient with vim or some other command line editor, by all means use that instead.

Ensure that the security origin is allowed and that all others are removed or commented out. It should look like this:

// Automatically upgrade packages from these (origin:archive) pairs
//
// Note that in Ubuntu security updates may pull in new dependencies
// from non-security sources (e.g. chromium). By allowing the release
// pocket these get automatically pulled in.
Unattended-Upgrade::Allowed-Origins {
            "${distro_id}:${distro_codename}";
            "${distro_id}:${distro_codename}-security";
            // Extended Security Maintenance; doesn't necessarily exist for
            // every release and this system may not have it installed, but if
            // available, the policy for updates is such that unattended-upgrades
            // should also install from here by default.
            "${distro_id}ESMApps:${distro_codename}-apps-security";
            "${distro_id}ESM:${distro_codename}-infra-security";
//          "${distro_id}:${distro_codename}-updates";
//          "${distro_id}:${distro_codename}-proposed";
//          "${distro_id}:${distro_codename}-backports";
};

Save the file using CTRL + X and then Y.

You may also wish to configure whether or not the system should automatically restart if it’s required for an update to take effect. The default behavior is to restart the server immediately after installing the update. To disable this completely, find the following line and uncomment it:

Unattended-Upgrade::Automatic-Reboot "false";

You can also replace false with a time if you’d like the server to be restarted automatically at a specific time:

Unattended-Upgrade::Automatic-Reboot-Time "04:00";

If your server does restart you must remember to start all critical services. By default Nginx, PHP and MySQL will automatically restart, but check out this Stack Overflow thread on how to add additional services if needed.

Finally, set how often the automatic updates should run:

nano /etc/apt/apt.conf.d/20auto-upgrades

Ensure that Unattended-Upgrade is in the list.

APT::Periodic::Unattended-Upgrade "1";

The number indicates how often the upgrades will be performed in days. A value of 1 will run upgrades every day.

Once you’ve finished editing, save the file using CTRL + X and then Y and restart the service to have the changes take effect:

service unattended-upgrades restart

Create a New User

We’ve finished configuring the web server basics and security updates. The next step in this tutorial is adding a new user to your server. This is done for two reasons:

  1. Later in this chapter we are going to disable SSH access for the root user, which means you need another user account in order to access your server
  2. The root user contains very broad privileges which will allow you to execute potentially destructive commands. Therefore it’s advised to create a new user account with more limited permissions for day-to-day use.

This new user will be added to the sudo group so that you can execute commands which require heightened permissions, but only when required.

First, create the new user:

adduser abe

You’ll be prompted to enter a password, then some basic user information. As mentioned previously, this password should be complex:

root@pluto:~# adduser abe
Adding user `abe' ...
Adding new group `abe' (1000) ...
Adding new user `abe' (1000) with group `abe' ...
Creating home directory `/home/abe' ...
Copying files from `/etc/skel' ...
New password: 
Retype new password: 
passwd: password updated successfully
Changing the user information for abe
Enter the new value, or press ENTER for the default
    Full Name []: Abe
    Room Number []: 
    Work Phone []: 
    Home Phone []: 
    Other []: 
Is the information correct? [Y/n] 

Next, you need to add the new user to the sudo group:

usermod -a -G sudo abe

Now ensure your new account is working by logging out of your current SSH session and initiating a new one:

logout

Then login with the new account:

ssh abe@pluto.turnipjuice.media

Generating a Key Pair

At this point, your new user is ready to use. For enhanced security, you are going to set up public key authentication. As you’re planning to configure WordPress on this server, it means it’s going to be publicly accessible, and therefore a possible target for attackers. It’s important to lock it down as best we can.

First, we’re going to need an SSH key pair, public and private keys. You may already have a generated them previously. If you haven’t generated an SSH key pair before, you might want to check out our Beginner’s to SSH for an in-depth explanation.

To create a key pair, enter the following command in your computer’s terminal (not the remote server):

ssh-keygen -t ed25519 -C "abe@laptop"

Replace “your_email@example.com” with something to help you identify this SSH key (it doesn’t have to be an email address).

You should receive a message as I have below, just hit return to accept the default location. You’ll then be prompted to enter a passphrase (optional), which will require you to enter a password every time you log in with this key pair:

abe@Abes-MBP:~$ ssh-keygen -t ed25519 -C "abe@laptop"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/Users/bradt/.ssh/id_ed25519): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /Users/bradt/.ssh/id_ed25519
Your public key has been saved in /Users/bradt/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:6zrqae1MT26zBVOHzGWVWJxH8xjrWr8TWM8io6Qsdx8 abe@laptop
The key's randomart image is:
+--[ED25519 256]--+
|            o=+=.|
|         o +. +++|
|          = . o..|
|         . . . . |
|        S     =..|
|         +. o+.oo|
|     ...oo..Eo .o|
|    .++=*.o  . ..|
|   o+o+*=+ ..  ..|
+----[SHA256]-----+

Copy the Public Key

Now that you have your SSH key pair, you need to copy the public key to your server. First, let’s create a place for it on the server. Go back to the SSH session to your remote server, ensuring you are logged in with the newly created user. Now create the .ssh directory and set the correct permissions:

mkdir   ~/.ssh
chmod 700 ~/.ssh

Within the .ssh directory create a new file called authorized_keys:

nano ~/.ssh/authorized_keys

Now switch back to your computer’s terminal (not the remote server). Assuming you saved the key in the default location, the following command will copy the key to your clipboard:

cat ~/.ssh/id_ed25519.pub | pbcopy

Switch back to the remote server terminal and paste your public key into the authorized_keys file. Save the `file using CTRL + X and then Y. Finally, set the correct permissions on the file:

chmod 600 ~/.ssh/authorized_keys

Now if you log out of the current SSH session and try reconnecting, you should no longer have to enter your user password. Remember, if you set a passphrase when creating the SSH key, you will need to enter it when prompted.

For the rest of this tutorial, you’ll notice I’m using sudo in front of each command, to heighten privileges for this command. This allows my ‘normal’ user to make ‘root’ user level changes.

SSH Configuration

With your new user created, it’s time to further secure the server by configuring SSH. The first thing you are going to do is disable SSH access for the root user, which will no longer let you log into the server via SSH using the root user. Open the SSH configuration file using nano :

sudo nano /etc/ssh/sshd_config

Find the line that reads PermitRootLogin yes and change it to PermitRootLogin no. Hit CTRL + X then Y to save the changes. In order for the changes to take effect you must restart the SSH service:

sudo service ssh restart

Now if you exit out of the current SSH session and try connecting with the root user you should receive a permission denied error message after entering the correct password for the root user.

The final step to securing SSH is to disable user login using a password. This ensures that you need your private SSH key to log into the server. Remember, if you lose your private key you will be locked out of the server, so keep it safe! Most virtual machine server providers like DigitalOcean do have other means of logging in, but it’s best not to rely on those methods:

sudo nano /etc/ssh/sshd_config

Find the line that reads #PasswordAuthentication yes and change it to PasswordAuthentication no. Hit CTRL + X then Y to save the changes. Once again, you must restart the SSH service for the changes to take effect.

sudo service ssh restart

Now, before you log out of your server, you should test your new configuration. To do this open a new terminal window, without closing the current SSH session and attempt to connect:

ssh abe@pluto.turnipjuice.media

You should log in to the server successfully. To further test that password authentication is disabled, temporarily rename the SSH key located in my .ssh directory. When attempting to log into the server this time you should receive a Permission denied (publickey) error.

If you’re still able to login with a password, there could be an included configuration file that’s overriding the PasswordAuthentication setting. Check the /etc/ssh/sshd_config.d folder to see if there are any configuration files in there:

abe@pluto:~$ sudo ls -la /etc/ssh/sshd_config.d
total 12
drwxr-xr-x 2 root root 4096 Apr  5 14:10 .
drwxr-xr-x 4 root root 4096 Apr  5 14:09 ..
-rw------- 1 root root   27 Apr  5 12:33 50-cloud-init.conf

In this case, there’s one configuration file containing one line PasswordAuthentication yes. Comment out that line or delete the file, restart the SSH service again, and hopefully you get the Permission denied (publickey) error.

Configure Uncomplicated Firewall

The firewall provides an additional transport layer of security to your server by blocking inbound network traffic. I’m going to demonstrate the iptables firewall, which is the most commonly used across Linux and is installed by default. In order to simplify the process of adding rules to the firewall, we use a package called ufw, which stands for Uncomplicated Firewall. The ufw package is usually installed by default, but if it isn’t go ahead and install it using the following command:

sudo apt install ufw

Now you can begin adding to the default rules, which deny all incoming traffic and allow all outgoing traffic. For now, add the ports for SSH (22), HTTP (80), and HTTPS (443):

sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https

To review which rules will be added to the firewall, enter the following command:

sudo ufw show added
abe@pluto.turnipjuice.media:~$ sudo ufw show added
Added user rules (see 'ufw status' for running firewall):
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp

Before enabling the firewall rules, ensure that the port for SSH is in the list of added rules – otherwise, you won’t be able to connect to your server! The default port is 22. If everything looks correct, go ahead and enable the configuration:

sudo ufw enable

To confirm that the new rules are active, enter the following command:

sudo ufw status verbose

You will see that all inbound traffic is denied by default except on ports 22, 80, and 443 for both IPv4 and IPv6, which is a good starting point for most servers.

abe@pluto.turnipjuice.media:~$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere                  
80/tcp                     ALLOW IN    Anywhere                  
443/tcp                    ALLOW IN    Anywhere                  
22/tcp (v6)                ALLOW IN    Anywhere (v6)             
80/tcp (v6)                ALLOW IN    Anywhere (v6)             
443/tcp (v6)               ALLOW IN    Anywhere (v6)             

Install Fail2ban

Fail2ban is a tool that works alongside your firewall. It functions by monitoring intrusion attempts to your server and blocks the offending host for a set period of time. It does this by adding any IP addresses that show malicious activity to your firewall rules. It’s highly recommended to install something like Fail2ban on your servers that will be running a WordPress configuration in order to secure and protect your web server, especially if you intend to install any third-party plugins.

The Fail2ban program isn’t installed by default, so let’s install it now:

sudo apt install fail2ban

The default configuration should suffice, which will ban a host for 10 minutes after 6 unsuccessful login attempts via SSH. To ensure the fail2ban service is running enter the following command:

sudo service fail2ban start

And to check that it’s running, run the status command:

abe@pluto:~$ sudo service fail2ban status
● fail2ban.service - Fail2Ban Service
     Loaded: loaded (/lib/systemd/system/fail2ban.service; disabled; vendor preset: enabled)
     Active: active (running) since Fri 2024-04-05 14:22:32 EDT; 12s ago
       Docs: man:fail2ban(1)
   Main PID: 2398 (fail2ban-server)
      Tasks: 5 (limit: 2309)
     Memory: 14.0M
        CPU: 1.062s
     CGroup: /system.slice/fail2ban.service
             └─2398 /usr/bin/python3 /usr/bin/fail2ban-server -xf start

Apr 05 14:22:32 pluto.turnipjuice.media systemd[1]: Started Fail2Ban Service.
Apr 05 14:22:32 pluto.turnipjuice.media fail2ban-server[2398]: Server ready

Job done! You now have a good platform to begin building your WordPress web server and have taken the necessary steps to prevent unauthorized access. However, it’s important to remember that security is an ongoing process and you should keep in mind the following points:

  • Only install server software (e.g. Nginx/Apache, PHP, MySQL/MariaDB) only from trusted sources
  • Regularly install software updates and security fixes
  • Enforce strong passwords using a tool such as 1Password
  • Think about how you would gain access to the server if you were locked out

That’s all for chapter 1. Later on in this guide, we’ll cover things like obtaining a Let’s Encrypt SSL certificate and setting up automated remote backups among other things. However, in the next chapter, I will guide you through installing Nginx, PHP-FPM, and MySQL.