← All Posts

Shipping from a Phone: Claude Code on a $4 VPS

5 min readApril 23, 2026
claude-codevpshetznertermiustailscaletmuxmobiletraveldevops

Traveling with a laptop is a tax. Traveling without one, if you rely on Claude Code every day, feels like an unacceptable loss.

A few trips into this setup, I stopped packing my MacBook for short trips. An iPhone and a $4-a-month server do the job. This is how the pieces fit.

Why a VPS, not a laptop and not a cloud shell

I tried the alternatives first.

Just bring the laptop — works, but carrying a MacBook into a crowded MTR car in Hong Kong feels heavier every year.

Cloud shell or browser IDEs — fine for a one-off fix. But my Claude Code isn't vanilla: specific ~/.claude/ config, a memory folder full of project context, MCPs for Gmail/Slack/Jira/Notion, custom skills, keybindings. Rebuilding that on every fresh cloud shell is not how I want to spend my mornings.

A VPS I own — one-time setup, persistent config, my keys, my history. Same Claude Code, same memory, same everything. From any device that can SSH, I have my full setup.

How the pieces fit
Phone + Termius
SSH client
Tailscale mesh
WireGuard, private
Hetzner VPS
tmux + Claude Code
Anthropic API
Claude models

Step 1 — Provisioning the Hetzner box

I picked Hetzner for three reasons: cheap, ARM (cheaper still), and the console is actually usable. The entry-level ARM server — CAX11, 2 cores, 4 GB RAM, 40 GB NVMe — runs €3.79/month. About $4 USD. I put mine in Helsinki (hel1) for balanced latency across the US/Asia corridor.

Walkthrough:

  1. Hetzner Cloud console → new project → new server.
  2. Image: Ubuntu 24.04, ARM64.
  3. Type: CAX11.
  4. Location: closest to where you expect to be most (travel-friendly).
  5. SSH key: paste your public key during creation. Never set a root password — skipping the SSH-key step makes Hetzner email you a temporary password, giving you 24 hours of SSH-with-password open to the internet. Skip that window entirely.
  6. Cloud Firewall: attach one that allows only SSH (port 22) inbound. Deny everything else.
  7. Create. Two minutes later, SSH in:
ssh -i ~/.ssh/hetzner_key root@<your-ip>

First things on the box:

apt update && apt upgrade -y
apt install -y fail2ban tmux git curl
systemctl enable --now fail2ban

Fail2ban watches SSH auth failures and auto-bans IPs after repeated bad attempts. On a public-IP VPS, it's not optional.

Step 2 — Installing Claude Code

Claude Code installs via npm:

# Node.js (ARM64 build)
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install -y nodejs

# Claude Code
npm install -g @anthropic-ai/claude-code

# First run — OAuth flow
claude
# Follow the printed URL, paste the code back, done.

Auth is OAuth for Claude Pro/Max users — you open the URL in any browser (your phone's browser works fine), click through, paste a code back into Claude Code. Less than a minute.

After that, I copied my ~/CLAUDE.md and memory folder from the Mac via rsync. (I've since put that into a git-backed vault — separate post. Start with git from day one if you can.)

Step 3 — An SSH client that's actually good on a phone

I'm on an iPhone, so I use Termius. Android users can do the same thing with Termux from F-Droid, which has a proper package manager — if that's you, pkg install openssh and you're off. For iOS, Termius is what works.

What makes Termius actually usable as a terminal on a phone:

  • A modifier-key toolbar above the software keyboard — Ctrl, Alt, Esc, Tab, |, ^C, ^I. Every keybinding that lives in muscle memory just works.
  • Keys in the Secure Enclave. Generate inside the app, the private key never leaves the phone.
  • Per-host config. One entry, one tap, you're in.
  • A ridiculous little AI command generator in the toolbar, which I never use but is there.

Setup is five minutes:

  1. Hosts → + → new host.
  2. Hostname: the Tailscale name (Step 4) or the Hetzner IP if you're still on public SSH.
  3. User: root.
  4. Keys → Generate key → set as default.
  5. Copy the public key it shows you, paste it into ~/.ssh/authorized_keys on the VPS (via an existing SSH session from your Mac).
  6. Tap the host entry. You're in.
Termius on iPhone, SSH'd into the Hetzner VPS with a Claude Code tmux session running
Termius on iPhone, SSH'd into the Hetzner VPS with a Claude Code tmux session running

But SSH over the public internet from random hotel Wi-Fi has two things against it: port 22 has to stay open to 0.0.0.0 (fail2ban helps but doesn't eliminate the exposure), and mobile networks do weird NAT things that can drop sessions mid-thought.

Step 4 — Tailscale for the mesh

Tailscale is a WireGuard-based mesh VPN. Each device authenticates once via a web flow and gets a stable 100.x.x.x IP that other devices on your mesh can reach. No ports open to the internet, no key distribution hell.

What Tailscale bought me:

  • Close SSH to the public. Port 22 now reachable only from my Tailscale mesh.
  • Stable internal DNS. I SSH to my-vps instead of an IP. Tailscale resolves it.
  • No NAT surprises. Hotel, airport, mobile data, home — same mesh, same hostname.
  • Free tier that's genuinely enough. 100 devices on the personal plan.

On the VPS:

curl -fsSL https://tailscale.com/install.sh | sh
tailscale up
# Follow the auth URL, done.

On the phone: install the Tailscale app, sign in, flip it on. Termius (or Termux) inherits the mesh — SSH to the Tailscale hostname works immediately.

After Tailscale was up, I changed the Hetzner Cloud Firewall to allow SSH only from Tailscale's address range (100.64.0.0/10). The VPS's public IP now refuses SSH from the open internet entirely.

SSH exposure: before vs after
Before
Port 22 open to 0.0.0.0/0
Every bot on the internet can probe. Fail2ban helps but doesn't eliminate.
After
Port 22 reachable only from 100.64.0.0/10
Only devices I've authenticated into my mesh can even see SSH.

Step 5 — tmux: the session that outlives your phone

The one thing mobile SSH sessions do unreliably: stay connected. Your phone locks, backgrounds the SSH app, switches networks, and the session drops — sometimes mid-Claude-Code-run.

tmux decouples the shell from the SSH session. Run claude inside a tmux pane and when your SSH dies, tmux keeps running on the server. Reconnect, tmux attach, you're back in the same Claude Code conversation.

# First time
tmux new -s work       # named session
# ... run Claude Code, do work ...

# SSH drops / phone locks / flight takes off

# Reconnect — from any device
ssh my-vps
tmux attach -t work    # back exactly where you left off

Once tmux attach became the first thing I typed after every SSH, the mobile-ness of the setup disappeared. It was just a terminal.

The Hong Kong moment

A few days into this setup, I was in a Hong Kong hotel, laptop intentionally left behind that morning to lighten my bag. An n8n workflow I'd built earlier that week was failing silently and I wanted to debug it before the context rotted out of my head. At a dim sum place, I did this:

What I shipped between dumplings
+0:00
Open Termius. ssh my-vps. tmux attach -t work.
+0:02
Claude Code still running from this morning. Type: "The n8n autoposter is failing at the Tavily node. Check the last three runs, find the error, draft the fix."
+0:03
Set phone down. Eat.
+0:20
Come back. Read the diff. Approve. Claude commits and pushes.
+0:22
Workflow fixed. Ordered another basket of siu mai.

That would have been impossible from a phone browser. Every tool Claude Code needed — MCPs, skills, memory, git, npm — lives on the VPS. The phone was just the screen.

What this costs and what it doesn't

Monthly: €3.79 Hetzner + $0 Tailscale (free tier) + Termius (free tier works; their paid tier is nicer) = roughly $4/month total.

One-time: about three hours end-to-end, including the UFW misconfiguration I gave myself before realizing Hetzner's Cloud Firewall was the right primitive. Without that detour, an hour is enough.

Not monetary but real: phone typing is slower. Screen is small. A bluetooth keyboard and a 10" tablet is a much better travel setup for trips over a few days. I'll do that next time.

What I'd do differently

Install Tailscale on day one. I ran with public-IP SSH for a few weeks before adding it. Unnecessary attack surface, and Tailscale is a five-minute install. Don't procrastinate it.

Don't hesitate on ARM. Claude Code, Node, Python, Docker, git, the Anthropic SDK — all work great on ARM. I spent ten minutes worrying about compat before installing. Zero minutes of actual compat problems.

Git your config from the start. I did this afterwards — copying Claude Code state from Mac to VPS was a rsync-and-hope instead of a git clone. Starting fresh, put your ~/.claude/ in a private repo and clone it onto the VPS.

Don't over-harden. I spent an hour on UFW then locked myself out when it clashed with Docker's iptables rules. Hetzner Cloud Firewall + fail2ban + Tailscale is enough. More isn't better.


The realization underneath all this: Claude Code is a networked agent, not a local app. It talks to the Anthropic API, reads your files, runs commands. None of that needs to live on the same device as your keyboard. Put it on a server you trust, reach it through a mesh, and your dev environment is wherever you are — including a noodle shop in Tsim Sha Tsui with a phone in your hand.