Below are a set of instructions I have compiled for provisioning and securing
a bare-bones Linux VPS (virtual private server) as this has become a common
requirement of late.
Some vendors now provide bootstrap scripts with dashboards/configuration tools
for basic server setup steps but I felt it was important to understand the
underlying steps involved without the overlay of these tools.
The following key assumptions apply:
1). Desired Target Server & Image: Linux/Ubuntu
(Chosen among a plethora of available distributions)
2). Vendor: Linode
(Note: I happen to be a happy AWS user and recommend them highly but
I'm choosing another provider to start the process from scratch and
to avoid the otherwise very convenient steps such as use of my existing
rsa keys for authentication, etc. Of course, I have heard heaps of
praise for Linode, as well, not least because of their straightforward
approach).
3). Client (Local Machine) Operating System: Ubuntu
The instructions below assume they are being performed on some
flavor of a *nix distribution on the client machine. To be specific, the OS
commands referenced here are for Linux/Ubuntu. Users on other *nix
distributions should be mindful of substituting their OS-equivalent commands,
as a result.
Windows users have multiple virtualization options at their disposal to install
a virtual Linux guest on their machines (see: Vagrant, VirtualBox, VMware ) or
windows applications providing such environments (see: Cygwin, MinGW).
4). Text Editor Commands: vim
In the file editing steps below I reference vim commands, as this is
my editor of choice. For these steps, if you do not have vim installed,
simply substitute your favorite text editor (e.g. 'nano', 'gedit', etc. )
mindful of its own commands, obviously.
CAVEAT EMPTOR: Requirements vary with every use-case scenario.
The instructions listed here are meant to be an illustrative example of
provisioning and securing a server for one particular base-case scenario.
You should take care to note your own particular requirements for server type,
security, and software installation and be mindful where they differ from
the instructions presented here.
( Note there are many options for provisioning a Linux server. Here is a random source of cloud providers and here is a source of comparison articles. Per our stated objective, we are going with Linode)
A). SIGN UP WITH VENDOR
Visit:
and create an account if you don't already have one.
Note: many vendors have free trial periods. At the time of this writing,
the option of trying Linode risk free for 7-days was available and this was
the option I chose.
Follow the remaining instructions to create your account (e.g., if creating a
Linode account for the first time, click on the link in email to activate your
account, etc.).
B). SELECT AN INSTANCE TYPE
Next, select an instance type from the menu of available ones you are
presented with. I chose the simplest/cheapest:
Linode 1024
(Note: it's conceivable that options and/or names may change over time).
C). SELECT REGION:
I chose:
Newark, NJ
At this point, you should be presented with a 'Linode Manager' page indicating
that your machine is being created.
Wait until it's created - (watch for the message 'Brand New' after refreshing).
D). SETUP MACHINE IMAGE:
1). Install Operating System
a). From the Linode Manager page, click on the 'Dashboard' and
under Options to the right click the 'Deploy an Image' link.
Select the 64-bit Ubuntu 16.04 LTS distribution
2). Choose a strong root password
From Linode themselves:
Enter a root password for your Linode in the Root Password field. This password must be provided when you log in to your Linode via SSH and must be at least 6 characters long and contain characters from two of the following categories: lowercase and uppercase case letters numbers punctuation characters
3). Configure remaining details as needed (disk swap sizes, etc.)
For this exercise, I left default disk sizes for swapping, etc.
(Note: these are important settings which you should consider
carefully given your particular requirements).
Once configuration settings have been set, you should then be met with a
somewhat more detailed screen listing your configuration details that you
have chosen.
Of particular importance here are the items in the Host Job Queue which
confirm Ubuntu 16.04 LTS as our operating system image on the machine
and that the root password has been set (we'll need that to login to our
server from the beginning).
The convenient 'Remote Access' tab lists the public ip for our machine
as well as ssh access. In my case these are:
SSH Access:
ssh root@50.116.53.185
Next, let us Boot our machine - as we can see from the Server Status
section in the top right of our Dashboard, the status currently reads:
"Powered Off"
Click Boot
and 'OK' to confirm.
Helpful Guide: https://www.linode.com/docs/getting-started
(Note: StackScripts is Linode's collection of helpful scripts for server
deployment, which we've purposefully avoided using here so as to get to
the underlying concepts).
For the steps that follow, we'll need to note our newly provisioned server's
public ip address:
IP address: 50.116.53.185
At this point, we have successfully provisioned for ourselves a basic server
with an Ubuntu Linux image installed. In the next section, we walkthrough the
very basic steps of securing the server we have just provisioned.
(Note: in the steps below we prioritize securing our freshly-minted server over software updates/upgrades which are performed at the end of this process, once basic security measures have been put into place. One could choose to perform software updates before following these security protocols. The trade off is an exposed server during update time (which might be negligible) versus the (small) chance of using an outdated security package before updates.)
A). REMOVE ATTACK VECTOR: root login
This means creating a user profile whose name only we know which has root
privileges but is not the actual role: 'root' (known to everyone).
1). Start by logging in to the remote server as root (default to begin with):
ssh root@50.116.53.185
You will get a message similar to the following:
The authenticity of host 50.116.53.185 (50.116.53.185) cant be
established. ECDSA key fingerprint is SHA256:i1rDpCRgVqjE5BJl0Xhk0...
Are you sure you want to continue connecting (yes/no)?
Answer yes to recognize our remote server and to add it to the list of
known hosts.
You will then see:
Warning: Permanently added '50.116.53.185' (ECDSA) to the list of known hosts.
which means that the remote server has been added to the file:
~/.ssh/known_hosts
on your local machine.
Immediately after this, you'll be prompted for the root password you setup
during deployment with Linode.
Enter that password at the prompt to log in.
If you remembered your password correctly, you should now be logged into
your newly provisioned server and should see a prompt similar to the
following:
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.9.7-x86_64-linode80 x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Last login: Tue Mar 7 20:18:09 2017
root@localhost:~#
This welcome message will vary over time and with the particular
distribution you have chosen to install. The important thing to note is
the prompt:
root@localhost:~#
Our objective is to change this so we never have to login as 'root' again.
2). To do this, we add a new user account whose profile has root privileges:
The following commands are Ubuntu-specific (modify them as required,
particularly if working on a non-Debian derived distribution):
sudo adduser grader
Use a strong password and store it safely (or memorize it)!
You will then be prompted to enter information for the grader.
You can fill these in as you like or just press Enter to skip filling in.
root@localhost:~# sudo adduser grader
Adding user grader ...
Adding new group grader (1000) ...
Adding new user grader (1000) with group grader ...
Creating home directory /home/grader ...
Copying files from /etc/skel ...
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Changing the user information for grader
Enter the new value, or press ENTER for the default
Full Name []: grader
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n] Y
Enter 'Y' when asked if the information is correct to finish adding
information for our new user.
3). Next, we assign the new user profile root privileges:
The commands below are specific to the vim text editor (modify them as
required if vim is unavailable or you prefer another editor like nano, etc.).
a). Create a sudoer file as follows:
sudo vim /etc/sudoers.d/grader
put this line inside the file:
grader ALL=(ALL:ALL) ALL
This means that for the user grader we are granting permission to run all
commands as all users and all groups from all hosts.
save the file (write and quit):
:wq
4). Verify the new user has been created:
sudo cat /etc/passwd | grep grader
this should return an entry.
5). Switch from our root profile to the new superuser profile:
su - grader
Verify your prompt changes from:
root@localhost:~#
to:
grader@localhost:~$
6). Now that we have a new superuser profile, we should disable logins from
the user 'root'.
Edit the server-side sshd_config file:
sudo vim /etc/ssh/sshd_config
Change the line:
PermitRootLogin yes
to:
PermitRootLogin no
save and quit:
:wq
(Note all work here is being done on the remote server, not your local
machine. This file is the ssh daemon configuration file which resides
server-side only. If you're only seeing ssh_config instead of
sshd_config, it's likely you're on the client rather than server).
7). Since we changed the sshd_config configurations file, we need the new
configurations to be read so we must restart the service as follows:
sudo service ssh restart
8). Now exit the new superuser profile:
exit
which will take you back to the 'root' profile.
Let us now exit and disconnect from the remote server:
exit
9). Let's now confirm that root login to our server has been disabled.
From the local client, attempt to login as root:
root@50.116.53.185
this should fail. Instead, however, you should be able to log in as:
grader@50.116.53.185
(that is, if you haven't forgotten your superuser's password)!
B). REMOVE ATTACK VECTOR: password-based authentication
Here, we remove password-based authentication as a means of external
access to our server due to vulnerabilities with this approach (namely,
weak passwords). In this step, we will substitute Secure Shell (SSH) login
and make use of public key encryption as a substitute for password-based
authentication.
1). On your LOCAL (client) machine, create an ssh directory (if not there):
*(As stated above, the following commands assume the client operating
system is some flavor of a nix distribution (vary these steps as required
to match your own operating system requirements. Windows clients can
refer to this).
mkdir ~/.ssh
(note this is in your home directory and prefixed '.' )
2). Now let's create a file called 'config' in this directory to which we will add
our remote server's connectivity details. If you already have one, just
append the information below to the existing contents of your config file.
vim ~/.ssh/config
Put the following lines in this file:
Host linode1
Hostname=50.116.53.185
The value next to 'Host' is the name you want to give your remote server.
The value for 'Hostname' should be the public IP address you were given
by the vendor for your machine.
save and quit:
:wq
This step is meant to simplify remote logins to our server. Whereas up
until now we used the public IP address of our server to login, we can now
simply use the hostname we have given our server to do this, as in:
ssh grader@linode1
3). As a first step towards replacing password-based authentication to our
server, let us now create a key pair. You can read more on this protocol
here.
Note that if you already have a key pair generated, you may skip this step
and simply make use of the private key you already have. This
discussion weighs the pros and cons of periodically renewing key pairs.
a). On the client (your LOCAL MACHINE) run the following command (if you
have no key pairs generated or wish to generate a new pair):
ssh-keygen
1). By default, the encryption used is 'RSA'. This can be changed to
another format with the '-t' argument as follows:
ssh-keygen -t <encryption>
A list of supported formats is available here.
(Yet another option is to use the -f argument to give a specific
name to the keys you generate here).
Assuming the standard RSA encryption was used, this will generate
two keys in your ~/.ssh directory:
id_rsa
id_rsa.pub.
Copy the contents of your public key id_rsa.pub (the private
key id_rsa should remain private and on the local client).
4). Back on the REMOTE SERVER we provisioned, create the .ssh directory
under the superuser's home directory:
mkdir ~/.ssh
a). Create a file called 'authorized_keys' and paste your id_rsa.pub
content into it as follows:
vim authorized_keys
Shift + Insert [ functions as 'paste' command ]
:wq
[ save and quit ]
b). Grant required permissions on this directory (you'll need your new
superuser password for the first one):
sudo chmod 700 ~/.ssh
[ grant user read/write/execute permissions ]
sudo chmod 644 ~/.ssh/authorized_keys
[ grant user read/write, group and others read permissions ]
c). Define localhost in hosts file with server IP address. This file
establishes static associations between IP addresses and hostnames
and is read first before any DNS lookups.
sudo vim /etc/hosts
if necessary, edit the first entry that it looks like this:
127.0.0.1.1. localhost.localdomain localhost
:wq
Different distributions have different requirements. As a result, should
host resolution errors crop up downstream - when attempting to service
a request for a hosted web application, for instance - edit this file to
include the explicit IP address of your server, as follows:
127.0.0.1.1 localhost ip-50.116.53.185
5). Test ssh key pair based login.
a). Quit the server:
exit
b). Back on the client machine, let us ssh back to the remote server.
The command to do this is as follows:
ssh <user>@<ipAddress> -i <pathToPrivateKey>
thus:
ssh grader@50.116.53.185 -i ~/.ssh/id_rsa
1). Note that if you took the extra step to edit your ~/.ssh/config
file on your local machine, our login command to the remote server
reduces to simply:
ssh <user>@hostname
thus:
ssh grader@linode1
This way, you don't have to remember the IP address every time.
C). REMOVE ATTACK VECTOR: default ssh port log-in
1). Back on the REMOTE SERVER, open the ssh config file for editing:
sudo vim /etc/ssh/sshd_config
Make sure you're on the server editing /etc/ssh/sshd_config
which is the ssh daemon which runs server-side. If all you see
is /etc/ssh/ssh_config, you're on your local machine as this is
the client side OpenSSH config file.
(More on OpenSSH: https://www.openssh.com/manual.html)
2). Change the ssh port from default to something else:
change:
Port 22
to:
Port 2200
[ or some other available port of your choosing ]
3). While we're here, let's also disallow password-based authentication:
change:
PasswordAuthentication yes
to:
PasswordAuthentication no
Save and exit
:wq
4). Restart the server's ssh service as these updated configurations need
to be reread for them to take effect:
sudo service ssh restart
5). Exit the server once again:
exit
6). Re-connect from the client using ssh with non-default port:
ssh <user>@XX.XX.XXX.XX -i <keyfile> -p <port>
thus:
ssh grader@50.116.53.185 -i ~/.ssh/id_rsa -p 2200
a). For greater convenience, on your LOCAL machine, add the ssh port to
our config file:
vim ~/.ssh/config
add the non-default port as follows:
Port=<port>
thus
Port=2200
save and quit:
:wq
For the record, the local ~/.ssh/config file entry for our remote server
should look something like this:
Host linode1
Hostname=50.116.53.185
Port=2200
b). To try out the simpler method, do the following.
Quit the remote server (yet again):
exit
Now, back on the client, with the ~/.ssh/config file edited to include
the port, simply ssh back in again as:
ssh grader@linode1
D). REMOVE ATTACK VECTOR: unauthorized traffic
With our server now minimally secured, it is out there, connected to and
accessible via the internet and as such it is prudent to be selective with any
incoming traffic from potentially malicious entities.
Here, we take steps to configure firewall settings so as to permission and
delegate specific types of traffic/requests to their respective ports using
Ubuntu's Uncomplicated Firewall UFW
which is a high-level tool that has made the once tedious tasks of micro-
managing iptables for firewall configurations a thing of the past.
While we're likely to want to allow ourselves to be able to reach out to the
world from our server, incoming traffic to our server is another matter
altogether and for which our guiding philosophy will be:
Start with no traffic and add on piecemeal
The UFW does not come enabled by default. This means that with a fresh
Ubuntu Linux server set up, all traffic is enabled from the get go.
(Note: be sure to process each of the following commands correctly. If
you make a mistake, you will have a chance to fix it before we enable
the firewall in the last step. Before it is enabled, however, you need to
make sure that you haven't setup the firewall to do unintended things -
key among which would be locking yourself out of the server by disabling
your chosen ssh port).
1). Blank Slate: reign in all traffic
allow ourselves all outgoing traffic:
sudo ufw default allow outgoing
deny all incoming traffic:
sudo ufw default deny incoming
2). First and foremost, allow incoming ssh requests:
sudo ufw allow 2200
[ make sure your configured ssh port is here ]
3). Allow http requests to our server (especially if you're going to host a
web app on it):
sudo ufw allow 80
4). Finally, add any other incoming traffic which might be of value or
importance to you. A reference for many of these can be found here .
At a later step, we will be setting our server's timezone for network
synchronization purposes. The default port for that is 123 so we'll set
the Network Time Protocol (NTP) for this as follows:
sudo ufw allow 123
5). With these commands processed, take a moment to look back in your
command history to ensure you haven't done anything unintended.
In particular, make sure you have allowed incoming ssh requests via
the port you configured in /etc/ssh/sshd_config (originally 22).
Each rule is updated as you enter the command, hence the return:
Rules updated
If you have made a mistake and need to change a port number, simply
enter the command with the correct port number.
If you're all good to go, enable the firewall as follows:
sudo ufw enable
at which point you'll see the following prompt:
Command may disrupt existing ssh connections.
Proceed with operation (y|n)?
Answer 'y' to confirm. You should then see a confirmation message as
follows:
Firewall is active and enabled on system startup
Another good reference on firewall setups is available here.
To recap, if you have come this far, you have successfully:
1). Removed the 'root' profile from external logins
2). Disabled password-based authentication in favor of the much stronger
public key encryption method
3). Changed the default ssh port to something other than the default
4). Limited all incoming traffic to only your permissioned activities and only
to specific ports.
(Be mindful that the steps above comprise a minimum for securing a server.
Plenty of other security issues exist and, of course, security is a hot topic
everywhere. Here is yet another non-exhaustive list of security vulnerabilities
to consider addressing when setting up a server for the first time.)
With these basic steps to securing your newly provisioned server, we're ready
to address any software updates and upgrades your machine may now need.
(Now that we've taken the basic steps to secure our freshly-minted server,
we can turn our attention to updates and upgrades of installed software.
This is an opportune time to just go ahead and apply any/all updates as we do
not yet have any tasks (projects/applications) running on the machine where
updates may have unintended consequences in one or more dependencies
in our processes).
TIP: When a machine is mature and has been in use, one should *not*
automatically apply updates (though package source lists may be kept up to
date) for precisely this reason. If at all possible, a redundancy in the form of
a 'dev' or backup production instance of the server should be used to test
updated software packages in parallel to the un-updated server in order to
pinpoint any unexpected behavior as a result of said updates and to prevent
update-related errors from propagating to the production environment.
A). UPGRADE SOFTWARE - in Ubuntu, this is generally a painless process and is
as simple as issuing the following commands:
1). update the package source lists
sudo apt-get update
This command mere updates the source lists so as to indicate the newest
available versions of packages. No software is installed in this step.
2). perform package updates:
sudo apt-get upgrade
This step performs the actual download and installation of packages
with reference to the source list as updated by 'update' command above.
B). (OPTIONAL/RECOMMENDED) ADDITIONAL SECURITY: fail2ban
Now that we have our updated our server software packages, we can turn
our attention to non-base install packages which might be helpful. One such
package is the fail2ban service, which is meant to protect against brute force
attacks in hacks using passwords after it detects a certain number of
unsuccessful login attempts.
While we have disabled passwords and are using an ssh-based authentication
protocol, the ssh daemon itself is exposed to the internet when it runs, which
leaves it vulnerable to a potential attack from hackers. It works by
auto-reconfiguring the iptable's firewall settings without bothering you but
can also send you reports when suspected attacks happen.
As a result, while it's optional, it's a good idea to install this service.
We should also install sendmail along with it, which is the most popular
Unix-based email implementation and with which we can have fail2ban
send us reports.
a). Since this is a non-standard package, we must specifically install it:
sudo apt-get install sendmail
sudo apt-get install fail2ban
b). Configure sendmail:
sudo sendmailconfig
...and answer 'Y' to prompts (for this exercise, at least).
since this resulted in a configuration change, we need to:
sudo service sendmail restart
c). Configuration files for fail2ban reside in the /etc/fail2ban directory
where the file jail.conf contains standard configurations but can be
overwritten in the event of an update.
As a result, we should create a custom configuration file that won't be
overwritten by updates by copying this file and editing its contents:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Ideally, we would only change settings which are not dealt with in
the standard configuration file but to simplify things let us edit our
custom '.local' file with our email address for fail2ban reports:
sudo vim /etc/fail2ban/jail.local
edit the line:
destemail = root@localhost
and change it to:
destemail = grader@localhost
:wq
sudo service fail2ban restart
Info: fail2ban.
C). SET TIMEZONE (RECOMMENDED):
It's advisable to set the server timezone to UTC which is impervious to
changes in Daylight Savings Time (DST) and synchronizes the timing of jobs
across geographic regions (for larger organizations down the line) and thus
makes reading logs much easier:
sudo dpkg-reconfigure tzdata
Select:
None of the Above
In the second page, select:
UTC
D). DETERMINE YOUR REQUIREMENTS - software requirements and use-cases
beyond this point tend to diverge greatly but it's likely you'll need to install some
helpful non-base utilities (some admins like 'finger' and a whole array of other
packages) as well any application-specific packages for your environment.
E). GOING FORWARD:
1). PACKAGE UPDATES - with our basic server setup now satisfied, you should
be aware that software tends to be updated fairly regularly (some
packages more so than others). As a result, it's a good idea to stay on
top of these developments and download, install, and test upgrades
when possible. This is especially important for security updates.
It is possible to configure automatic updates known as
unattended-upgrades in Ubuntu. Here is a reference.
Another source for managing packages and software updates is this.
2). Remember: upgrade with caution
As cautioned before, when the server is mature and handling production
environment tasks, it's always better to first test updates rather than
immediately rolling them out into a production environment.
Congratulations - you have just completed one base-line provision and setup of
a barebones Linux server.