Notes
Claude Code as a Linux sysadmin
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
SSH access. Confirm you can
ssh user@serverfrom the machine running Claude Code (your key in~/.ssh, the public key in the server'sauthorized\_keys). If it works in a plain terminal, it works for Claude. A~/.ssh/confighost alias makes everything cleaner —ssh isisbeats memorizing an IP.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
mainauto-deploys via the GitHub Action"). The richer this is, the fewer wrong turns it takes.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/myappbehind 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.
Details
- Section:
- Notes
- Updated:
- 2026-06-20
More in this section
Related pages