All posts

blog

Managing several Hetzner servers from one desktop

Published

One server fits in your head. The second one is where the workflow quietly breaks — not because anything is hard, but because two machines that look the same are now one wrong window apart.

The moment it breaks

It is rarely dramatic. You run docker compose down in a terminal you thought was staging. You copy an .env to the right path on the wrong machine. Both servers have the same prompt, the same directories and the same container names, because you built them from the same notes.

The fixes below are cheap, and most of them are about making the machines easy to tell apart.

Make every server say its name

Give each server a hostname that says what it is, so the shell prompt says it too. A name the console suggested at creation, such as ubuntu-4gb-fsn1-1, tells you the size and the location, not the role.

sudo hostnamectl set-hostname prod-fsn1

Some people also colour the production prompt red. It sounds silly until the first time it stops you.

An SSH config alias for each server

~/.ssh/config turns addresses and options into short names. Every tool that uses your system SSH — ssh, scp, rsync, Docker contexts — picks the aliases up.

~/.ssh/config

Host prod
    HostName 203.0.113.10
    User deploy
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

Host staging
    HostName 203.0.113.20
    User deploy
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

IdentitiesOnly yes makes SSH offer only the key you named. Without it, an agent holding several keys offers each in turn, and the server can disconnect with “Too many authentication failures” before the right one comes up.

One key per machine you sit at

The tempting setup is one key per server. The better one is one key per computer you work from — your laptop, your desktop — added to every server that computer should reach.

The difference shows on a bad day. When a laptop is lost, you remove one public key from each server and everything else keeps working. With a key per server copied onto every laptop, a lost laptop means rotating every key you have.

Hetzner Cloud can store public keys in the project and add them when a server is created, so a new machine has the right keys from its first boot.

Docker contexts for the same commands everywhere

A Docker context per server lets you run the same command against any of them from your own shell, without logging in. The SSH alias does the connection work.

docker context create prod --docker host=ssh://prod
docker context create staging --docker host=ssh://staging

docker --context staging ps
docker --context prod logs -f --tail 100 shop-web-1

Prefer --context on each command over docker context use. A context you switched to an hour ago is the wrong-window problem again, moved into a setting you cannot see.

Keeping the same stack in step

Two servers running the same stack drift apart one manual fix at a time. Keep the compose file in Git and apply it to every server the same way instead of editing files on each machine.

With contexts, a loop is enough for stacks built from images and named volumes. Staging goes first, and a failure stops the loop before production. Bind-mount paths refer to the remote machine and the .env is read on your side, so check those before relying on it.

for host in staging prod; do
  docker --context "$host" compose -f compose.yml -p shop pull &&
    docker --context "$host" compose -f compose.yml -p shop up -d --remove-orphans ||
    break
done

At around five servers, or the first time two of them need different settings, a configuration-management tool starts to earn its learning curve.

When a rebuilt server looks like an attack

Rebuild a Hetzner server from the console and it comes back with new SSH host keys. Your next connection stops with “REMOTE HOST IDENTIFICATION HAS CHANGED”. That is exactly what a man-in-the-middle attack looks like — and exactly what a rebuild looks like.

If you rebuilt it yourself, remove the old entry and check the new fingerprint before you accept it, using the server’s web console in the Hetzner panel, which does not go over SSH:

# on your computer
ssh-keygen -R 203.0.113.10

# in the Hetzner web console, on the server
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub

Compare that output with the fingerprint SSH shows on the next connection. If you did not rebuild the server, remove nothing — find out why the key changed.

What one screen is actually worth

Measured honestly: with aliases, contexts and distinct hostnames, a terminal handles several servers well. What a single view adds is mostly not speed. It is seeing every server’s state at once — which one is low on disk, which container keeps restarting — and making the server you are acting on impossible to mistake.

If that is not a problem you have, the setup above is enough, and free.

Several servers in MihraOps

MihraOps keeps a server list with each machine’s containers and live CPU, memory and disk usage under its own name, and checks every server’s host fingerprint on each connection. A changed fingerprint is refused, not offered as a warning to click past.

The Free plan includes 1 server. Pro removes the limit — which is about where the problem this post describes begins.

Read next