skip to content

Self-host a static site in the cloud

/ 8 min read

This article serves as a practical guide for hosting a static website on a cloud service provider. The description particularly focuses running the site on a virtual private server (vps). The primary objective of this guide is to act as a reminder for my future self, but cheaper or even free alternatives do exist (for example, GitHub Pages).

Overview - the most important steps

  1. Create ssh keys
  2. Set up vps instance (using ssh key)
  3. Create and and configure your server
  4. Configure DNS record


In order to follow through with this guide, you need to own a domain. Before buying a domain consider that some domains (e.g. *.dev) require the TLS certificates. Caddy does this out-of-the-box, but it is also possible to use another web server. More on how to automatically update TLS certificates with Let’s Encrypt.

You need an account at Hetzner or another cloud service provider. Any provider will do, as long as you can buy a server instance, e.g. a vps or dedicated server. I will skip a description on how to buy a domain and how to set up an account at Hetzner.

Create ssh keys

In order to connect with your vps via ssh, we need to create a set of ssh keys. This can be skipped, if you do not want to use ssh keys for authentication (in that case the password for your root-login will be sent to your email-address by Hetzner).

Linode’s documentation describes step-by-step what to do. So I am only going to summarise the commands:

Terminal window
cd ~/.ssh # create directory if it does not exist
ssh-keygen -t rsa -b 4096 -f awesome-ssh-key
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /Users/metters/.ssh/awesome-ssh-key
Your public key has been saved in /Users/metters/.ssh/
The key fingerprint is:
SHA256:eSxXEnXyxXy0BXdsI6XXunG/rmXxETtFTUpxxXv8i3A metters@metters-mac.local
The key's randomart image is:
+---[RSA 4096]----+
| o+oo...o..|
| . =.o+.+ oo|
| .o+ooo ++.|
| . . .=X.E + |
| +*S.. + . |
| . +*o.o . |
| .o.+ . |
| .+ . . |
| .oo ..|

Set up vps instance (using ssh key)

Click to expand and read about cloud service providers and registration

Create account for cloud service

Create an account at a service provider that offers cloud service. You can pick any service provider you want, however I will focus on Linux (Ubuntu). There are differences with the CPU performance, amount of storage, maximum traffic, etc.


I am currently using Hetzner (scroll to “Prices”)

  • Sign up without referral link
  • Sign up with referral link (20 Euro cloud credit for you and me)


These are some options I came in touch with:

  1. Strato was recommended to me, because they are based in Germany (so is Hetzner), and they seem to be cheaper than Hetzner. For example, they offer three tiers of service named “mini-vserver”, one of which can be tested for free for one month (as of 17.06.2023)
  2. Linode (check the pricing for “Shared CPU”) is located in the United States. I think they are worthy to be mentioned because I found their documentation well written. It helped me a lot to set up my linux instance.
  3. Vultr is an American cloud services provider that is famous for their powerful API that lets users automate their cloud server deployment.
  4. Uberspace is a Germany-based web hosting company that lets the users decide how much they pay for the service, so those who cannot afford the service otherwise can. Others that are more fortunate are kindly asked to pay more to help others out.

Create and configure your server

Create a new project

Set up a new project and a new server using Ubuntu as operating system. You are going to be asked to add a ssh key. It is possible now to configure firewall rules either with the GUI or using cloud init config. I configure ufw directly on the Ubuntu instance (further below), but of course there’s no reason to not configure the server with the help of those two other options.

The GUI will display the ip address of your server, as soon as it is set up.

Update ssh config

The next step is to set up the ssh config on your local machine. Open the file via:

Terminal window
vim ~/.ssh/config

Add the following entry

Host awesome-vps
Hostname # change to your ip address
User root
IdentityFile ~/.ssh/awesome-ssh-key

Recommended: Add a non-sudo user

Add a non-sudo user

Connect via ssh:

Terminal window
# with configured ssh config:
ssh awesome-vps
# without configured ssh config:
ssh root@ # change to your ip address

This connects you as root, but I would rather connect as a less privileged user. The following part illustrates how to add another user. You can use the same ssh key as root, but you should create and use another set of ssh keys instead!

To start the dialogue to add another user, e.g. with the name metters, execute:

Terminal window
adduser metters

You will be prompted to enter a password and additional information for the new user. Grant administrative privileges to metters by adding the account to the sudo group. After that, whenever there are commands that need to be executed as user with root privileges, you can just put sudo in front of the command. Then switch to the new user account (the prompted password is the one you set up for metters) and create a .ssh directory for the new user. Configure the permissions for the new directory and create a file, e.g. named authorized_keys.

Terminal window
su - metters
usermod -aG sudo metters
mkdir ~/.ssh
chmod 700 ~/.ssh
touch ~/.ssh/authorized_keys

Copy and paste the contents of your ssh public key into this file, then save the file and close the text editor. After this, update the permissions for authorized_keys:

Terminal window
chmod 600 ~/.ssh/authorized_keys

Update ssh config

On your local machine update the ssh config file again:

Host awesome-vps
Hostname # change to your ip address
User metters
IdentityFile ~/.ssh/{name of the private ssh key}

If you still want to connect as root, simply put the username before the hostname:

Terminal window
ssh root@awesome-vps

Now you can connect via ssh as root (or user metters, if you followed the previous optional steps):

Terminal window
ssh awesome-vps

The following steps assume you are connected as non-sudo user. Therefore, the sudo keyword is required for many commands.

Set up Firewall (UFW)

Execute the following commands, one after another:

Terminal window
sudo ufw disable # turn off firewall
sudo ufw default deny incoming # block all incoming traffic
sudo ufw default allow outgoing # allow all outgoing traffic
sudo ufw allow ssh # allow incoming traffic on port 22
sudo ufw allow http # allow incoming traffic on port 80
sudo ufw allow https # allow incoming traffic on port 443
sudo ufw enable # turn on firewall

To check the firewall configuration execute sudo ufw status or sudo ufw status verbose to check the ufw configuration. It should print something like this:

Terminal window
sudo ufw status
Status: active
To Action From
-- ------ ----
22/tcp ALLOW Anywhere
80/tcp ALLOW Anywhere
443 ALLOW Anywhere
22/tcp (v6) ALLOW Anywhere (v6)
80/tcp (v6) ALLOW Anywhere (v6)
443 (v6) ALLOW Anywhere (v6)

Set up web server: Caddy

Download and install

First, run sudo apt update && sudo apt upgrade, in order to install available updates.

After this install Caddy. The vps is an Ubuntu instance, there is a section about the installation on Ubuntu.

Click to see the commands to install Caddy (its latest stable version at the time of this writing)
Terminal window
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf '' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf '' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

Regularly used commands to manage Caddy

Terminal window
sudo systemctl status caddy # print the current status of Caddy
sudo systemctl start caddy # turn Caddy on
sudo systemctl stop caddy # turn Caddy off
sudo systemctl reload caddy # restart Caddy (necessary after editing the Caddyfile)

Configure Caddyfile

Edit the Caddyfile in order to configure Caddy. You can find it under /etc/caddy/:

Terminal window
sudo vim /etc/caddy/Caddyfile

This is nearly the most basic setup for your site

/etc/caddy/Caddyfile, {
root * /var/www/html/

Do not forget to restart Caddy after editing the Caddyfile with the reload command!

Add a static site

In order for Caddy to serve your site, you must create a folder which contains the build artefact or contents of the website (which can also be a simple html file):

Terminal window
sudo mkdir -p /var/www/html/ # the name of the last folder does not have to match the domain/url, but it is helpful, when hosting several sites
sudo echo '<h1>Hello World!</h1>' > /var/www/html/ # this is where the build artefact is stored, in this case a single html file

Configure DNS record

Add an A record for every domain/subdomain that you need. With a Caddyfile as the one shown above that would be three entries: @, www, and blog, with the latter two as subdomains.

@ IN A // change to your ip address
www IN A // change to your ip address
blog IN A // change to your ip address

NOTE: Saving the file, does not immediately take effect. Propagating edited DNS records to the nameservers may take up to a few hours!

Use the following command to find out what ip address the domain refers to. As soon as the displayed ip address matches the one you configured in your DNS record, open the previously configured domain in your browser.

Terminal window
dig +short # do not forget the dot at the end # the ip that was configured in your DNS record