Sign in

Notes

Claude Code as a Linux sysadmin

Updated 2026-06-20
On this page

For a while now, Claude Code has effectively been the sysadmin for my servers. I describe what I want — provision a box, deploy a site, chase down a 502 — and it SSHes in, does the work, and tells me what it changed. The shift is from typing commands into a server to typing intent into a prompt. After setup, I rarely SSH in by hand.

This is how I have it wired up, running real production boxes (a couple of Linode VPSes behind Caddy) this way.

How it works

Claude Code runs on my local machine, not on the server. When it needs to touch a box, it runs ordinary ssh from my terminal:

ssh isis "systemctl status caddy"

That's the whole trick. The SSH key already lives in ~/.ssh on the machine where Claude Code runs, so I never paste a key anywhere — Claude just shells out to ssh and the OS handles auth. Same for provider CLIs: if the Linode CLI is configured locally, Claude can call it.

Persistent context comes from CLAUDE.md and Claude Code's automatic memory — not from re-explaining the server every session. I keep a short server doc that loads at the start of every session, so Claude already knows the layout.

Setup

  1. SSH access. Confirm you can ssh user@server from the machine running Claude Code (your key in ~/.ssh, the public key in the server's authorized\_keys). If it works in a plain terminal, it works for Claude. A ~/.ssh/config host alias makes everything cleaner — ssh isis beats memorizing an IP.

  2. A server doc in CLAUDE.md with the non-secret facts Claude needs: host alias and SSH user; what's installed (Caddy, Docker, systemd services, runtimes); where things live (web roots, the Caddyfile, log paths, deploy scripts); and how deploys work ("merging to main auto-deploys via the GitHub Action"). The richer this is, the fewer wrong turns it takes.

  3. Provider API access (Linode example). Keep a scoped Linode API token in the environment or a git-ignored file. Claude calls the Linode API/CLI to manage DNS and domains — without the token ever landing in a prompt or a committed file.

This works on any Linux VPS with SSH key auth — Linode, DigitalOcean, Hetzner, a plain EC2 box.

What it handles

  • Provisioning — Caddy or Nginx, UFW, fail2ban, unattended upgrades, users and permissions, reverse proxies, Let's Encrypt certs.

  • Deployments — pull from GitHub, run the build (Rust, Node, …), restart the service; add a new site or domain to the Caddyfile; auto-deploy on merge.

  • Troubleshooting — paste a 502 or a stack trace and it SSHes in, reads the right logs (app, system, Caddy access/error), forms a hypothesis, and fixes it.

  • Ongoing ops — system updates, resource checks, config and data backups, service restarts, reading custom audit logs.

The way I actually talk to it

Set up example.com to serve my Rust app in /var/www/myapp behind Caddy, with HTTPS.

I'm getting a 502 on mysite.com — here's the error: [paste]. Check the Caddy and app logs and fix it.

Point the A record for staging.mysite.com at the new server IP.

Run security updates on prod and tell me if anything looks off.

Short intent in; documented work out.

Guardrails (and why I leave them on)

Handing an agent SSH to production sounds reckless until you use the permission model. By default Claude Code asks before it runs a command, so I see the exact ssh … "…" before it executes. In settings.json I allowlist the boring read-only stuff so it stops asking (systemctl status, journalctl, git status, caddy validate) and deny the things I never want it doing unprompted. It moves fast on diagnostics and waits for a nod on anything that changes state.

Where it's weaker

This shines on Linux servers you reach over SSH. It's a worse fit where there's no shell to drive — GUI-only appliances, desktop apps — though pasting logs and errors for diagnosis still helps there.

TODO (Chris): a real war story here would land — the gnarliest thing you've had Claude fix on a live box.