Perché eseguo tutto su Hetzner
- infrastructure
- devops
- vps
- self-hosting
AWS e GCP vanno bene finché non vedi la fattura. Ecco perché ho spostato la mia infrastruttura su Hetzner VPS e non ci ho mai ripensato.
Ho usato DigitalOcean per tre anni. Poi AWS per un periodo. Lo schema era sempre lo stesso: iniziare in piccolo, aggiungere servizi, guardare la fattura salire oltre quello che qualsiasi persona sensata dovrebbe pagare per ospitare alcune web app e un database. L'anno scorso ho spostato tutto su Hetzner. Non me ne sono mai pentito.
La differenza di prezzo non è un errore di arrotondamento
Un Hetzner CPX31 ti offre 4 vCPU, 8 GB di RAM, 160 GB NVMe e 20 TB di traffico in uscita per circa 15 EUR al mese. L'equivalente più vicino su AWS EC2 (t3.large, memoria paragonabile, EBS gestito, trasferimento dati) supera gli 80 EUR se includi l'egress a volumi reali. Non è una differenza del 10% da ignorare. È cinque volte il prezzo.
Hetzner dedica i vCPU. Non sono core virtuali sovrascritti che spariscono quando un vicino rumoroso si avvia. Ho eseguito carichi CPU sostenuti sulle istanze CPX e i numeri reggono. Ottieni quello che dice la scheda tecnica.

Egress: la tassa nascosta che smetti di pagare
Ogni grande provider cloud ti addebita il trasferimento dati in uscita. AWS è famosamente costoso: $0,09 per GB dopo il primo GB. GCP è simile. Azure è leggermente meglio ma comunque pesante su grandi volumi. Hetzner include 20 TB di traffico in uscita in ogni piano VPS. Venti terabyte. Per la maggior parte dei progetti non raggiungerai mai quel limite.
Servo asset video, download di PDF pesanti e risposte API che possono essere verbose. Su AWS quei costi di traffico da soli aggiungevano da $30 a $60 al mese. Su Hetzner sono zero perché rimango dentro la franchigia inclusa. Una CDN davanti (il piano gratuito di Cloudflare va bene per la maggior parte dei pattern di traffico) fa sì che l'origine veda pochissimo egress grezzo.
Le tariffe di egress non sono un costo del fare business. Sono un meccanismo di fidelizzazione. Nel momento in cui accetti questa visione, cambiare fornitore diventa più semplice.
Storage NVMe e snapshot che funzionano davvero
Lo storage locale su NVMe delle istanze CPX e CCX è veloce. Non "veloce come il cloud" dove la scheda tecnica dice SSD ma la latenza racconta un'altra storia. Veloce davvero. Letture sequenziali ben oltre 1 GB/s nella pratica. Per un database Postgres o una cache di build Next.js quella differenza si sente.
Gli snapshot costano il 20% del prezzo del server al mese e sono a una chiamata API di distanza. Creo uno snapshot prima di ogni deploy. Se qualcosa va catastroficamente storto posso ripristinare in meno di due minuti tramite la Cloud Console o l'API REST. L'API è pulita e ben documentata, il che conta quando vuoi automatizzare queste cose.
- Snapshot: un click o API, fatturati al 20% della tariffa mensile del server
- Floating IP: riassegna tra server istantaneamente, nessun problema con i TTL del DNS
- Reti private: L2 flat tra i tuoi server nello stesso datacenter, gratuito
- Volumi: collega/scollega block storage persistente mentre il server è in esecuzione
- Firewall: regole stateful gestite dallo stesso pannello o API
Il compromesso onesto: sei tu a gestire l'uptime
Hetzner non astrae le operazioni come fa AWS. Non c'è RDS gestito, niente ECS, niente Lambda. Ottieni una macchina Linux e una rete pulita. Quello che ci fai è problema tuo. Questo è o terrificante o liberatorio a seconda della tua esperienza.
Ho risolto il problema dell'uptime in tre livelli. Primo, Ploi per il provisioning e i deploy. Ploi parla con l'API di Hetzner, avvia server, installa Nginx, runtime PHP o Node, configura SSL e gestisce deploy a zero downtime tramite Git hook. L'interfaccia è abbastanza semplice da usare davvero. Secondo, backup automatici del database su Hetzner Object Storage (compatibile S3) ogni sei ore. Terzo, Cloudflare davanti per protezione DDoS, caching e un edge globale così il VPS in singola regione non sembra lento agli utenti a Tokyo.
Provisioning e hardening in 60 righe
Ploi gestisce la configurazione del runtime, ma eseguo comunque uno script di hardening su ogni nuovo server prima di consegnarlo. Ecco il cuore di esso. Niente di intelligente, solo le basi che contano: disabilitare l'autenticazione con password, creare un utente non root, configurare UFW e regolare alcuni parametri del kernel.
#!/usr/bin/env bash
# harden.sh — run once as root immediately after provisioning
set -euo pipefail
NEW_USER="deploy"
SSH_PUBKEY="ssh-ed25519 AAAAC3Nza... your-key-here"
# Create deploy user with sudo, no password prompt for deploys
useradd -m -s /bin/bash "$NEW_USER"
usermod -aG sudo "$NEW_USER"
echo "$NEW_USER ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/"$NEW_USER"
# Inject SSH key
mkdir -p /home/"$NEW_USER"/.ssh
echo "$SSH_PUBKEY" > /home/"$NEW_USER"/.ssh/authorized_keys
chmod 700 /home/"$NEW_USER"/.ssh
chmod 600 /home/"$NEW_USER"/.ssh/authorized_keys
chown -R "$NEW_USER":"$NEW_USER" /home/"$NEW_USER"/.ssh
# Harden SSH
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
systemctl restart sshd
# UFW: allow SSH + HTTP + HTTPS only
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
# Kernel hardening: disable IP forwarding unless you need it, enable SYN cookies
cat >> /etc/sysctl.d/99-harden.conf <<EOF
net.ipv4.ip_forward = 0
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
net.core.somaxconn = 65535
EOF
sysctl --system
# Unattended security upgrades
apt-get install -y unattended-upgrades
dpkg-reconfigure -f noninteractive unattended-upgrades
echo "Hardening complete. SSH as $NEW_USER."Dopo l'esecuzione, Ploi subentra. Puntalo sull'IP del server, seleziona lo stack di runtime, inserisci l'URL del repo e una deploy key, e il primo deploy va in porto in circa tre minuti. I deploy successivi sono innescati da un Git push e si completano in meno di 30 secondi per la maggior parte delle app Node.

Usare tutto questo con un agente AI per il codice
Uso Claude Code come agente di coding sulla maggior parte dei miei progetti. Una cosa che conta molto quando un agente gestisce l'infrastruttura è la memoria a lungo termine. L'agente non ricorda gli IP dei tuoi server, le regole UFW o il motivo per cui hai scelto la porta 9000 per l'endpoint delle metriche. Ogni sessione riparte da zero a meno che tu non gli fornisca un contesto persistente.
La mia soluzione è un file HETZNER.md incluso nel repository alla radice del progetto. L'agente lo legge all'inizio di ogni sessione legata all'infrastruttura e ha il quadro completo: specifiche del server, indirizzi IP, servizi deployati, pianificazioni dei backup, problemi noti. Quando qualcosa cambia, chiedo all'agente di aggiornare il file. Diventa la fonte di verità che sopravvive ai reset della finestra di contesto.
Il file è breve e diretto. Niente prosa, solo fatti su cui l'agente può agire. Ecco un esempio reale di come appare:
# Hetzner Infrastructure Memory
## Servers
| Name | IP | Type | Region | OS |
|-------------|---------------|--------|--------|--------------|
| web-prod | 65.21.xxx.xxx | CPX31 | HEL1 | Ubuntu 24.04 |
| db-prod | 65.21.yyy.yyy | CPX21 | HEL1 | Ubuntu 24.04 |
## Deploy user: `deploy` (no-password sudo)
## SSH key: ed25519, stored in 1Password under "Hetzner deploy key"
## Services on web-prod
- Nginx 1.26, proxy to Node :3000
- PM2 manages the Node process (app name: `jumpinotech`)
- SSL via Let's Encrypt, auto-renew via certbot systemd timer
- Cloudflare proxied: DNS A record points to 65.21.xxx.xxx
## Database (db-prod)
- Postgres 16, port 5432, bound to private network only (10.0.0.x/24)
- Backups: pg_dump every 6h via cron, upload to Hetzner Object Storage bucket `db-backups-prod`
- Restore: `hetzner-restore.sh` in /home/deploy/scripts/
## Firewall (UFW on both servers)
- 22, 80, 443 open on web-prod
- 22, 5432 open on db-prod (5432 restricted to private network only)
## Snapshots
- Taken manually before each major deploy via Hetzner Cloud Console
- Naming convention: `web-prod-YYYY-MM-DD-pre-deploy`
## Known Gotchas
- Private network interface is eth1, not eth0 — use this for DB connection string
- Ploi deploy hook URL changes if you delete and recreate the site; update GitHub webhook
- Object Storage endpoint: `https://fsn1.your-objectstorage.com`
Con questo file nel repo, posso aprire una nuova sessione, dire "controlla lo script di backup su db-prod" e l'agente conosce l'IP, l'utente, il percorso dello script e l'endpoint dello storage senza che io debba ripetere nulla. Legge il markdown, forma un piano ed esegue comandi SSH o chiamate API con il contesto giusto. Nessun IP allucinato, nessuna supposizione sui nomi dei servizi.
Quando Hetzner non è la risposta giusta
I datacenter Hetzner si trovano in Germania, Finlandia e negli USA (Ashburn e Hillsboro). Se i tuoi requisiti di conformità richiedono residenza dei dati in APAC o hai bisogno di latenza a singola cifra di millisecondi per gli utenti a Sydney, stai guardando a un setup ibrido o a un provider diverso. Per i carichi di lavoro europei e nordamericani va bene.
I servizi gestiti sono assenti. Se hai bisogno di un cluster Redis gestito, Kafka gestito o un runtime di funzioni serverless, li aggiungerai da altrove o li eseguirai tu stesso. Per me non è un problema. Per un team senza esperienza ops potrebbe esserlo. Ploi copre molto del divario per le web app nello specifico, ma non sostituisce una PaaS completa.
Per tutto il resto, l'economia è difficile da contestare. Core dedicati, RAM reale, storage NVMe, egress generoso, API pulita e un'azienda europea che non nasconde le tariffe nei caratteri piccoli. Se esegui web app, progetti personali o strumenti interni e sei ancora su un hyperscaler per abitudine, tira fuori le ultime tre fatture e fai i conti. I numeri prenderanno la decisione al posto tuo.