Setting up multiple websites on a Hetzner VPS
Part1 - A detailed step by step guide from zero to static website hosting
Buy a VPS and create a SSH key
If you are looking for a low-budget VPS, Hetzner has a nice After setting up your account, you should create a SSH key to connect to your server
ssh root@<my-ip>Ok, now are accessing your Hetzner remote machine from your computer! Feeling Matrix vibes? Not yet? Well, let’s move on with the next steps
Create a new user
We all know that security through obscurity isn’t going to take you anywhere, but i think that it should be better to avoid using “root” user. It helps keeping things organized and logs end up being more understandable. Let’s create a new user with sudo privileges
add user <my-name>usermod -aG sudo <my-name> // give user admin privilegesusermod -aG sudo <my-name> // give user admin privilegesgetent group sudo // check if user is in sudo and docker groupRemember to restart the SSH session and ssh as <my-name>@<my-ip>
Now that we have our own user, we can deactivate root login. I’ll use vim as text editor, but you can use nano if you prefer
sudo vim /etc/ssh/sshd_config# You can also use nano: sudo nano /etc/ssh/sshd_configTo keep ssh as the only way of connecting, deactivate root login and password authentication. Let’s even change port and allow the newly created user
PermitRootLogin noPasswordAuthentication noPort 2222 # change from default 22AllowUsers <my-name>Ok, let’s restart
sudo systemctl restart sshSet up SSH key auth for your user
# On your local machine, generate new ssh keys if you don't have them
# Copy public key to serverssh-copy-id -p 2222 yourusername@your_server_ipUsing SSH key for a newly created user
Create the .ssh directory (if it doesn’t exist)
mkdir -p /home/myname/.sshchmod 700 /home/myname/.sshchown myname:myname /home/myname/.sshAdd your new ssh public key
vim /home/myname/.ssh/authorized_keysNow let’s set permissions
chmod 600 /home/myname/.ssh/authorized_keyschown myname:myname /home/myname/.ssh/authorized_keysOk, we can now log in through SSH using:
ssh -i ~/.ssh/mykeyname -p 2222 myname@yourserveripUpdating everything
Before configuring our environment, it is better to updated essential packages
sudo apt update && sudo apt upgrade -yFine, now we can install a few utilities
apt install curl vim ufw git -ySetting up firewall
Ok, first of all let’s setup some firewall rules using ufw
$ ufw default deny incoming$ ufw default allow outgoingWe denied incoming traffic, but the rule is not active yet: we still have to enable the ufw service. Before doing that, we should avoid to be shut off of our beloved VPS, so we have to allow SSH connections
ufw allow 2222/tcp # your new SSH portufw allow 80/tcp # HTTPufw allow 443/tcp # HTTPSOk, now let’s enable our firewall
ufw enableAll right, we have a nice firewall up and running.
Installing Docker
Everything on our server should be Dockerized, so let’s start with installing the right tools:
# Install Dockercurl -fsSL https://get.docker.com -o get-docker.shsh get-docker.sh
# Add your user to docker groupsudo usermod -aG docker yourusername
# Install Docker Composesudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-composesudo chmod +x /usr/local/bin/docker-composeReverse proxy with traefik
Now it’s time to think about reverse proxy: we’ll use traefik, which is a good choice when using Docker.
mkdir -p ~/docker/{traefik,websites}cd ~/dockerCreate a docker network
docker network create webCreate a docker compose file for traefik
vim ~/docker/traefik/docker-compose.ymlservices: traefik: image: traefik:v3.0 container_name: traefik restart: unless-stopped ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik.yml:/traefik.yml:ro - ./acme.json:/acme.json - ./logs:/logs networks: - web environment: - TRAEFIK_API_DASHBOARD=true
networks: web: external: trueCreate traefik yml file configuration
vim ~/docker/traefik/traefik.ymlapi: dashboard: true insecure: false
entryPoints: web: address: ":80" http: redirections: entrypoint: to: websecure scheme: https websecure: address: ":443"
providers: docker: endpoint: "unix:///var/run/docker.sock" exposedByDefault: false network: web
certificatesResolvers: letsencrypt: acme: email: your-email@domain.com storage: acme.json httpChallenge: entryPoint: web
log: level: INFO filePath: "/logs/traefik.log"
accessLog: filePath: "/logs/access.log"Setup SSL certificate storage
cd ~/docker/traefiktouch acme.jsonchmod 600 acme.jsonmkdir logsAdding further security
Setup fail 2 ban
sudo apt install fail2ban -ysudo systemctl enable fail2bansudo systemctl start fail2banSetup security updates
sudo apt install unattended-upgrades -ysudo dpkg-reconfigure -plow unattended-upgradesConfigure log rotation
sudo vim /etc/logrotate.d/docker-logsAdd a few options:
/home/yourusername/docker/traefik/logs/*.log { daily rotate 30 compress delaycompress missingok notifempty create 644 yourusername yourusername}Good, now you can start traefik
cd ~/docker/traefikdocker compose up -dWe’re ready to go!
Ok, now let’s try to upload a static site:
First of all, create a new folder for alll future websites that will be hosted on our VPS
mkdir -p ~/docker/websitescd ~/docker/websitesNow upload the project folder
# Upload entire folderscp -P 2222 -i ~/.ssh/your_key_id -r /path/to/your/project yourusername@your_server_ip:~/docker/websites/We also need to setup docker:
vim docker-compose.ymlAnd configure traefik to handle certificates and correctly redirect from yourdomain.com to your project:
services: mysite: image: nginx:alpine container_name: mysite restart: unless-stopped volumes: - ./:/usr/share/nginx/html:ro networks: - web labels: - "traefik.enable=true" - "traefik.http.routers.mysite.rule=Host(`yourdomain.com`)" - "traefik.http.routers.mysite.entrypoints=websecure" - "traefik.http.routers.mysite.tls.certresolver=letsencrypt" - "traefik.http.services.mysite.loadbalancer.server.port=80"
networks: web: external: trueNow we can start the container
docker compose up -dCheck DNS propagation
nslookup yourdomain.com# this should print your Hetzner VPS IP. If it doesn't, try to allow some time for DNS propagationTraefik should get SSL certificates
docker logs traefik -fCheck if website can be reached
curl https://mysite.comNice! Now you have your static website ready to rock!
Next time we’ll talk about CI/CD using GitHub Actions