Setting up and securing a Ubuntu virtual private server

In this guide, I will show you how to obtain access to a dedicated web server, configure the server for SSH, and secure the server from unauthorized access. The guide is written from the perspective of a Windows user, but nearly all of the steps will be identical for those connecting from a Mac or Linux system.


Requirements:

Before beginning you will need to have the following:

  • A host machine with an Internet connection and an open firewall
  • Access to the terminal or command prompt
  • A text editor (I recommendNotepad++


Cloud provider and server:

There are hundreds of cloud service providers available and each has their own mix of plans, prices, features, and support. For web hosting, there are two broad categories of service types: managed and non-managed. In a managed web service, the service provider will have a high level of control over the website. They will often have a web-based editor for interacting with the web server, automated builds for deploying and creating a website, and even templates or visual HTML editors. When using a managed provider, you will primarily interact with your website and web host using the provider’s web-based control panel. A non-managed host will do none of this for you. Instead, you will be provided direct access to the web server and you must perform most of the setup and configuration manually. It is this second type of web host that we are interested in.

Another general distinction when looking at cloud services is the hardware configuration and virtualization provided. In a shared hosting environment, multiple users all share the same hardware resources and operating system. This typically occurs in a Unix environment, with each user having access only to his or her specified directories. While some shared providers allow the user to install applications locally, most server administration functions are handled by the provider. When full control over the server is needed, you must use either a dedicated or virtual host. In both cases, each individual customer has a server that is fully isolated from other users, and the user has full administrative control over the server. The cloud services provider implements an interface that simulates hardware control over of the server. The only difference between a dedicated and virtual host is the actual underlying hardware. A dedicated host will run on dedicated physical hardware, whereas a virtual host will run inside a virtual container. A single physical server typically only has one dedicated host, but may have multiple virtual hosts.

Virtual hosting is by far the most common type of cloud services being provided. Virtualization technology has reduced the overhead to an almost negligible amount. In addition, virtual hosting allows for on-the-fly hardware reconfiguration. Memory, disk space, even processors cores can often be added and activated within minutes. In some cases, without even the need to restart the virtual server. Amazon AWS, Microsoft Azure, and Google Cloud Platform all provide virtual servers. Digital Ocean is a cloud provider that offers both virtual and dedicated hosting, and they are the provider that I would recommend for small to medium scale projects. Another good choice is 1&1.

The rest of this guide will assume that Digital Ocean is the chosen cloud provider, but as long as you have direct access to your server, the steps will be identical for others. When selecting a virtual host the monthly or yearly fee will be determined by the needed hardware and resources. For a small or even medium website, the required hardware is minimal, and the base hosting package should be fine. At the time of this writing, Digital Ocean was offering 1GB of memory, 1 CPU, 30GB of storage, and 2TB of data transfer for $10 / month.


Spinning up the server

Once a hosting plan is purchased, there will be two ways of interacting with the server. The first is through the cloud provider’s web interface. This will be used for configuring things related to the hosting account, and for performing tasks that would otherwise require physical access to the server, such as turning on or shutting down hardware, inserting a disk or CD image, and configuring an external firewall. For all other tasks, you will connect to your server using the SSH protocol and interact with it using a remote terminal.

Before a virtual server can be used, an operating system must be installed. Digital Ocean provides numerous choices for operating systems, almost all of which are Unix based. Two of the most popular choices are Fedora (the public version of Red Hat Enterprise) and Ubuntu. While Fedora and RedHat are more common on servers, this guide will be based on an Ubuntu install. While experienced Linux users will be able to follow this guide for a RedHat environment, significant differences exist between the two operating systems. Unless you are familiar with both environments, it is highly recommended that you choose Ubuntu for your server OS. This guide was written and tested on Ubuntu 16.04.3, but all of the content should be fully compatible with 17.04 as well.

Digital Ocean refers to their virtual servers as droplets. Setting up a droplet and installing Ubuntu on it is very simple. Log into your account, select the Droplets link, and follow the prompts to add a new server. As the operating system is stored as a full image, the setup process should take less than a minute. As part of the process, you will be asked to either provide a root account password or to have the root password generated randomly and emailed to you. It is very important that the root password is kept secure. Later we will disable remote logins using the root account, but for now, anyone with the root password will have full control over your server and website. Once your droplet setup is complete, make note of your server’s IP address and root password.

Digital Ocean and most other cloud providers charge you only for the time your server is actually enabled. Having your droplet enabled and having it actually running are two different things. If you know that you are not going to need to use your host for some time, you can disable the droplet using the Digital Ocean control panel and you will not be billed for the period that it’s inactive. However, simply shutting down your virtual server is not enough to prevent billing.


Connecting via SSH:

SSH (or Secure SHell) is a protocol for allowing secure, encrypted terminal access to a host. SSH is the primary way in which remote Linux servers are accessed (another option is a graphical remote desktop connection, but this far less common). Once configured, connecting to your server via SSH is nearly indistinguishable from having a shell opened locally. The Ubuntu configuration offered by Digital Ocean has SSH enabled by default and all of the necessary software on the server has already been installed. However, you will need software on your client computer that supports SSH. If you are running Linux or Mac, you will likely already have the SSH client software installed. On Windows, you will need to install additional software. For a purely command line approach, you can install Microsoft’s Linux Subsystem for Windows. This will provide you with the ability to run a full Linux Bash shell from within Windows. The shell comes with the ssh application installed. However, on Windows systems, I would recommend installing a free SSH client called Bitvise SSH. In addition to providing a very nice terminal window, Bitvise also features a graphical file explorer for transferring files to and from your computer and the remote server. The software also allows you to create connection profiles, securely store password and keys, and customize almost every aspect of your remote connection.

To connect to your server via SSH will need three pieces of information. The IP address of your server, the user account you want to connect with, and the password for that account. As this is the first time you are connecting, the root account and password must be used. The command to connect to a remote machine using SSH is:

ssh {user}@{remote-host}

If the IP address of your server happened to be 0.0.0.0, then you would issue the command ssh root@0.0.0.0. By default, ssh uses port 22. If this is the first time you have made an outgoing SSH connection from your local computer, you may receive a firewall warning asking you to allow the connection. If your server is pre-configured to use a different port number, then you will need to specify the port number by using the -p option: ssh {user}@{remote-host} -p {#port}.

The first time you connect to a new remote server, your client software will notify you that the identity of the server you are connecting to can’t be verified, and will ask you if you want to trust the server and add it’s certificate to your trust store. This happens because SSH uses public key encryption to establish a secure connection. However, unless your server was pre-setup with a certificate signed by a trusted authority, your client computer can’t know for sure that the server really is what and whoit says it is. Although very unlikely, it is possible that a man-in-the-middle attacker is intercepting all communication between you and the server and is passing you a fake certificate, backed by the attacker’s private key. If this is a concern, you can manually copy the public certificate directly from the server and then import it, but this is unnecessary for most security needs. Once you have trusted the server’s public certificate, the SSL client software will use that certificate to verify all future communications. Since only the server has the corresponding private key, both the integrity and authenticity of messages can be trusted.

After connecting to the remote server, you will be prompted for the user password. Once authorized, you will be logged in as root and the default shell will start. On most Ubuntu installs, the default shell is Bash. You can confirm this with the following commands.

echo $0         // When run from a terminal, displays the current shell.
echo $SHELL     // Displays the default shell for the current environment

The choice of a shell is often a personal preference. Most of the commands in this guide will work with any shell, but some of the shortcuts are Bash specific. If you are not inside of bash, you can switch shells by simply executing the command bash.

Securing the server:

As this was the first time we logged into the remote server, we were required to use the root user account. However, continued use of the root account is a huge security risk and should be avoided. One of our first tasks will be to create a new user account that we will use when connecting. In addition, we will need to make some additional changes to the SSH configuration to increase security. The following steps will go a long ways towards hardening the server from most common attacks.

  1. Create a non-root, administrative user
  2. Create a SSH keyfile that we will use to connect to the server instead of a password
  3. Disable password logins for SSH connections to the server
  4. Change the SSH port the server uses and configure a firewall
  5. Install automated software that will detect and block repeated (brute force) login attempts

Creating a non-root account

The first step in securing our server will be to create a non-root account that we will use for all future SSH sessions. We will make this user a member of the sudo group, which will enable the user to perform commands as if they were root, but without needing to actually switch accounts. Any command that needs the permission of the root user can be run on our new account by prefixing the command with sudo. Requiring an explicit command each time we want to do something as a root user follows the security principal of least access. A user or other entity should always have the minimum amount of access needed to perform a task or operation. The principle of least access is the cornerstone of the Linux security model.

The following is the general form of the command to create a new user and assign the user to an existing group.

adduser {new-username} --ingroup {existing-group}       // general form

adduser admin --ingroup sudo                            // example

After running the command you will be prompted to fill in some additional details for the user, such as their name, phone number, and location for their home directory. The default (blanks) for all of the options are fine, although if you plan on having multiple remote users you may want to fill in some of the details. Finally, you will be prompted to create a password for the new user. Since the new account is part of the sudo group, this password gives the new user root-level access. It is important that you select a strong password. However, keep in mind that you will be entering this password a lot, so it should also be something you are comfortable typing.

Once the adduser program finishes, you will see a prompt telling you the new user account has been created. You can test that everything went smoothly by switching to the new account and trying some commands.

  • Switch to the new account with the su (switch user) command. If you named your new account “admin” you would type su admin. Since you are currently using the root account, you will not be prompted to enter a password. Instead, you will see that the prefix to your shell environment line will have changed to “admin@something”.
  • Verify that you do not have root access by attempting to list the contents to the root user’s directory with a ls /root command. You should receive a “permission denied” message.
  • Now, run the same command, but this time with a sudo prefix (you can hit the [up] arrow on the keyboard to bring up the previous command). The full text of the new command would be sudo cd /root. You will be prompted to enter a password. This is the password for the current account (admin), not the root account. Note, that if the /root directory is empty, the output from the command will be blank, but you will not get a “permission denied” error.
  • Switch back to the root user by issuing a sudo su root command. Note that you will likely not be prompted to enter your password again. The default policy in Ubuntu is to only require a sudo password once every fifteen minutes. If you want more (or less) time, this value can easily be changed. See this Life Hacker article for details.
  • If things go well, we will never need to directly use the root account again, so take one last look around then change back to your new admin account with su admin.

The rest of this guide will assume that you are logged in with a non-root account and that the name of this account is admin. However, any name can be used as long as it doesn’t conflict with an existing user.

Command reference:

adduser {new-username} --ingroup {existing-group}       // add a new user and make them part of an existing group
su {user}                                               // swtich (become) a different user
sudo {command}                                          // run a command as if you were a root user, but without having to actually switch
ls {path}                                               // list the contents of a directory

groupadd {new-group}                                    // create a new group
adduser {existing-user} {existing-group}                // add an already created user to an already created group

who                                                     // show the account and date/time that the  current user originally
                                                           logged in as (may be different than the active user if su was used)
groups                                                  // show the groups that the current user is a member of


Creating an SSH keyfile for authentication

The SSH protocol allows for a number of means of authentication. The simplest is using the password belonging to the specified user account. This is enabled by default, and now that we have a new user, we could log in to a new SSH session using the new username and password. However, SSH provides a much better means for user authentication by way of a key file. A key file is a randomly generated string of bytes that is almost certainly unique. The full key is actually composed of two parts that are mathematically linked: a private and a public portion. The public portion is stored on the server. When a client attempts to log in, the server first generates a random message, encrypt the message using the client’s public key, then sends the encrypted message to the client. The client must then use his or her private key to decrypt the message and send it back to the server for verification. Since only the private key can be used to decrypt the message, the server knows that the client is authentic and allows it to log in. Using a key file is more secure than a password since the key used for encryption is much longer and nearly impossible to guess with brute force. The downside to a key file is that just like a set of physical keys if a key file is lost the user will not able to log in. To further enhance security, the private portion of a key file is typically encrypted with a passphrase. When the client SSH software needs to use the key file, it must first prompt the user for the passphrase so that the file can be decrypted. This effectively means that the SSH session now requires two-factor authentication. The client must both possess the physical file and must know the passphrase necessary to decrypt and use the file. Both of these pieces must be compromised for an attacker to gain access to the system.

The first step to enabling key file logins is to generate the public and private keys. This should always be done on the client since a private key should never be transmitted over the Internet. Generating the two keys is very easy and can be accomplished either from a command shell or using key management software (such as the key manager built into Bitvise SSH). Most Mac and Linux systems have the ssh-keygen tool installed by default. Windows users will either need to use a Bash-like application or the key tool built into Bitvise SSH or PuTTY. At the start of this guide, I recommended that Windows 10 users install the Linux Subsystem for Windows, which fully replicates a Linux shell. A lighter weight choice is to install Git Bash. Git is a popular version control repository and the Windows version of Git comes with a simple bash shell that includes a number of commonly used Linux programs such as ssh and ssh-keygen. You can download Git from git-scm.com/download/win. Once you have a bash or other Linux command prompt open, you can generate the keys with the following command”

ssh-keygen -t rsa       // t for "type"

After executing the command, the utility will prompt you for a file name and location. The default is to place the generated keys in the /home/{username}/ directory. You can accept the default or enter a new pathname. If you want the generated keys to be saved in the current directory, enter ./id_rsa for the file name. The program will then prompt you to enter a passphrase that will encrypt the private key. This passphrase should be different from the password for the user account. Once the keys have been generated, navigate to the directory they were placed with cd {directory-name}, and then list the contents of the directory with ls -l. Unless you changed the filename, you should see two similarly named files: is_rsa, which is the private key portion, and id_rsa.pub, its public counterpart.

Now that the key pair has been generated, the next step is to copy the public key portion of the file to the remote server. This can be done either manually, or by using the ssh-copy-id tool. Try using the tool first, and if it doesn’t work, read on for manual instructions. The format of the copy-tool is:

ssh-copy-id -i {public-key-file.pub} {user@host}

If you didn’t change the default filename or location, the command would be ssh-copy-id -i ./id-rsa.pub admin@0.0.0.0. The utility will then prompt you to enter the password associated with the user account. If the utility is able to establish an SSH connection, it will append the public key from the selected file to the list of public SSH keys stored in the user’s home directory on the server. On Ubuntu, the location of this file is ~/.ssh/authorized_keys.

If the copy-id utility fails or isn’t available on your system, then you will need to copy the public key manually.

  1. Open the id_rsa.pub inside a text editor (or use nano ./id_rsa.pub to open it in the terminal)
  2. Copy the entire contents of the file to your clipboard, including the blank line at the end.
  3. On the server, run nano ~/.ssh/authorized_keys
  4. to open the authroized files in the Nano text editor.

  5. Use the arrow keys to navigate to the blank line at the end of the document (or the first line if it is empty
  6. Paste the contents of the clipboard by pressing [shift]+[insert]. (This works for most, but not all terminals)
  7. Save the contents of the file by pressing [ctrl]+[O], then [enter] to accept the filename
  8. Exit nano with [ctrl]+[x]

Once the public key has been copied to the server, you can use a new SSH connection to test the key file authentication. Make sure to use a new session for testing, as misconfigurations can sometimes cause you to lock your account or even all accounts out of ssh. To log into an SSH session utilizing your private key, you will use the -i {private-key-file} option (i for identity). The full command is:

ssh admin@0.0.0.0 -i id_rsa

You will then be prompted to enter the passphrase associated with the key. If the public portion of the key was successfully copied to the server, your account will be authenticated and you will be logged in. Pay careful attention to the messages displayed and make sure that key authentication was accepted.

If you are using Bitvise SSH then you will need to import the key file into Bitvise before it can be used.

  1. From the main Bitvise SSH window, select the link for “Clinet key manager”
  2. Select Import and navigate to the directory where the key pair is stored. (You may need to change the displayed file types to “all”
  3. Select the private key (id_rsa), press open, and enter the passphrase for the key
  4. Assign the key to one of the numbered profiles and add any optional comments
  5. When back at the main window, change the Initial Method drop-down selectio to “publickey+password”
  6. Select the numbered option from the Client Key drop-down and then enter the passphrase for the key.

Once you have successfully established a new ssh connection using the key file you can safely close the old session. From now on it is assumed that you will use key authentication for all connections

Command reference:

ssh-keygen -t rsa                           // generate a linked pair of RSA keys
ssh-copy-id -i {public-key} {user@host}     // copy the public SSH key to the remote user's account
ssh admin@0.0.0.0 -i id_rsa                 // open a new SSH session using the specified keyfile for authentication

[shift] + [insert]                          // paste the contents of the clipboard into Nano (and most other editors)
[right-click]                               // paste the contents of the clipboard into the current terminal

[ctrl] + [x]                                // Exit nano. You will be prompted to save the file if changes were made
[ctrl] + [w]                                // Write (save) to to a file


Disabling password login for SSH authentication

Now that we have a key file and verified that it can be used, we need to disable the password method for SSH logins. This step is crucial, as having the security of key file authentication e is no good unless we require that it be used.

As with most programs in Linux, the ssh service is configured through a text file. The file follows its own sort of syntax for entering keywords and values. The default configuration does a fairly good job of securing the server and only a couple of small changes are needed. The location of the file we want to edit is /etc/ssh/sshd_config. In general, the /etc folder contains configuration files for most multi-user programs and services. Ubuntu comes installed with a number of text editors including Vi or Vim, as well as Emacs. However, we are going to continue with using Nano for simple changes. For security, standard users don’t have write access to files located in the /etc directory, so we will need to use sudo to open the file for writing.

sudo nano /etc/ssh/sshd_config          // as a root user, open the SSH config file in Nano

To disable password authentication we are going to add a line to the file. The line can be added anywhere, but for organizational purposes, it makes sense to add it under the # Authentication section located near the beginning of the file. The line we want to add is

PasswordAuthentication no

In a default SSH configuration, the PasswordAuthentication setting will either not be present or will be prefixed with a #, meaning that is will be treated as a comment and will have no effect. However, when adding new lines to a configuration file it is always a good practice to search the file for a similar line that might conflict. To search the file in Nano, press [ctrl] + [W], then enter the search text “Password” and hit [enter]. By default, the search is not case-sensitive. Nano will search for the given string, and if found, will position the cursor at the match. To move to the next occurrence of the term, press [alt] + [W]. Continue advancing the search until you confirm that no other “PasswordAuthentication” lines are active. If you find a line that is active, you can either delete it or prefix the line with a ‘#’ to turn it into a comment.

Adding the line PasswordAuthentication no disables SSH password authentication for all users. This is the most secure option but also poses a risk. If the private keys for all of the sudo users were ever completely lost, there would be no way to access the server remotely. This risk is somewhat mitigated by the service department of our virtual host. In most cases, the service team can access the virtual host directly and can usually restore the SSH configuration file to its default setting. However, if you choose to encrypt the contents of the home folder, then the only choice might be to do a full restore of the server. For this reason, it may be wise to allow password authentication for the root user, but disable it for all other accounts. If you choose to do this it is doubly important that you select a very strong root password, and that you do not log in as the root user unless absolutely necessary. To disable password authentication for all but the root, use the following lines in place of the single line above:

Match all
PasswordAuthentication no
Match User root
PasswordAuthentication yes
Match all

The Match directive is used to define the scope of ALL following commands. The scope is maintained until another Match directive is encountered.

Once the changes to the file are complete, exit Nano with [ctrl] + [X] and confirm that you want to write the changes to disk. If you receive a permission error, double check that you opened the file with sudo. Now that the SSH configuration has been changed, we need to reload the SSH service for the changes to take effect. Use either of the following commands to restart the SSH daemon (while there is a technical distinction between the two commands, for the most part, they can be used interchangeably). Note that restarting the service will not disrupt any existing SSH connections, but it will affect all new SSH connections, so be sure not to disconnect from your current session until you have verified that new logins are accepted.

sudo systemctl restart ssh          // script that restarts the SSH service
sudo service ssh restart            // wrapper command that restarts the SSH service

Now, let’s make sure that new configuration is being enforced. From the client machine, run the command ssh admin@0.0.0.0 to try to create a new SSH session using password authentication. If the configuration changes were correct, you will quickly receive a Permission denied (publickey). response, informing you that the only supported authentication method is with a key file. Next, confirm that you can still connect using the key file by running the previous command of ssh admin@0.0.0.0 -i id_rsa. Once you have successfully logged in, you can safely close any other open SSH sessions. From now on, you should only use a passphrase-protected key file for logging in to your server.

Command Reference:

/etc/ssh/sshd_config                    // file containing most SSH settings
sudo nano /etc/ssh/sshd_config          // open the config file for editing

sudo systemctl restart {service         // script that restarts a service
sudo service ssh restart {service}      // wrapper command that restarts a service

sudo systemctl reload {service}         // reloads a service without shutting it down, not supported by all services
sudo service {service} reload           // wrapper command for reloading a service

[ctrl] + [X]                            // Nano: exit
[ctrl] + [O]                            // Nano: write (output) file to disk
[ctrl] + [W]                            // Nano: search a file for a (w)ord or string
    [alt] + [W]                            // Nano: go to the next occurence of a given (w)ord or string
[shft] + [INS]                          // Paste the contents of the clipboard


Changing the SSH port number

By default, the SSH service on the host listens for incoming connections on port 22. This makes it easy for clients to connect since they know what the standard port number is. This also poses a security risk. Many attempts at intrusion begin with a port scan. An attacker will use a tool such as nmap that will scan a host for any open ports. Since port numbers 0 – 1023 are reserved for well-known services (such as http, smtp, and ssh), it is common for an attacker to scan hundreds or thousands of different hosts, looking for any open, well-known ports. Although SSH seems to be a very strong protocol, it is almost inevitable that a vulnerability will be discovered at some point. When it does, attackers will flock to looking for compromised systems running SSH, and they will start by looking for an open port 22. While the risk of port scanning can’t be fully eliminated, it can be mitigated by changing the SSH port number to something outside the well-known range. Port values can take on any integer up to 65335. While an attacker could run a port scan of all 65,336 ports on a host, this takes a significant amount of time, and when performing bulk scans, most attackers will limit scans to the lowered number ports.

Changing the SSH port number goes hand-in-hand with changing or setting up a firewall. Making the actual SSH change is as simple as opening up the ssh configuration file from the previous section and changing the value of the Port statement. However, before changing this value, it is very important to make sure that we update any firewall rules to allow connections on the new port. Without this step, it is easy to lock yourself out of SSH access to your server. Keep in mind that just like changing the authentication method, changing the port number won’t disrupt any existing connections but it will affect all new connections.

The first step is to select a new port number. Pick anything between 1024 and 65335, but preferring numbers towards the middle of the range. Once you have a number selected, we need to test whether that port is open on your server. The best way to do this is to use the nmap tool on a client computer to scan the host. Nmap is installed by default in many versions of Linux. For Windows and Mac users, it can be downloaded as a binary from nmap’s official website. The tool operates entirely from the command line, so once it is installed, open a new terminal and run the following command:

nmap {host-name-or-ip} -p {port}

First, let’s scan the server for port 22. Since we currently have an open SSH session, we know that port is available, and nmap should report its state as open. Typically, you will see one of three possible values for any given port:

  • open: the port is not blocked by a firewall and has an active service listening for connections
  • closed: the port is not blocked by a firewall, but the server is not listening for new connections
  • filtered: the port is blocked by a firewall

Once we have confirmed that port 22 is open, run the same scan but on the new port. You will most likely receive the filtered response, meaning that a firewall is blocking access. If you receive a closed response, then there is no firewall configured for your server. In the unlikely event that the port you selected is open, then another service has already selected that port and you should pick a different number.

On most cloud-based virtual servers there are two sources of firewalls. The first or outer firewall is built into the cloud provider’s infrastructure. This is either a physical hardware firewall or a virtual firewall designed to mimic a physical one. The outer firewall exists fully outside your virtual server, and therefore it must be configured using your cloud provider’s control panel. You will have no access to the outer firewall from within Ubuntu. The second or inner firewall is a software-based firewall that runs on your host server. Ubuntu comes with two choices for a software firewall: iptables and a simplified wrapper called ufw.

The default configuration for Digital Ocean droplets (and most other virtual host providers) is to have only a few ports enabled in the outer firewall and then to have the software firewall disabled by default. Depending on your security needs, you can choose to rely on either one, or both firewalls to protect your server. The advantage of using the outer, virtual firewall is that it can be configured from anywhere using a relatively simple interface. However, changes to the outer firewall take longer to update: anywhere from a few minutes to a half-hour depending on the provider’s infrastructure. The inner, software-based firewall provides a much finer degree of control and configuration changes take effect immediately; however, the interface is a little more complicated.

My recommendation is to use both. Set the outer firewall to allow connections on the common ports you may need (21, 22, 25, 80, 443, 465, 993), and then enable the inner
firewall to block access to everything until you explicitly allow it as needed. This means that you won’t have to make frequent changes to your cloud provider’s firewall and can instead focus on administering your server. If you choose to follow the above recommendation, then you will first need to add the new SSH port to your outer firewall. The process should be straightforward. If you are using Digital Ocean:

  1. Log into your account, select your server, go to the “networking” section, then click on the profile link under the Firewalls section.
  2. You should be on a page listing all of the allowed inbound and outbound connections. Select the Rules tab.
  3. Under “Inbound Rules” select the New Rule drop-down and choose custom
  4. Edit the new row so that the protocol is TCP and the port range contains just the new port number
  5. Select save, and then repeat the process for the outbound section
  6. Select the Droplets tab. You should see a progress bar showing you how long it will take for the new rule to become effective
  7. If your server is not displayed under the Droplets tab, then press the Add Droplets button to select the droplet you want the firewall applied to

Now that the new SSH port has been enabled on the virtual firewall, we need to check if a software firewall has been configured in Ubuntu. Most versions of Linux come with a firewall application called iptables. The service is very robust and allows for highly tuned and customized control over all aspects of the firewall. However, the command syntax and options for using iptables can sometimes be daunting. For that reason, Ubuntu comes with a front-end wrapper called ufw (Ubuntu FireWall) that runs the necessary iptables commands behind the scenes. For our purposes, ufw will work great and so we won’t explore the command details for iptables.

To see if Ubuntu’s firewall is enabled, run the following command: (From here on out, I will not include sudo in the command examples. If you run a command and receive a permission denied error, you will need to rerun the command with the sudo prefix.)

ufw status      // prints whether or not the firewall is enabled, and if it is,
                   the current set of rules

If the status is listed as inactive or disabled, then you can now use nmap to test the state of the new port. Rerun the previous command and confirm that the state of the new port is now “closed”, meaning that it is not being blocked by the firewall, but doesn’t yet have an attached service.

If ufw is enabled, then we will need to add a rule that will allow access to our new port. By default, ufw is configured with a default-deny policy. This means that all ports are blocked, except those that you explicitly allow. However, since this setting can be changed to default-allow, ufw has specific rules for both DENY and ALLOW. In general, if you are using the default settings, then you will not need to add any DENY rules since simply removing an ALLOW rule would have the same effect. Mixing allow and deny rules is often the soruce of firewall connection issues. To add a new rule, you will use the following command structure:

ufw allow [in/out] {port}

The [in] or [out] argument is optional. If ommitted, the new rule will apply to both incoming and outgoing connections. So if the new SSH port number you selected was 3456, then you would issue the commands ufw allow in 3456 and ufw allow out 3456 (when configuring firewalls, it is usually preferable to be as explicit as possible so two commands are used here instead of one).

Once the new rules have been added, rerun the ufw status command to see that the rules are not in effect. Finally, run ufw reload to have the new rule changes become effective. At this point, you should have no firewall based barriers to using your new SSH port. Run nmap one more time and make sure that the new port number is in a closed state. If it is still showing as filtered, then you will need to double-check the configuration of both your outer and inner firewalls.

We are now ready to update the SSH configuration file to switch the service over to the new port. As before, use sudo to open the /etc/ssh/sshd_config file in Nano. Near the top, you will see a line that begins with Port and should have a value of 22. Change this number to your new port number, write the changes to disk, and close the file (see the command reference in the previous section for details on using Nano).

Once the confiuration has been updated, restart the SSH service with service ssh restart. Test the new port number by creating a NEW ssh session. To specify the port number, add the -p {port #} argument to your SSH command, i.e. ssh admin@0.0.0.0 -p 3456. Once you are able to connect to the new port, you can safely close out any sessions using the old port.

The final step is to block access to port 22 (and any other ports you are not actively using) using ufw. To do this, we will delete the rule allowing connections on port 22. We will do this by using the following commands

ufw status numbered         // show a numbered list of all active rules
ufw delete {rule #}         // delete or remove an active rule
ufw reload                  // reload the rules, activating any changes

We want to delete any rules associated with port 22. These will either be listed by port number, or up application name (SSH). To make things simpler, ufw keeps a list
of application profiles. These profiles simply map a service name (ssh in this case) to a well-known port or range of ports (22 for us). Even though we changed the SSH port to a different number, the ufw profile still believes that ssh is running on port 22. So if you see SSH listed in any of the rules, you will need to delete these.

At this point, the only rules that you should have will be an ALLOW rule for incoming and outgoing connections on the newly configured SSH port. You may also see rules for that port number with a (v6) next to it. This means that ufw has configured the rule for both the traditional IPv4 and the new IPv6 protocols. Repeatedly run the ufw status numbered and ufw delete commands until you have the desired set of rules. Double check one last time that you have the new SSH port enabled, and then issue the ufw reload command. You will likely get a warning that activating the changes could cause the current SSH session to be terminated. As long as your current session was started with the new port, the session will not be lost.

Congratulations! You now have a virtual server with very secure SSH access. There is one more optional step that you can take for some bonus security.

Command Reference:

apt-get install nmap        // install nmap on Linux. Works on Ubuntu and most other versions
nmap {host} -p {port}       // scan a host to see get the state of a specified host
nmap {host} -p {1-}1023}    // scan all well-known ports on the given host

ufw status                  // display the status of the firewall, and if enabled, all active rules
ufw status numbered         // display the active rules as a numbered list
ufw enable                  // turn the firewall on and set it to start when the system boots
ufw disable                 // turn off the firewall

ufw allow in {port}         // allow incoming connections on the given port
ufw allow out {port}        // allow outgong connections on the given port
ufw allow {port}            // allow incoming and outgoing connections on the given port

ufw delete {rule #}         // removes the specified numbered rule
ufw delete in {port}        // removes the incoming rule for the specified port
ufw delete out {port}       // removes the outgoing rule for the specified port

ufw reload                  // reloads the ruleset, making any changes active
ufw reset                   // fully resets the firewall; useful if reload doesn't have the desired effect


Further brute-force protection (optional)

If you have followed the steps so far, then your virtual host will be configured to allow the only key file based SSH logins. This alone will stop any brute-force attack since trying to replicate the private key file would take an attacker billions of years on even the fastest computer. However, the attacker can still try. That is, there is nothing on the server that would prevent an attacker from trying to login to an account hundreds or even thousands of times in a row, Also, if you chose to allow password-based authentication for the root user, then depending on the strength of your root password, it may be feasible for an adversary to crack it with a brute-force attack.

One simple way to prevent these types of attacks is to ban a client from trying to connect after some number of unsuccessful attempts. This can be configured manually through the SSH settings file, but a simpler approach is to install and configure the fail2ban application service. The service will monitor your log files for failed connection attempts, source IP addresses, and time stamps, and based on these values will add a firewall rule temporarily banning the client. There are many aspects of fail2ban that can be customized, but for our purposes the defaults are fine. Installing and starting fail2ban is as simple as running

apt-get install fail2ban

We do need to make one configuration change. By default, fail2ban uses the well-known ssh port. We need to update this to use our new port number. The change is simple: open /etc/fail2ban/jail.local inside of Nano (make sure to run the command as sudo) and change the port = ssh line to port = {new port#}. Save your changes, exit Nano, and then restart the service with service fail2ban restart.

Under the default configuration, it is difficult to test if fail2ban is working since you would need to make unsuccessful SSH connections attempts very quickly. So quickly, that the process would need to be automated with a script or program that emulates a brute-force attacker. However, you can verify that fail2ban is running by using the service fail2ban status command. For more information on fail2ban (or any Linux program) read the built-in manual by running man fail2ban. When reading the manual pages inside a terminal, you can use [PgUp] and [PgDn] to scroll entire pages, and [UP] and [DOWN] to scroll single lines. Press [Q] to quit and return to the command prompt.

Comand Reference:

apt-get install {package}           // install a pacakge to your machine using an automated script
apt install {package}               // wrapper for apt-get. Shows a handy progress bar during installiation

man {package}                       // show the manual page for a built-in or installed application
[PgDn]                              // scroll to the next page while reading a manual entry
[Q]                                 // quit the manual and return to the prompt

service {name} statis               // display the process name, status, and recent activity log of a named service


Adendum

After completing the above steps you will have a secure virtual web server set up and prepped for any number of future uses. The next part of this guide will use the server we just created as a base for installing and configuring the Apache httpd web server. Web hosts are one of the most frequently attacked targets due to their unique mix of high visibility and public access. While the security precautions we established go a long way towards securing ssh access to our server, they are only the beginning. Once a host is intentionally opened up to the public, an entirely new branch of security considerations come into play. However, the cornerstone of this security is still the principle of least access, and the security base we established here can be naturally extended to securing a web host as well.

A good next step would be to create a full disk backup of your web server. This can be done either through the server itself or by using your cloud provider to create an off-system backup. In addition to protecting against future crashes and errors, a backup of the current system can easily be used to spin up additional pre-configured servers for a variety of tasks.

As a final thought, here are some general purpose Linux commands and tricks that may be useful:

Command Reference:

cut -d: -f1/etc/passwd          // view a list of all user accounts on the system
makedir -p {dirA/dirB/dirC}        // Make a directory and all needed subdirectories
hostname -I                        // show the IP address of the host

tail {filename}                 // display the last few lines of a file
less {filename}                 // display a long file as pages, allows forward and backward scrolling

sudo !!                         // run the previous command as sudo
!!                              // run or reference the previous command
alias S=sudo                    // tired of typing sudo? Shorten it to 'S'
{command} !$                    // run a command with an argument equal to the last argument used
{command} [ALT] + [.]            // interactive cycle through the last arugment of the last commands
[CTRL] + [A]                    // place cursor at start of command
[ALT] + [D]                       // delete last word of command


About the author

Brian Houle: (blog owner)  I'm a computer science senior at the Metropolitan University of Denver. I love technology of all kinds and have recently become interested in expanding my knowledge of web frameworks.

3 Comments

  1. blackwolf

    January 17, 2018
    Reply

    interesting what are you going to blog about next?

    • Brian Houle

      January 20, 2018
      Reply

      I'm finiishing up Part 2 of the guide now. This will cover installing and securing the Apache web server. Thanks for the interest.

Would you like to share your thoughts?

Your email address will not be published. Required fields are marked *

Leave a Reply

%d bloggers like this: