Setup a VPS on Hetzner Cloud

Lambda What, Lambda Who

For deploying my personal projects and websites I have mostly relied on the hobby tiers of serverless services like Netlify, Github pages, or Cloudflare pages. Prior to that I used simple FTP on shared hosting.

The DX of using serverless platforms is very appealing. Just connect a Github account, push your code, and they do all the work for you. For simple statically generated websites, it can’t be beat. But when I started using lambda functions for server-side actions, I quickly hit some annoying edge cases because the runtimes are not 100% Node compatible.

That experience motivated me to setup my own VPS for deploying containerized applications. It was also an opportunity to learn how the abstractions and tools I use actually work. I chose Hetzner because it was the cheapest and I like German stuff.

These are the steps I took to get up and running with a VPS on Hetzner Cloud:

Hetzner Cloud

Start a Hetzner account at accounts.hetzner.com/signUp.

Donate the blood of your first born child. I kid, but if you are from the U.S. you will need to verify your identity using your passport and then wait for a manual approval. My approval only took a couple hours.

Log into your new Hetzner account and choose Hetzner Cloud. In the Hetzner Cloud dashboard, start a new project and add a server. I used the following settings for a basic server to host my side projects.

Location

Choose the nearest location. For me it was (Ashburn, VA) us-east.

Image

Either choose Ubuntu OS or if you plan on using Docker, you can switch to Apps and select Docker CE. It will give you the latest Ubuntu OS with Docker Community Edition pre-installed. If you prefer, you can also install Docker yourself later.

Type

Shared cVPU x86 (Intel/AMD) CPX11 - 2GB Ram / 40GB SSD

Networking

Select both IPv4 and IPv6. IPv6 only is a tiny bit cheaper, but you will almost definitely still need IPv4 also.

SSH Keys

Add an SSH Key. Github has a nice guide on generating SSH keys. Lately I’ve been using 1Password’s SSH agent and I like how I can easily use the same keys on different machines.

Skip for now

Volumes, Firewalls, Backups, Placement groups, Labels, and Cloud Config can be configured later if you need them.

Name

I used the default name.

Click Create & Buy Now.

Configure your server

Now it’s time to SSH into the server and set it up.

Copy your new server’s IP Address and open a terminal.

ssh root@<server_ip_address>

The first time you will be prompted to add the server’s fingerprint to your known_hosts.

Updates

Run system updates to make sure we are using the latest, greatest, and most secure packages.

sudo apt-get update

sudo apt-get upgrade

SSH

Create a new sudo user.

sudo adduser <new_user_name>

usermod -aG sudo <new_user_name>

Add the public SSH key to your new user on the server. To do that, switch to your new user.

su <new_user_name>

Then make a new .ssh directory.

mkdir ~/.ssh

To that directory, add a new file authorized_keys. Open that file with nano and paste in your public key.

nano ~/.ssh/authorized_keys

End your session and log back in with your new user to make sure SSH is working properly.

ssh <new_user_name>@<server_ip_address>

Now that you can ssh in with your username, you should disable login via the root user. Navigate to the ssh directory and edit the sshd_config file. You may want to disable login via password entirely as well.

cd /etc/ssh
nano sshd_config
# sshd_config

PermitRootLogin no
PasswordAuthentication no

A lot of people recommend changing the default SSH port, but I left mine on 22.

For convenience you can edit your local .ssh/config file like so:

Host <nickname>
  HostName <server_ip_address>
  User <new_user_name>
  PreferredAuthentications publickey

With the above config, instead of typing ssh <new_user_name>@<server_ip_address> every time, you can just type ssh <nickname>.

UFW

Enable Uncomplicated Firewall and allow ports for SSH.

sudo ufw enable
sudo ufw allow OpenSSH
sudo ufw allow 22

Fail2Ban

Install Fail2Ban to prevent brute force attacks.

sudo apt install fail2ban

To configure, copy the jail.conf to jail.local and edit the file with nano.

cd /etc/fail2ban

# copy conf
sudo cp jail.conf jail.local

# edit
sudo nano jail.local

Make sure the sshd jail is enabled. It should be on by default.

[sshd]
enabled = true
port    = ssh

Nginx

Install Nginx and allow ports 80 and 443 on your firewall.

sudo apt-get install nginx
sudo ufw allow 'Nginx Full'

Setup reverse-proxy to allow hosting multiple containers on a single server. Each container will be accessed publicly through a subdomain.

There are several different methods for configuring reverse-proxy. Using sites-enabled / site-available has long been the convention for Debian/Ubuntu, but a lot of people recommend avoiding it. I find it overly complicated.

A simpler method is to add a *.conf file to the conf.d directory for each subdomain.

cd /etc/nginx
# conf.d/subdomain.conf

server {
        listen 80;
        listen [::]:80;
        server_name subdomain.example.com;
        location / {
                proxy_pass http://localhost:3000;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
}

To only use subdomains and remove the default Nginx page from your root domain, disable default in sites-enabled.

sudo unlink sites-enabled/default

And return a root 404 page instead.

# conf.d/root.conf

server {
        listen 80 default_server;
        listen [::]:80 default_server;
        server_name _;
        location / {
                return 404;
        }
}

Restart Nginx.

sudo systemctl restart nginx

Docker

If you didn’t install Docker when you added your server, install it now by following instructions in the docs.

unattended-upgrade

Keep your system up to date automatically by installing unattend-upgrade.

By default it only auto updates security updates, but you can configure to update other packages as well. I left it on the default settings.

# install
sudo apt install unattended-upgrades
# configure
sudo dpkg-reconfigure unattended-upgrades
# check is running
sudo systemctl status unattended-upgrades

git

Install git so you can clone your repos, build them on your server, and share with the world.

apt-get install git

Congrats, you’re officially a system admin! 🤜🤛

Next time, we will actually deploy a Node.js app in a Docker container.