$linuxjunkies
>

Set Up a Hetzner Cloud Server from Scratch

Create a Hetzner Cloud server from scratch: project setup, SSH key upload, cloud-init bootstrapping, cloud firewall, IPv6, and snapshots in one structured guide.

BeginnerUbuntuDebianFedoraArch9 min readUpdated June 7, 2026

Before you start

  • A Hetzner Cloud account with a project API token
  • hcloud CLI installed locally (or access to the Hetzner web console)
  • An Ed25519 SSH key pair generated on your local machine
  • Basic familiarity with SSH and the Linux command line

Hetzner Cloud offers excellent price-to-performance ratios and a clean API-driven interface. Whether you're deploying a personal VPS or a production workload, getting the foundation right—SSH keys, cloud-init bootstrapping, a tight firewall, IPv6, and snapshot backups—saves you from painful retrofits later. This guide walks through every layer from project creation to a verified, hardened server.

Create a Project and Upload Your SSH Key

All Hetzner Cloud resources live inside a project, which acts as an isolation boundary for billing, firewalls, and networks. Log in to console.hetzner.cloud, click New project, and give it a meaningful name (e.g., prod-web).

Before creating a server, upload your SSH public key so it can be injected at boot time rather than relying on a root password.

Generate a key if you don't have one

ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/hetzner_ed25519

Ed25519 is preferred over RSA for new keys: smaller, faster, and equally secure for this use case.

Add the public key to the project

In the Hetzner console, navigate to Security → SSH Keys → Add SSH Key. Paste the contents of ~/.ssh/hetzner_ed25519.pub. Give it a recognisable label like laptop-2025.

Alternatively, use the hcloud CLI (install it first):

# Debian/Ubuntu
sudo apt install hcloud-cli

# Fedora / RHEL family
sudo dnf install hcloud

# Arch
sudo pacman -S hcloud
hcloud context create prod-web   # enter your API token when prompted
hcloud ssh-key create --name laptop-2025 --public-key-from-file ~/.ssh/hetzner_ed25519.pub

Write a cloud-init User-Data File

cloud-init runs once on first boot and can create users, install packages, write files, and run arbitrary commands—all before you ever log in. Providing user-data eliminates manual setup steps and makes your server reproducible.

Create a local file called cloud-init.yml:

cat > ~/cloud-init.yml <<'EOF'
#cloud-config

# Create a non-root admin user
users:
  - name: deploy
    groups: [sudo, systemd-journal]
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-ed25519 AAAA...your-public-key... [email protected]

# Disable root SSH login and password auth
write_files:
  - path: /etc/ssh/sshd_config.d/99-harden.conf
    content: |
      PermitRootLogin no
      PasswordAuthentication no
      AuthenticationMethods publickey

# Update packages and install essentials
package_update: true
package_upgrade: true
packages:
  - fail2ban
  - unattended-upgrades
  - curl
  - vim

# Enable services
runcmd:
  - systemctl enable --now fail2ban
  - systemctl restart ssh
EOF

Replace the ssh_authorized_keys value with your actual public key string. The runcmd block uses systemctl directly—cloud-init runs as root, so no sudo is needed here.

Create the Server

In the console: Servers → Add Server. Pick a location close to your users, choose an OS (Ubuntu 24.04 LTS is a solid default), select your server type (the CX22 at 2 vCPUs / 4 GB RAM is a good starting point), attach your SSH key, and paste the contents of cloud-init.yml into the User data field.

With the CLI:

hcloud server create \
  --name web-01 \
  --type cx22 \
  --image ubuntu-24.04 \
  --location nbg1 \
  --ssh-key laptop-2025 \
  --user-data-from-file ~/cloud-init.yml

The server will be running within 30–60 seconds. Note the IPv4 and IPv6 addresses printed in the output.

Configure the Hetzner Cloud Firewall

Hetzner's cloud firewall is a network-level filter applied before traffic hits your server—independent of any in-guest firewall. Apply it as a first layer; run ufw or nftables inside the guest as a second layer.

Create and apply the firewall via CLI

# Allow SSH, HTTP, HTTPS inbound; block everything else
hcloud firewall create --name web-fw
hcloud firewall add-rule web-fw --direction in --protocol tcp --port 22 --source-ips 0.0.0.0/0 --source-ips ::/0
hcloud firewall add-rule web-fw --direction in --protocol tcp --port 80 --source-ips 0.0.0.0/0 --source-ips ::/0
hcloud firewall add-rule web-fw --direction in --protocol tcp --port 443 --source-ips 0.0.0.0/0 --source-ips ::/0
hcloud firewall apply-to-server web-fw --server web-01

Outbound traffic is permitted by default. If you only need SSH for now, omit the port 80/443 rules and add them later. Restricting SSH to your own IP (--source-ips 203.0.113.5/32) is strongly recommended if you have a static address.

Guest-level firewall with ufw (Ubuntu/Debian)

ssh deploy@<server-ipv4>
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
sudo ufw status verbose

Enable and Verify IPv6

Hetzner assigns a /64 IPv6 prefix to every server at no extra cost. Ubuntu 24.04 cloud images configure the primary IPv6 address automatically via SLAAC. Verify it is working:

ip -6 addr show eth0
curl -6 https://ifconfig.me

You should see your server's IPv6 address returned. If not, check that the interface has a global-scope address (not just link-local fe80::). On Hetzner, the full /64 block is routed to your server, so you can bind additional addresses from that range to the same interface:

# Add a second address from your /64 (replace the prefix)
sudo ip -6 addr add 2a01:4f8:xxxx:xxxx::2/64 dev eth0

To make it persistent, add a Netplan stanza on Ubuntu (/etc/netplan/60-extra-ipv6.yaml) or a systemd-networkd drop-in on other distros.

Take a Snapshot

Once your baseline server is configured and verified, take a snapshot before installing application software. This gives you a clean rollback point.

Snapshots require the server to be powered off for maximum consistency, though Hetzner does support live snapshots. Powering off is safer for boot-critical data:

hcloud server shutdown web-01
# Wait ~10 seconds for clean shutdown
hcloud server create-image --type snapshot --description "baseline-2025-07" web-01
hcloud server poweron web-01

List your snapshots with:

hcloud image list --type snapshot

Snapshots are billed at €0.0119/GB/month. They are not continuous backups—for those, enable Backups in the server settings (automated daily backups at 20% of the server cost).

Verify Everything

# Connect as the deploy user (not root)
ssh -i ~/.ssh/hetzner_ed25519 deploy@<server-ipv4>

# Confirm root login is blocked
ssh root@<server-ipv4>   # should be refused

# Check cloud-init completed successfully
sudo cloud-init status --long

# Verify fail2ban is active
systemctl is-active fail2ban

# Test IPv6 outbound
curl -6 https://ifconfig.me

# List active ufw rules
sudo ufw status numbered

Troubleshooting

  • SSH refused after creation: cloud-init may still be running. Wait 60–90 seconds and retry. Check the console's serial console (Server → Console) for boot output.
  • cloud-init didn't run my packages: Validate your YAML syntax with python3 -c "import yaml,sys; yaml.safe_load(sys.stdin)" < cloud-init.yml before next boot. A single indentation error silently skips entire sections.
  • Locked out of SSH: Use the Hetzner console's built-in Rescue system—boot into it, mount your disk, and fix /etc/ssh/sshd_config.d/99-harden.conf.
  • IPv6 shows only link-local address: Ensure the Hetzner network interface has IPv6 enabled. In rare cases, sysctl net.ipv6.conf.eth0.disable_ipv6 may be set to 1—set it to 0 and add the setting to /etc/sysctl.d/.
  • Firewall rule blocked a port you need: Hetzner's cloud firewall rules take effect immediately after creation. Use hcloud firewall describe web-fw to audit current rules, then add or delete as needed.
tested on:Ubuntu 24.04Debian 12

Frequently asked questions

Can I use a password instead of an SSH key on Hetzner Cloud?
Yes, but you shouldn't. Hetzner will email a root password if no SSH key is attached, but our cloud-init config immediately disables password authentication. SSH keys with Ed25519 are faster, simpler, and far more secure.
What is the difference between Hetzner snapshots and backups?
Snapshots are manual, point-in-time images you create yourself; they persist until deleted and are billed per GB. Backups are automated daily images enabled per-server at 20% of the server's hourly cost, with Hetzner retaining the last seven.
Do I need both the Hetzner Cloud Firewall and ufw inside the server?
The Hetzner firewall is a network-level filter that stops traffic before it reaches your NIC—it keeps your attack surface small even if the guest OS is misconfigured. ufw inside the guest gives you process-level control and is a good defence-in-depth practice.
Why use cloud-init instead of configuring things after first login?
cloud-init makes your setup reproducible and scriptable. If you need to rebuild or clone the server, re-attaching the same user-data file recreates the baseline automatically, with no manual steps and no chance of forgetting a hardening step.
How do I restore a server from a snapshot?
Run `hcloud server rebuild --image <snapshot-id> web-01` to overwrite the server's disk with the snapshot, or use `hcloud server create --image <snapshot-id>` to spin up a new server from it. The snapshot ID is shown by `hcloud image list --type snapshot`.

Related guides