How to Run Claude Code on a VPS: Beginner Step-by-Step Guide (2026)

run claude code on a vps

Short version: rent a €5 VPS, close every public port including SSH, reach the server only over Tailscale, and run Claude Code in tmux so it keeps working while your laptop is closed. Then hand the admin work to Claude with one copy-paste prompt.

Last week, Rob Hallam (@robj3d3) posted a setup on X that blew up: a cheap VPS that runs Claude Code around the clock, locked down so tight it’s invisible to the public internet, and administered entirely by the AI itself. You open an SSH app on your phone, type what you want, and your server does it.

The original post packs 16 steps into tweet format, and a lot of people in the replies called it complex. It’s not complex. It’s dense. So here’s the beginner-friendly version. I’ll show you how to run Claude Code on a VPS step by step, explain why each step exists, and give you a copy-paste agent prompt at the end that hands the whole server over to Claude.

Updated November 7, 2026: added what the recent SSH incidents (the Tailworm flaws in Tailscale SSH, the sshole OpenSSH bug, the SSHStalker botnet) mean for this setup, plus an sshd check, MagicDNS, Tailscale access rules, auto-updates, the Docker firewall trap and an optional exit node.

Table of Contents

What You’re Actually Building

Before touching anything, let’s get the mental model right. You’re setting up four things that work together:

  • A VPS (virtual private server): a small rented Linux machine that runs 24/7 for the price of two coffees a month.
  • Tailscale: a free private network between your devices (Tailscale calls it a tailnet). Your laptop, your phone, and your server can talk to each other, and nobody else can even see the server exists. Traffic is encrypted with WireGuard and goes directly between your devices when it can, or through Tailscale’s encrypted relays (DERP) when it can’t. Tailscale’s servers only hand out keys and rules, your traffic doesn’t pass through them.
  • Cloudflare: sits in front of your domain, so the only public traffic that ever reaches the server is web traffic coming through Cloudflare.
  • Claude Code: Anthropic’s terminal AI agent. It lives on the server inside a tmux session and does all the actual admin work. You just tell it what you want.

The end result: a server you never administer by hand. You connect from any device, say “set up a new site for this domain” or “why is the app down”, and Claude Code handles it. The security model means there’s no public SSH port for anyone to attack. Your entrance is a private tunnel.

Rob claims about 40 minutes for the whole setup. As a beginner, give yourself a relaxed afternoon. Nothing here is hard, but you’ll want to read as you go instead of racing through it.

What You Need

  • Around €5 to €10 a month for the VPS.
  • A domain you own (any registrar works, we’ll point it at Cloudflare).
  • A Claude account with a paid plan or API access, since Claude Code needs one to log in.
  • Free accounts for Cloudflare, Tailscale, and Termius. Tailscale’s Personal plan is free for up to 6 users with unlimited devices.
  • Zero Linux experience. That’s the point of the whole article.

Part 1: Buy Everything (About 15 Minutes)

Step 1: Get a cheap VPS

Go to Hetzner or DigitalOcean and create the smallest cloud server they offer. Pick Ubuntu 24.04 as the operating system. At checkout, tick the box for automatic backups. It costs a little extra and it will save you one day.

Being German, I’ll point you to Hetzner. Their small cloud instances are ridiculously good value and the data centers are in the EU. DigitalOcean is just as fine if you prefer it.

Don’t finish the creation process yet if your provider asks for an SSH key. We’ll generate one in Step 4, and adding it during creation is the cleanest way. If you already created the server, no problem, you can add the key afterwards too.

Step 2: Put your domain behind Cloudflare

Create a free Cloudflare account and add your domain. Cloudflare gives you two nameservers. Log into your registrar (where you bought the domain) and replace the existing nameservers with those two. Propagation usually takes minutes, sometimes a few hours.

Why bother? Because later we’ll configure the server firewall so that only Cloudflare can reach your websites. Attackers scanning the internet will find nothing. Cloudflare also gives you free HTTPS and caching on top.

Step 3: Install Termius and Tailscale on your devices

Install two apps on both your laptop and your phone:

  • Termius: a friendly SSH client. This is the app you’ll open to talk to your server. The free tier is enough.
  • Tailscale: the private network. Sign in with the same account on every device. Free tier covers this whole setup.

Why No Public SSH: The 2026 Picture

If “invisible server” sounds paranoid, look at this year:

  • Bots never stop. A SANS honeypot logged over 20 million SSH brute-force attempts in about 100 days (February to May 2026), most of them against root.
  • SSHStalker (February 2026). Flare found a botnet that brute-forced its way onto at least 7,000 Linux servers through weak SSH passwords, then planted its own SSH key as a backdoor. Key-only login (Step 4) stops the first part, and Step 10 shows you how to spot the second.
  • sshole, CVE-2026-123158 (November 5, 2026). An OpenSSH bug let attackers log in with an overlong password to accounts that had no usable password. It only hits musl-based systems like Alpine, not Ubuntu, and it’s fixed in OpenSSH 10.6p2. The lesson still applies to everyone: “password login is off” is an assumption, and a public port 22 lets the next bug test it.
  • Tailworm (late October 2026). A researcher published a batch of Tailscale flaws, the worst of them in Tailscale SSH, Tailscale’s optional built-in SSH server. An attacker who had already taken over one device that was allowed to log in to a server through Tailscale SSH could do more than that login should allow, in the worst case get root. Tailscale fixed the SSH and Serve issues in version 1.104.4 (bulletins TS-2026-014 to TS-2026-017, published October 26). The encrypted network itself wasn’t broken.

What that means for this guide:

  • No public SSH port at all. You reach the server only through Tailscale.
  • Regular OpenSSH with keys, with Tailscale SSH left off. Step 7 explains the difference.
  • Tailscale on auto-update, so the next fix arrives without you (Step 5).
  • Access rules so a lost or hacked phone can only reach what it needs to (Step 11).

Part 2: Lock It Down (About 30 Minutes)

This is the part that scared people off in the replies. Take it slow and follow the order exactly. The order matters because we test the private entrance before we brick up the public one.

Step 4: SSH keys only, never passwords

In Termius, go to Keychain and generate a new SSH key (Ed25519 is the default and it’s fine). An SSH key is a pair of files: a private half that never leaves your device and a public half you hand to the server. It replaces passwords entirely and can’t be brute-forced.

Paste the public key into your VPS provider’s SSH key field when creating the server. Password login should stay off forever. In Step 5 we’ll check that it actually is.

Step 5: First login, updates, and Tailscale on the server

In Termius, create a new host with your server’s public IP, user root, and your new key. Connect. You’re in a Linux shell now. Run updates first:

Terminalbash
apt update && apt upgrade -y

Still on the server, check that SSH really refuses passwords. Most providers turn password login off when you add a key, but verify it instead of trusting it:

Snippettext
sshd -T | grep -E '^(passwordauthentication|kbdinteractiveauthentication|permitrootlogin)'

You want to see passwordauthentication no, kbdinteractiveauthentication no and permitrootlogin without-password (that’s how sshd prints “keys only”). If any line says yes, add a small override file and reload SSH:

Snippettext
cat > /etc/ssh/sshd_config.d/00-hardening.conf <<'EOF'
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
EOF
sshd -t && systemctl reload ssh

The 00- prefix matters. For each setting, sshd uses the first value it reads, and files in that folder are read in alphabetical order, so yours beats a provider file like 50-cloud-init.conf. sshd -t checks the config first, so a typo can’t take SSH down. Keep your current session open and test a new connection before you close it.

Then install Tailscale on the server and log it into your network. The --hostname flag gives the server a short name you’ll use from now on:

Terminalbash
curl -fsSL https://tailscale.com/install.sh | sh
tailscale up --hostname=myserver

Tailscale prints a URL. Open it in your browser, approve the machine, done. Your server is now part of your private network and has a second IP address starting with 100.

Finally, turn on automatic updates for Tailscale and check the version:

Terminalbash
tailscale set --auto-update
tailscale version

The version should be 1.104.4 or newer, the release that fixed Tailworm. Ubuntu already installs security updates for everything else on its own through unattended-upgrades, so leave that running.

Step 6: Disable Tailscale key expiry for the server

Tailscale logins expire after a few months by default. Fine for laptops, terrible for a server you never want to touch. In the Tailscale admin console, find your server in the machine list, open the three-dot menu, and click Disable key expiry. One click, never think about it again.

Step 7: Verify SSH over Tailscale works BEFORE locking anything

This step is the seatbelt. In Termius, add a second host entry with the hostname myserver instead of the public IP. Tailscale’s MagicDNS (on by default) resolves that name on every device in your tailnet, so you never have to remember the 100.x address. Connect to it. If it works, great. If it doesn’t, stop and fix it before moving on.

Optional: in that Termius session (so on the server), you can look at the connection itself. Use your laptop’s or phone’s name as it appears in the first list:

Terminalbash
tailscale status
tailscale ping your-laptop-name

status lists every device in your tailnet. ping says whether the two are connected directly or via DERP (a relay). Both work, direct is just faster.

One thing that confuses a lot of people: what you just did is regular SSH over Tailscale. The server’s normal OpenSSH answers, and the keys in ~/.ssh/authorized_keys decide who gets in. Tailscale also has an optional feature called Tailscale SSH (tailscale set --ssh) that swaps in Tailscale’s own SSH server and logs you in by tailnet identity. That’s the part the Tailworm flaws were in. This guide doesn’t need it, so leave it off. It’s off unless someone turned it on, and tailscale set --ssh=false makes sure.

Do not skip this step. In the next one we slam the public door shut, and Tailscale becomes your only way in.

Step 8: Close the firewall

Now go to your provider’s firewall settings (Hetzner Cloud Firewall or DigitalOcean Cloud Firewall, in the web dashboard, not on the server itself). Delete every inbound rule. Then add exactly one: allow TCP port 443, but only from Cloudflare’s published IP ranges.

That’s it. No public SSH. No open ports for bots to poke at. Web visitors come in through Cloudflare on 443, and you come in through the Tailscale tunnel, which doesn’t need any inbound firewall rule at all. Leave outbound traffic open: Tailscale, updates and Claude Code all connect out.

Two notes on this:

  • Why the provider firewall and not ufw? It sits in front of the server, so nothing on the server can open a hole in it. That matters once Docker shows up: ports Docker publishes skip ufw’s rules completely, a classic trap. With a cloud firewall that can’t happen. If your provider has no cloud firewall and you use ufw instead, publish Docker ports on 127.0.0.1 only (the agent prompt below tells Claude to do that anyway).
  • Want zero open ports? A Cloudflare Tunnel (cloudflared) connects out to Cloudflare, so you can delete the 443 rule too. It’s one more piece to run, which is why this guide sticks with the 443 rule.

Step 9: Test from outside. The server is now invisible

Try to SSH to the public IP. It should hang and time out. Ping it, nothing. Then connect via myserver, works instantly. Optional: if you have nmap on your laptop, scan the public IP from there:

Terminalbash
nmap -Pn -p 22,80,443,3000,5432,8080 YOUR_PUBLIC_IP

Every port should say filtered. Port 443 shows filtered too, because only Cloudflare’s IPs may reach it. From the public internet, your server no longer exists. This is a much stronger position than the usual “install fail2ban and hope” approach.

Step 10: One SSH key per device

Generate a separate key in Termius on your phone and copy its public half (the line starting with ssh-ed25519). Then, on the server, add it to ~/.ssh/authorized_keys (one key per line). Paste your real key between the quotes:

Terminalbash
echo 'ssh-ed25519 AAAA...your-phone-key... phone' >> ~/.ssh/authorized_keys

Use >> (append), never >, or you overwrite the keys that are already there. Never copy a private key between devices. If you lose your phone, you delete one line on the server and remove the phone from the Tailscale admin console, and that device is locked out.

Once in a while, read that file with cat ~/.ssh/authorized_keys. You should recognize every line. A key you didn’t add is exactly the backdoor SSHStalker plants.

Step 11: Limit who can reach the server (Tailscale access rules)

Out of the box, Tailscale’s access policy lets every device in your tailnet reach every other device on every port. For one person with a laptop, a phone and a server, that’s fine to start with. But Tailworm’s whole attack started from one already-hacked device, so it’s worth narrowing: your devices may reach the server on SSH, and the server may reach nothing.

All of this happens in the Tailscale admin console in your browser. Do it in this order, so you never lock yourself out:

  1. Open Access controls. Add a tagOwners block to the existing policy (leave everything else alone) and save:
Snippettext
"tagOwners": {
  "tag:server": ["autogroup:admin"]
},
  1. On the Machines page, open your server’s three-dot menu, choose Edit ACL tags, add tag:server and save. Test SSH to myserver.
  2. Back in Access controls, replace the allow-all rule (the acls or grants block with * in it) with this, save, and test SSH again:
Snippettext
"grants": [
  {"src": ["autogroup:member"], "dst": ["tag:server"], "ip": ["tcp:22"]}
],

If a test fails, the policy editor keeps older versions you can restore, and the emergency console from the FAQ still works.

The rule only allows traffic to the server, so a hacked server can’t reach your laptop. It also stops your laptop and phone from talking to each other over Tailscale. If you use your tailnet for more (like the multi-machine setup in my Herdr guide below), add a grant for that, for example {"src": ["autogroup:member"], "dst": ["autogroup:member"], "ip": ["*"]}.

Part 3: Run Claude Code on the VPS (About 5 Minutes)

Step 12: Install tmux and Claude Code

Terminalbash
apt install tmux -y
curl -fsSL https://claude.ai/install.sh | bash

The second command is the official native installer for Claude Code. On first run it will ask you to log in with your Claude account by opening a URL on another device. Follow the prompt once and it stays logged in.

Step 13: Run Claude Code inside tmux

tmux is a terminal that keeps running on the server after you disconnect. This is what makes the whole thing magical. Claude Code can chew on a 20-minute task while your laptop is closed and your phone is in your pocket.

Terminalbash
tmux new -s claude
claude

To leave the session running and disconnect, press Ctrl+b, then d. To come back later from any device, run tmux attach -t claude. That’s the entire workflow you’ll use daily.

Once you run several agents at once, or agents on more than one machine, tmux gets crowded. That’s where Herdr comes in: a tmux-style terminal built for agents that shows which one is working, which one is done and which one needs you. In my Herdr guide and video I connect a Mac, a Linux laptop and a Windows PC into one Herdr over the same Tailscale + plain SSH setup as here, with Tailscale SSH off.

Part 4: Hand the Server Over to Claude

Here’s the clever bit of Rob’s setup. Instead of learning server administration, you write one long handover prompt that tells Claude Code everything about the machine: the facts, the security model, the folder conventions, and the standing rules. Then you make it write all of that into a CLAUDE.md file, which Claude Code reads automatically at the start of every future session.

Two details in the prompt matter more than the rest. First, you spell out the security model explicitly so Claude doesn’t helpfully “fix” your locked firewall when something seems unreachable. Second, backups come before features. A nightly job that pushes your data to a private GitHub repo, tested with a real restore, before a single page goes live.

After that, you let Claude install the web server itself. Rob recommends Caddy with the Cloudflare DNS plugin, because it gets HTTPS certificates via DNS and plays nicely with the locked-down firewall. I run Caddy behind Cloudflare myself, and it’s a solid combo (with one small gotcha around social cards I wrote about in Twitter Card Not Showing? The Cloudflare Redirect Trap).

From then on, you never administer the server again. You open Termius from anywhere and say what you want. If you’re curious what daily work with Claude Code looks like, I covered my own workflow in Ship Faster with ChatGPT Codex + Claude Code.

The Copy-Paste Agent Prompt

Here’s a full handover prompt you can paste into Claude Code as your first message. Replace the placeholders in square brackets with your own values first.

Snippettext
You are the system administrator of this server. I am a beginner and you handle all admin work from now on. Read everything below carefully. Then, before doing anything else, write all of this information into a CLAUDE.md file in the current directory so every future session starts with full context.

SERVER FACTS
- Provider: [Hetzner / DigitalOcean], smallest cloud instance, Ubuntu 24.04
- Public IP: [YOUR_PUBLIC_IP] (locked down, do not rely on it for access)
- Tailscale IP: [YOUR_100.x_ADDRESS]. All SSH access happens through Tailscale only.
- Domain: [yourdomain.com], DNS is managed in Cloudflare
- Automatic provider backups are enabled

SECURITY MODEL (never change this, never "fix" it)
- The provider firewall allows inbound TCP 443 from Cloudflare's published IP ranges only. Nothing else is open to the public internet, including SSH.
- SSH is regular OpenSSH, key-only, reached over Tailscale. Password authentication stays disabled permanently.
- Tailscale SSH (tailscale set --ssh) stays off. Never enable it.
- Tailscale auto-update stays on, and Tailscale must stay at version 1.104.4 or newer.
- I manage the Tailscale access policy in the admin console. Do not change it.
- Never open new public ports. Every new service you install must bind to localhost or the Tailscale interface only, unless I explicitly approve otherwise.
- If you use Docker, publish container ports on 127.0.0.1 only, never on 0.0.0.0.
- Never install or reconfigure firewall tools (ufw, iptables, fail2ban) without asking me first.
- If something seems unreachable from outside, the firewall is working as intended. Ask me before touching any network configuration.

FOLDER CONVENTIONS
- Each project lives in /srv/http/[domain], one folder per site or app.
- Each project runs in its own tmux session, named after the domain.
- The web server is Caddy with the Cloudflare DNS plugin. All sites are defined in one Caddyfile.
- Databases are SQLite files inside the project folder unless a project clearly outgrows that.

STANDING RULES
- Confirm with me before any destructive action: deleting files, dropping databases, removing packages, overwriting configs, anything involving rm.
- Backups before features. A nightly backup job that pushes my data to a private GitHub repository must exist and pass a real restore test before we deploy anything.
- Keep the system updated, but tell me before major version upgrades. Leave unattended-upgrades enabled.
- Tell me if ~/.ssh/authorized_keys ever contains a key I didn't add.
- After every task, summarize what you changed and where.
- When unsure about anything touching security, DNS, or data, ask instead of guessing.

MY PREFERENCES
- Explain what you are doing in plain language as you go. I want to learn.
- Prefer boring, standard tools over clever custom solutions.
- [Your timezone, language, stack preferences, anything else]

FIRST TASKS, IN THIS ORDER
1. Write this entire handover into CLAUDE.md.
2. Set up the nightly backup job to a private GitHub repo and prove it works with a restore test.
3. Install Caddy with the Cloudflare DNS plugin and serve a placeholder page for [yourdomain.com] over HTTPS.
4. Set up SQLite and show me where project data will live.

Confirm you have understood the security model before you start.

Optional: Use the Server as an Exit Node

Bonus feature: the server can double as your personal VPN on hotel or café Wi-Fi. An exit node sends all your internet traffic through the server, so websites see the server’s IP instead of yours. On the server:

Snippettext
echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-tailscale.conf
echo 'net.ipv6.conf.all.forwarding = 1' >> /etc/sysctl.d/99-tailscale.conf
sysctl -p /etc/sysctl.d/99-tailscale.conf
tailscale set --advertise-exit-node

If Tailscale says the change needs tailscale up, run tailscale up --hostname=myserver --advertise-exit-node.

Approve it in the admin console (server’s three-dot menu, Edit route settings, tick Use as exit node). If you set up the access rules from Step 11, also add {"src": ["autogroup:member"], "dst": ["autogroup:internet"], "ip": ["*"]} to grants. Then pick the server as exit node in the Tailscale app on your phone or laptop. Keep in mind that the server’s IP is tied to your hosting account, so this hides you from the Wi-Fi, not from the world.

FAQ

Do I need Linux experience to run Claude Code on a VPS?

No. You need to copy about five commands and click through a few dashboards. Everything after the setup is done by Claude Code itself, and the handover prompt tells it to explain its work in plain language.

How much does this setup cost per month?

The VPS is €5 to €10 a month. Cloudflare, Tailscale, and Termius all have free tiers that cover this use case (Tailscale’s Personal plan covers up to 6 users and unlimited devices). On top of that you need a paid Claude plan or API credits for Claude Code.

Is it safe to let an AI agent administer a server?

Safer than you’d think, if you do it like this. The server holds no secrets at the start, the firewall makes it invisible, backups run nightly, and the standing rules force Claude to confirm before destructive actions. The blast radius of a mistake is one cheap, backed-up box.

Why not just run Claude Code on my laptop?

Because a laptop sleeps, disconnects, and travels. The tmux session on the VPS keeps working while your laptop is closed. You can kick off a task from your phone on the train and check the result an hour later.

What if I lock myself out?

That’s what Step 7 prevents, and it’s why the order matters. If it happens anyway, every provider has a web-based emergency console in the dashboard that works regardless of the firewall. Log in there and fix your Tailscale connection. Find that console now, before you need it.

Should I turn on Tailscale SSH instead of regular SSH?

Not for this setup. Tailscale SSH has real upsides (no key files, logins tied to your Tailscale account), but it adds a second SSH server to trust, and that’s where the October 2026 Tailworm flaws were. Tailscale fixed them in 1.104.4 and doesn’t tell everyone to turn the feature off. Regular OpenSSH with keys over Tailscale is simpler and has decades of scrutiny behind it.

Is Tailscale still safe after Tailworm?

Yes. The flaws were in Tailscale SSH and Tailscale Serve, not in the encrypted network itself, and an attacker first needed a device already inside your tailnet. Keep auto-update on, keep Tailscale SSH off and narrow the access rules (Step 11), and this setup isn’t exposed to them.

Where to Go From Here

Follow the 13 steps once and you own an always-on AI-administered server for the price of a Netflix subscription. Start small: let Claude deploy a static page, then a little app with SQLite, and grow from there. If you want to peek over Claude’s shoulder now and then, my roundup of Linux monitoring tools is a good place to start.

And go follow Rob on X. This setup is his, and he ships stuff like this constantly. BOSH, as he’d say.

whoami

Stefan

Stefan founded Ceos3c in 2016 and has spent a decade teaching cybersecurity, Linux, and web development through hands-on tutorials. He builds and ships real products while sharing his journey: ProofSwap.me, XPMetric.com, and EasyEarningsTracker.com. Find him on YouTube (youtube.com/stefanrows) and X (@StefanRows).