Claude Code sulla tua VPS: codice da ovunque, anche dal telefono
Cosa otterrai: Claude Code che gira sul tuo server Hetzner, accessibile da qualsiasi device: terminale desktop, browser, o telefono. Dai istruzioni in linguaggio naturale; Claude edita il codice, lancia comandi, committa e pusha. Coolify prende il push e fa il deploy in automatico.
Perché far girare Claude Code sul server?
Potresti far girare Claude Code in locale e collegarlo a GitHub. Ma farlo girare sul server ti dà qualcosa di diverso:
- Accesso diretto al codebase, container Docker, database e log
- Niente sync di file o upload necessari
- Le sessioni persistono tra cambi di device: parti dal desktop, continui dal telefono
- Coolify auto-deploya a ogni push senza dover lasciare il server
Il telefono diventa un thin client. Tutto il calcolo e il contesto vivono sulla VPS.
Prerequisiti
- VPS Hetzner con Coolify in funzione (vedi Setup del server)
- Tailscale installato e blindato (vedi Tailscale e Firewall)
- Accesso SSH via Tailscale (
ssh -p 2222 [email protected]) - Un abbonamento Claude Pro/Max o una API key Anthropic
Installa Claude Code
SSH al server e installa Node.js + Claude Code:
# Node.js 22
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install -y nodejs
# Claude Code
npm install -g @anthropic-ai/claude-code
claude --version
Installa tmux se non c'è:
apt install -y tmux
Clona la tua repo
Coolify costruisce le immagini Docker da GitHub; non tiene un clone locale sul server. A Claude Code serve una copia locale per lavorare.
Genera una chiave SSH per l'accesso a GitHub:
ssh-keygen -t ed25519 -C "p1-hetzner" -f ~/.ssh/id_ed25519 -N ""
cat ~/.ssh/id_ed25519.pub
Aggiungi la chiave pubblica su GitHub → Settings → SSH and GPG keys → New SSH key.
Poi clona:
ssh-keyscan github.com >> ~/.ssh/known_hosts
git clone [email protected]:YOUR_USER/platform.git /root/platform
git -C /root/platform config user.name "Il Tuo Nome"
git -C /root/platform config user.email "[email protected]"
Configura i permessi auto
Di default, Claude Code chiede conferma prima di ogni edit di file o comando bash. Per uso da server, disabilitalo:
mkdir -p ~/.claude
cat > ~/.claude/settings.json << 'EOF'
{
"permissions": {
"allow": [
"Bash(*)",
"Read(*)",
"Write(*)",
"Edit(*)",
"Glob(*)",
"Grep(*)",
"WebFetch(*)",
"TodoWrite(*)"
],
"deny": []
}
}
EOF
Claude Code ora editerà file, eseguirà comandi e committerà senza fermarsi a chiedere.
Sistema fail2ban per Tailscale
fail2ban monitora i log SSH e banna gli IP dopo tentativi falliti. I tuoi nodi Tailscale stanno nel range 100.64.0.0/10; mettili in whitelist così fail2ban non ti chiude mai fuori:
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
ignoreip = 127.0.0.1/8 ::1 100.64.0.0/10
[sshd]
enabled = true
port = 2222
EOF
systemctl restart fail2ban
Per sbannare un IP subito:
fail2ban-client set sshd unbanip 100.87.104.91
Sistema il ts-input di Tailscale (importante)
Le versioni recenti di Tailscale aggiungono una catena iptables chiamata ts-input che implementa il filtro host basato su ACL. Di default droppa tutto il traffico peer Tailscale (100.64.0.0/10) prima ancora che UFW lo veda, incluse le tue connessioni SSH.
Controlla se la regola è presente:
iptables -L ts-input -n --line-numbers
Se vedi DROP -- 100.64.0.0/10, Tailscale sta bloccando l'SSH peer. Crea un servizio systemd per rimuoverla in automatico a ogni boot:
cat > /etc/systemd/system/tailscale-fix-iptables.service << 'EOF'
[Unit]
Description=Remove Tailscale DROP rule for peer SSH access
After=tailscaled.service
Wants=tailscaled.service
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'sleep 3; iptables -D ts-input $(iptables -L ts-input --line-numbers -n | grep "DROP.*100.64.0.0/10" | awk "{print $1}") 2>/dev/null || true'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable tailscale-fix-iptables
systemctl start tailscale-fix-iptables
Perché succede: Tailscale forza la sua policy ACL a livello iptables. Se l'ACL non permette esplicitamente una porta, Tailscale droppa il traffico. Il fix permanente è aggiornare la tua ACL Tailscale su tailscale.com/admin/acls per permettere tutto il traffico tra i tuoi nodi ({"acls": [{"action": "accept", "src": ["*"], "dst": ["*:*"]}]}). Finché non lo fai, il servizio systemd se ne occupa a ogni restart.
Alias di comodità
Aggiungi a ~/.zshrc:
# Avvia o ri-attacca la sessione Claude Code
alias dev='cd /root/platform && TERM=xterm-256color tmux new-session -A -s dev'
alias claude-attach='TERM=xterm-256color tmux attach -t dev'
Lancia Claude Code
dev
claude
Al primo avvio, Claude Code rileva la tua env var ANTHROPIC_API_KEY e ti chiede se usarla. Se hai un abbonamento Claude Pro/Max, scegli No e autenticati con l'account. Costa meno e usa la quota dell'abbonamento.
Collegati dal telefono
Claude Code ha una modalità Remote Control che fa da ponte tra il tuo telefono e la sessione del server in esecuzione.
Dentro Claude Code:
/rc
Claude Code attiva il Remote Control e stampa l'URL della sessione:
/remote-control is active. Code in CLI or at
https://claude.ai/code/session_XXXX
Sul telefono:
- Apri l'app Claude (deve essere aggiornata) → le sessioni Remote Control compaiono in automatico
- Oppure apri direttamente l'URL della sessione nel browser del telefono
Adesso stai controllando la sessione del server dal telefono. Scrivi istruzioni; Claude edita codice, esegue test, committa, pusha.
Workflow: desktop vs telefono
Da desktop (controllo completo):
ssh -i ~/.ssh/hetzner-platform -o IdentitiesOnly=yes -p 2222 [email protected]
dev # avvia/ri-attacca tmux + va nella repo
claude # lancia Claude Code
Usalo quando devi vedere i diff, rivedere l'output con cura o lavorare su modifiche complesse multi-file.
Da telefono (istruzioni veloci):
- Apri l'app Claude → tap sulla sessione Remote Control attiva
- Scrivi l'istruzione: "aggiungi un endpoint /health all'API"
- Guarda Claude lavorare in real time sul server
- Quando ha finito, Coolify prende il push e fa il deploy
Usalo per fix rapidi, piccole feature o per controllare lo stato lontano dalla scrivania.
Riconnetterti dopo una pausa:
La sessione tmux resta viva anche quando ti disconnetti. Per riprendere da dove avevi lasciato:
# Da desktop
ssh [email protected] -p 2222
claude-attach # ri-attacca alla sessione esistente
# Da telefono
# Apri l'app Claude → la sessione è ancora elencata, tap per riconnetterti
Hetzner Cloud Firewall: cosa tenere
Docker bypassa UFW, quindi il Cloud Firewall di Hetzner è la vera porta per i servizi Coolify. Ecco il setup minimo sicuro:
| Porta | Protocollo | Sorgente | Scopo |
|---|---|---|---|
| 2222 | TCP | 100.64.0.0/10 | SSH (solo Tailscale) |
| 80 | TCP | Range IPv4 Cloudflare | HTTP (via proxy Cloudflare) |
| 443 | TCP | Range IPv4 + IPv6 Cloudflare | HTTPS (via proxy Cloudflare) |
| 8000 | TCP | Tuo IP + CIDR webhook GitHub | Dashboard Coolify + auto-deploy |
| 6001 | TCP | Tuo IP | Coolify real-time |
| 6002 | TCP | Tuo IP | Terminale Coolify |
| ICMP | any | 100.64.0.0/10 | Ping (solo Tailscale) |
Cosa rimuovere:
- Porta 3000 esposta direttamente (le app girano dietro Traefik su 80/443)
Any IPv4 / Any IPv6sulle porte 80/443 se sei dietro al proxy Cloudflare: chiunque può bypassare Cloudflare e arrivare al tuo origin- Vecchi IP personali sulla 2222 una volta che sei completamente su Tailscale
Range IPv4 Cloudflare (aggiungili tutti alle porte 80 e 443):
173.245.48.0/20
103.21.244.0/22
103.22.200.0/22
103.31.4.0/22
141.101.64.0/18
108.162.192.0/18
190.93.240.0/20
188.114.96.0/20
197.234.240.0/22
198.41.128.0/17
162.158.0.0/15
104.16.0.0/13
104.24.0.0/14
172.64.0.0/13
131.0.72.0/22
IPv6:
2400:cb00::/32
2606:4700::/32
2803:f800::/32
2405:b500::/32
2405:8100::/32
2a06:98c0::/29
2c0f:f248::/32
Troubleshooting
| Problema | Causa | Fix |
|---|---|---|
ssh: Connection refused via Tailscale | Regola DROP ts-input attiva | iptables -D ts-input $(iptables -L ts-input --line-numbers -n | grep "DROP.*100.64" | awk '{print $1}') |
ssh: Connection refused dopo molti tentativi | Ban di fail2ban | fail2ban-client set sshd unbanip YOUR_TAILSCALE_IP |
missing or unsuitable terminal: xterm-ghostty | tmux non conosce Ghostty | Usa TERM=xterm-256color tmux ... |
| Remote Control non visibile nell'app Claude | App non aggiornata | Aggiorna l'app Claude dallo store |
| Sito giù dopo modifiche al firewall | IP Cloudflare rimossi da 80/443 | Riaggiungi i range IP Cloudflare a entrambe le porte |
| Claude Code chiede conferma | settings.json mancante | Crea ~/.claude/settings.json con le regole di allow di sopra |