Full VPS Server Set Up Guide Using Ubuntu 18.04 LTS
Primespot Engineering - May 21, 2020
Here is an exact guide followed at Primespot to set up a fully functioning and secure VPS on cloud hosts such as DigitalOcean.
This guide covers setting up basic server function, SSH, Nginx, Docker, a firewall, Fail2ban, security, etc.
After following every step of this guide, you should have a fully functioning server that you can securely SSH into and host websites or applications.
Getting started
First, provision your server. This is usually done from the website of whatever cloud hosting provider you choose. I prefer DigitalOcean. But this guide should work with any of them.
After provisioning your server, login to your server by SSH.
$ ssh root@your_server_ip
Now you need to add a new user. It would be a security vulnerability to rely on root for access to your server.
# adduser michael
Replace michael with whatever user name you would like.
Now the user needs to be granted super user privileges so it has full access to the system.
# usermod -aG sudo michael
This grants your new user the same privileges as root.
SSH access to the server
SSH allows you to login to your server from the command line. The rest of this guide will use SSH for all of the commands.
So far, we have been logging in the insecure way. We need to make SSH secure before we proceed
Over the life over your server, you will have many "hacking" attempts on your server. These attempts will really just be scripts testing SSH for vulnerabilities. The following steps will secure your server from this type of threat.
First, we need to add public key authentication.
From your local machine (not the VPS server)...
$ ssh-keygen
When prompted for the key path, pick a path to a new key file, such as id_rsa_primespot_server
Note: Don't use the key file name in the example above (id_rsa_primespot_server). Replace this name with a key name of your choosing. Prepending the name with id_rsa as I did in the example is a fine practice thought. This way, you know exactly what the file is by looking at the name.
Next you have to copy the public key to the server.
I am going to show you two ways to do this. The first option is the manual option. The second will be easier and automated.
I generally prefer the second option, but it's good to show how to manually do it as well.
Method 1
To get the public key on your local machine:
$ cat ~/.ssh/id_rsa_stcc1.pub
Then copy the key to the clipboard (Ctrl-C or Cmd-C typically). Then ssh back into the server as the root user.
$ ssh root@server_ip_address
Switch users to the user you created earlier. I am using the user michae for this example.
# su - michael
Now we'll configure the ssh directory in your home directory.
$ cd
$ mkdir ./ssh
$ chmod 700 ./ssh
Next, the public key needs to be pasted into the authorized_keys file inside the ~/.ssh directory.
Note: We are using vim for this file edit. Vim is perfect for this use case since we only have terminal access. Vim is also a very popular editor that many software development shops use including Primespot. Check our blog for posts about how to use Vim effectively.
$ vim ~/.ssh/authorized_keys
Paste the public key in.
Note: To paste in vim, press the i key to enter into what's called insert mode. Then press Ctrl-v on Windows/Linux or Cmd-v on Mac. If this doesn't work, then you are probably not using a terminal that supports copy and paste functions. Google for a terminal program that will work for your needs. On Mac, I recommend Hyper
After pasting the public key in, save and quit out of vim.
Note: To save and quit out of vim, first press the escape key to exit insert mode and enter what is called normal mode. Afterwards, type :wq to save and exit. That is colon, w, q.
Method 2
Alternatively, you can use the ssh-copy-id utility on the local machine. Just specify the correct key file in the command.
On your local machine, type:
$ ssh-copy-id -i ~/.ssh/mykey user@host
Replace ~/.ssh/mykey with the path and file name of your key file.
Also replace user and host with the user name and host address of your VPS.
If using a custom key file instead of the default (we are in our example), you must add the private key on the local machine using ssh-add. This adds the custom key to the registry, but it only adds it in memory. This means that you’ll have to repeat this process every time you restart your local machine.
$ ssh-add ~/.ssh/my_private_key
Replace ~/.ssh/my_private_key with the path and name of your private key file on your local machine.
Note: The private key is the key file that doesn’t end in .pub.
Alternatively to using ssh-add, you can specify the identity file at the command line in the ssh command like so:
$ ssh -i /path/to/identity user@host
Another option still is to create an ssh config file on your local machine at ~/.ssh/config. This will specify options for different connections.
Host myservername
User michael
HostName myserver.com
IdentityFile ~/.ssh/myserverprivatekey
Tip
The format used in the example above can be used multiple times in the config file if you are using multiple VPS servers.
With this setup, you can simply type:
$ ssh myservername
This will automatically connect you using the correct host, user, and key.
Now restrict the permissions to this file on the server machine.
$ chmod 600 ~/.ssh/authorized_keys
Now attempt to login to the server using the public key instead of a password. If it works, proceed to the following instructions.
In the next steps, we are going to disable password authentication. This will significantly improve security by only allowing those with a valid SSH key to login.
We'll start by entering vim again and editing the SSH config file. In this file, we will set/verify a few options.
$ sudo vim /etc/ssh/sshd_config
Find the line that specifies PasswordAuthentication, uncomment it, and change the value to no. It should look like this:
PasswordAuthentication no
Ensure the following keys are set like this:
PubkeyAuthentication yes
ChallengeResponseAuthentication no
PermitRootLogin no
Save the file and exit by typing <ESC> :wq.
Next we need to restart the SSH server. It is common practice to restart a service whenever config files are changed. If you edit a config file on a Unix-based operating system and forget to restart the service, it is likely that the changes won't take effect.
$ sudo systemctl reload sshd
Before logging out, open a new terminal window and test logging in by typing:
$ ssh michael@your_server_ip
This step is critical. If the config isn't set up correctly and you can't login passwordless, you won't be able to login at all. Fixing the problem will require you to log into the server directly through your VPS provider and enable password authentication.
If you have a domain name pointed to the server and you want to be able to ssh into the server by typing the domain name instead of the IP address, you need to add this line to your /etc/hosts file on your local machine:
server_ip_address domain_name
Replace server_ip_address with the IP address of the server. Replace domain_name with the domain name pointing to the server. These fields are separated by whitespace. A single space will do. Tabs works as well.
You can edit your /etc/hosts file by typing the following command at the command line:
$ sudo vim /etc/hosts
This concludes our section on setting up SSH. Now that we have completed these steps, we have secure access to our server. This will be essential for completing the rest of the steps in this guide as well as maintaining your server from now on.
Firewall
Now that we are finished with setting up SSH, the hardest part is over.
In fact, it's pretty much smooth sailing from here until we set up Nginx.
Now it's time to set up the firewall. We will start by setting a few firewall settings. Then we will enable the firewall.
Allow OpenSSH through the firewall:
$ sudo ufw allow OpenSSH
$ sudo ufw allow 80/tcp
$ sudo ufw allow 443/tcp
Now activate the firewall.
$ sudo ufw enable
Now we're going to rate limit ssh connections.
$ sudo ufw limit ssh/tcp
Check this status by typing:
$ sudo ufw status
And that's it. We now have a working firewall.
Fail2ban
Fail2ban is a tool to help prevent unauthorized SSH attempts. It's an excellent security addition and takes only a few minutes to set up.
$ sudo apt-get update
$ sudo apt-get install fail2ban sendmail
The sendmail package is required to send email notifications.
Fail2ban uses the file /etc/fail2ban/jail.conf to maintain its settings. These settings can be overridden by creating and editing a jail.local file.
So first you need to make a copy of the jail.conf file named jail.local. Then edit the settings to fit the server’s needs. Alternatively, you can simply create a jail.local file and add your config.
$ sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Below is a good starting point for the jail.local file.
DEFAULT]
# email address to receive notifications.
destemail = root@localhost
# the email address from which to send emails.
sender = root@<fq-hostname>
# name on the notification emails.
sendername = Fail2Ban
# email transfer agent to use.
mta = sendmail
# see action.d/ufw.conf
actionban = ufw.conf
# see action.d/ufw.conf
actionunban = ufw.conf
[sshd]
enabled = true
port = ssh
filter = sshd
# the length of time between login attempts for maxretry.
findtime = 600
# attempts from a single ip before a ban is imposed.
maxretry = 5
# the number of seconds that a host is banned for.
bantime = 3600
Next you need to set up Fail2ban to start when the system starts up and to begin running the service.
$ sudo systemctl enable fail2ban
$ sudo systemctl start fail2ban
And that's all it takes to set up Fail2ban.
Swap file
Swap files are files on the hard disk that the operating system can use when memory is low. The operating system will actually write in-memory data to the swap file instead of storing it in memory.
While this will enable you to have a bit of extra memory when memory is low, it's also much slower. Still, it's a good idea to configure it in case you need it.
First, check if swap is already enabled (it shouldn’t be on digitalocean).
$ sudo swapon --show
If the result is empty, there is no swap enabled.
First, allocate the file necessary for the swap file.
$ sudo fallocate -l 1G /swapfile
If this fails, use this command instead:
$ sudo dd if=/dev/zero of=/swapfile bs=1024 count=1048576
Then set the permissions.
$ sudo chmod 600 /swapfile
Use the mkswap utility to create a swap area on the file.
$ sudo mkswap /swapfile
Activate the swap file:
$ sudo swapon /swapfile
Edit the /etc/fstab file.
$ sudo vim /etc/fstab
Paste the following line in:
$ /swapfile swap swap defaults 0 0
Verify the swap file is active with this command:
$ sudo swapon --show
The swappiness value of the system determines how likely the system is to use the swap file. The lower the number, the less likely the swap space will be used.
Use the following command to check the swappiness value of the system:
$ cat /proc/sys/vm/swappiness
The default is 60. 10 is probably a better number for a production server. Set the swappiness value with the command:
$ sudo sysctl vm.swappiness=10
To persist this setting across reboots, open the file /etc/sysctl.conf
$ sudo vim /etc/sysctl.conf
Then append the following line in there:
$ vm.swappiness=10
Exit vim by typing :wq
This concludes the section of this guide involving basic setup of the server. Next, we’ll install and set up Docker.
Docker
Docker is a system for containerization. Put simply, it runs a full-blown system in a single process making it easy to maintain, move, and deploy the system.
Each system contains a full Linux distribution, essential tools, and whatever is needed to help your app run.
This can be a PostgreSQL database, a Node process, a Rails app...It can be anything really.
It's also common to run multiple of these containers for a single application.
More information about Docker is outside of the scope of this article. Pay attention to our blog for more information, however.
Now that the introduction is out of the way, it's time to get the Docker service running on our VPS. This will help us with production deployments that are using Docker. If you aren't planning to use Docker, you can skip this step.
It’s always a good idea to update the packages before doing anything with apt.
$ sudo apt update
Next there are prerequisite packages that need to be installed.
$ sudo apt install apt-transport-https ca-certificates curl software-properties-common
You need to add the GPG key from the official Docker repository to the system.
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
Now the Docker repository needs to be added to APT sources.
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
Update packages again.
$ sudo apt update
The following command ensures that you are installing from the Docker repo instead of the official Ubuntu repo.
$ sudo apt-cache policy docker-ce
Whew. That was a lot. Now we can finally install Docker.
$ sudo apt install docker-ce
Confirm that Docker is running.
$ sudo systemctl status docker
Now your user needs to be added to the docker group to avoid having to use sudo with every docker command.
$ sudo usermod -aG docker ${USER}
Reload your terminal:
$ su - ${USER}
Confirm that you’re in the docker group:
$ id -nG
Nginx
With Docker installed, we can actually run Nginx in a Docker container. While the approach is pretty cool, I generally prefer to install and run Nginx locally on the system. Therefore, this guide will cover installing Nginx locally on the system.
Note: This guide is not going to cover setting up and configuring Nginx. That is a huge topic on its own.
To start, we will install Nginx.
$ sudo apt update
$ sudo apt install nginx
Next, we need to ensure that the firewall we set up earlier will allow Nginx to pass through.
To see what applications are available to add to UFW, use the command:
$ sudo ufw app list
'Nginx Full' should be on the list.
You can add 'Nginx Full' to UFW with the command:
$ sudo ufw allow 'Nginx Full'
Nginx Full enables both http and https access.
Verify the changes with the command:
$ sudo ufw status
Check the status of the Nginx server with:
$ systemctl status nginx
Verify the web server is working in a web browser by first getting your IP address:
$ curl -4 icanhazip.com
Then loading http://your_server_ip_address in a web browser. You should see the Nginx welcome page. If this works, Nginx is good to go. If you have a website ready, create an Nginx server block and upload it.
HTTPS support
The next step is to enable HTTPS. To do this, you must first have a domain name pointing to the server.
First, add the Certbot repository to apt. This will ensure you get the newest version installed.
$ sudo add-apt-repository ppa:certbot/certbot
Next install Certbot’s Nginx package:
$ sudo apt install python-certbot-nginx
Next, ensure Nginx config files and server block files are set up correctly. The config file for the domain you are trying to get an SSL certificate for should have the correct server_name directive.
Also, make sure UFW is configured to allow HTTPS through the firewall:
$ sudo ufw status
Run this command to get an SSL certificate for the chosen domain:
$ sudo certbot --nginx -d domain_name
Replace domain_name with your domain name.
Certificates will automatically renew due to a cron job that Certbot set up. To verify the renewal process should proceed unhindered, run this dry run of the renewal:
$ sudo certbot renew --dry-run
If you find no errors, test the website domain with https://. If everything is correct, you are done.
Conclusion
I know that this guide is enormous. It will likely take you anywhere between two and four hours to work through it. It might be a bit quicker if you're fast and experienced when it comes to DevOps.
Still, this guide follows many best practices when it comes to setting up a production server.
Furthermore, with HTTPS and Docker enabled, you are completely ready to launch your application.
Happy hacking!