Stop Giving AI Agents Your SSH Keys
You want your AI agent to deploy code, restart services, and check logs across your fleet. The standard approach is to hand it an SSH key with broad access and hope for the best. There's a better way.
If you're a CTO or DevOps lead in 2026, you have a problem. Your engineers are using AI agents — Claude Code, Codex, Devin, OpenCode — and those agents need to touch your infrastructure. Deploy a binary. Restart a daemon. Pull a log file. Check disk usage on the database server.
The agent needs remote access. And the standard answer — "just give it SSH" — is the wrong answer.
Why SSH + AI agents is a security nightmare
Let's walk through what happens when you give an AI agent SSH access to your fleet:
1. Key sprawl. You generate a keypair for the agent. Where does the private key live? On the developer's laptop? In the agent's sandbox? In a CI secret store? Now you have an SSH private key that can access every server, sitting in a place you don't fully control. Rotate it every 90 days? Nobody does.
2. No scoping. SSH keys don't have permissions. A key either connects to a host or it doesn't. You can't say "this agent can restart nginx but can't read /etc/shadow." You can't say "this agent can access staging but not production." You end up creating per-host keys, per-user accounts, sudoers rules — a whole access management layer that exists because SSH has no concept of scoped operations.
3. Firewall holes. The agent needs to reach port 22 on every target. If the targets are behind NAT, in a VPC, or on a corporate network, you're opening firewall rules, setting up bastion hosts, or building VPN tunnels — just so the agent can connect. Every open port is attack surface.
4. No audit trail. SSH logs tell you someone connected. They don't tell you what the agent did, what commands it ran, what files it transferred, or whether those actions were appropriate. You find out about problems when something breaks, not when they happen.
5. The agent doesn't know your fleet. SSH requires the agent to know hostnames, IP addresses, key paths, users, ports. Your infrastructure changes — servers are added, renamed, retired. The agent's SSH config is stale within a week.
The relay model: outbound-only, no inbound access
remotecmd takes a different approach. Instead of the agent connecting directly to each server, every machine connects outbound to a central relay. The agent talks to the relay; the relay forwards to the target.
This flips the security model on its head:
- No inbound ports. Target machines open zero ports to the internet. They connect outbound to the relay over WebSocket (port 443, looks like HTTPS). No firewall rules, no bastion hosts, no VPN.
- No SSH keys. The agent authenticates to the relay with a token. The relay authenticates targets with a separate daemon token. At no point does the agent have a shell on the target — it sends commands through the relay, and the daemon executes them.
- NAT traversal for free. Because targets connect outbound, it works behind NAT, corporate firewalls, and VPCs. The agent doesn't need to know the target's IP — just its name.
- Every command is logged. The relay sees every command, every file transfer, every result. You get an audit trail by default, not by configuring syslog.
What an agent workflow looks like
Here's a real workflow. An AI agent needs to deploy a new binary to 6 servers, restart the daemon, and verify it's healthy:
# Agent deploys to all targets in one command
$ remotecmd-cli exec --targets all --cmd "remotecmd-cli update"
# Agent copies a config file
$ remotecmd-cli cp --target db1 --src ./config.yaml --dst /etc/app/config.yaml
# Agent restarts and verifies
$ remotecmd-cli exec --target web1 --cmd "sudo systemctl restart app && curl -s localhost:8080/health"
# Agent pulls logs for debugging
$ remotecmd-cli exec --target web1 --cmd "journalctl -u app --since '5 min ago' --no-pager"
No SSH. No key management. No firewall configuration. The agent discovered the fleet from --targets all, which the relay resolves to the 6 registered daemons. Every command went through the relay and was logged.
But is it fast enough for production?
A relay adds a hop. Every command goes client → relay → target → relay → client. That's latency. For interactive commands (exec), it's negligible — a few milliseconds. For file transfers, it matters.
I spent two weeks optimizing this. The result: parallel TCP streams that auto-tune based on file size. A 50MB binary transfer over a 77ms RTT relay link takes 72 seconds — 1.21× faster than scp doing the same transfer directly. The relay doesn't slow you down; the parallel streams make you faster than a single SSH connection.
Read the full performance writeup →
The DevOps case: fleet management without MDM
If you're managing a fleet of servers — VPS instances, LXC containers, edge devices, Raspberry Pis — you probably have a mix of tools: Ansible for config management, a monitoring agent for metrics, SSH for ad-hoc work, maybe a VPN for access. remotecmd replaces the SSH + VPN layer:
- Onboarding a new server takes one command:
remotecmd-cli daemon start. It connects to the relay, registers itself, and is immediately addressable by name. No Ansible inventory update, no SSH key deployment, no firewall rule. - Ad-hoc operations work without SSH:
remotecmd-cli exec --target db1 --cmd "df -h". Run it from your laptop, from a CI pipeline, from an AI agent — same command, same result. - File distribution is built in:
remotecmd-cli cp --target all --src ./deploy.sh --dst /opt/app/deploy.sh. One to many, parallel transfers, integrity verified. - Self-updating fleet:
remotecmd-cli exec --targets all --cmd "remotecmd-cli update". Every server pulls the latest binary, verifies the SHA256, smoke-tests it, and atomically swaps. 6 servers updated in 30 seconds.
The CTO case: agent-ready infrastructure
Here's the strategic question: is your infrastructure ready for AI agents?
In 2026, your engineers are using AI agents that can write code, run tests, and deploy changes. But those agents hit a wall when they need to touch a remote server. You either:
- Give them SSH access — and accept the security debt, the key management overhead, and the firewall configuration that comes with it.
- Build a custom API — for every operation the agent needs. Deploy endpoint, restart endpoint, logs endpoint. Six months of engineering work, and now you're maintaining a second infrastructure API alongside SSH.
- Say no — and your engineers do the deploy manually. The agent writes the code, the human ships it. You've built a CI/CD pipeline with a human bottleneck.
remotecmd is the fourth option. One binary on each server, one relay, one CLI. Agents get scoped remote access through a single authenticated channel. No SSH keys, no custom APIs, no human bottleneck.
Open source, self-hosted, no vendor lock-in
remotecmd is open source on GitHub. The relay is a single Go binary you can self-host on any VPS. The daemon is the same binary in a different mode. The CLI is the same binary again. One binary, three roles, zero dependencies.
Self-host the relay on a $5 VPS. Run the daemon on every server you manage. Distribute the CLI to your engineers and their agents. You own the infrastructure — no SaaS dependency, no per-seat pricing, no vendor lock-in.
The open-source CLI is at v2.4.1 and the hosted relay at v1.6.0: parallel TCP streams for fast file transfers, fleet-wide auto-update, cron scheduling, health monitoring, team management, and — as of the latest release — agent tokens and an MCP server. It runs my own fleet of fifteen machines across three countries.
Update: the token now exists
When I first published this, the argument was ahead of the software. I could tell you why an agent should hold a scoped credential instead of an SSH key, but the honest answer to "so how do I mint one?" was a fiddly sequence of flags most people would get wrong. As of v1.6.0 it is one command:
$ rcmd agent-token --targets web1 --expires 4h --label claude
Token: tok_operator_9f2c...
Role: operator
Targets: web1
Expires: in 4h0m
MCP config:
{ "mcpServers": { "rcmd": {
"command": "rcmd", "args": ["mcp"],
"env": { "RCMD_TOKEN": "tok_operator_9f2c..." } } } }
Revoke anytime: rcmd team revoke --token tok_operator_9f2c...
Paste the config into your agent and it has four tools — list targets, exec, copy, health. What it does not have is a say in what those reach. --targets is mandatory, so there is no accidental fleet-wide agent token. Expiry defaults to 24 hours, because a credential that outlives its task is a liability and an agent is not around later to notice it still works. --role viewer gives you an agent that can investigate and cannot execute.
The refusals are deliberately readable: viewer role cannot perform exec, access denied to target db1. That is not politeness. An agent that gets a vague error retries; an agent that is told it lacks the role stops. The difference shows up as load on your servers.
What I found while building it
Writing the audit trail is what made me read my own audit code properly, and two things fell out that nobody had reported.
The audit log was storing raw sub-tokens in plaintext — on disk, five thousand entries deep, and handed back over the wire to anyone who ran the audit command. The log whose entire purpose is accountability was also the most convenient place on the system to harvest working credentials, helpfully annotated with each one's role and scope. It now stores a fingerprint, and existing logs get hashed the first time a patched relay reads them. When I deployed, production told me it had migrated two real entries.
The second was worse in kind if not in blast radius: the alert and webhook handlers authenticated but never authorized. They checked that your token was valid and then stopped asking questions. A read-only viewer token — the role that exists specifically to be safe — could repoint the account's alert email, or register a webhook and quietly receive every command event on the account, signing secret included.
Both are fixed. I mention them because the pitch of this post is that scoped credentials with an audit trail beat SSH keys, and that pitch is worth nothing if the audit trail is itself a credential store. The uncomfortable version of the lesson: I had shipped the roles, the scoping and the log, and still had a privilege escalation sitting in the least-privileged role. Writing the tests is what found it, not the code review I had already given it.
The rest of the seatbelt
Two more things landed in the same release, both aimed at the specific ways an autonomous agent fails rather than the ways a human does.
Everything is written down, with outcomes. The audit log previously covered only exec, which meant an operator could copy a database dump off a host with rcmd cp and leave no trace at all. It now covers file transfers, tunnels, scheduled jobs and multi-target fan-out, and it records what actually happened — the real exit code and duration, not a row of zeros written before the command had run. A command lost to a disconnect is marked as such rather than quietly missing, because the interesting entries are exactly the ones that would otherwise vanish.
Rate limits, per token. A retry loop with no backoff is the normal failure mode of an autonomous agent — it does not get bored, and it cannot tell a permanent failure from a transient one. The relay now throttles per token and replies with how long to wait, so a confused agent backs off instead of spinning, and one noisy agent cannot starve a teammate or take a host down.
None of this makes an agent safe to hand root. It makes the blast radius something you chose on purpose, with an expiry date and a receipt.
Get started in 5 minutes
# 1. Install the CLI (Linux/macOS)
$ curl -sL https://github.com/javimosch/remotecmd-cli/releases/latest/download/remotecmd-cli-linux-amd64 -o ~/.local/bin/remotecmd-cli
$ chmod +x ~/.local/bin/remotecmd-cli
# 2. Start a relay (on your VPS)
$ remotecmd-cli relay daemon start --port 3032
# 3. Start a daemon on each target server
$ remotecmd-cli daemon start --relay wss://your-relay:3032 --token YOUR_TOKEN
# 4. Run commands from anywhere
$ remotecmd-cli exec --target web1 --cmd "uptime"
$ remotecmd-cli cp --target web1 --src ./app --dst /opt/app
That's it. No SSH keys. No firewall rules. No bastion host. Your servers are now addressable by name from anywhere, and your AI agents can manage them through a single, auditable channel.
The bottom line
SSH was designed for humans typing into terminals. It was not designed for AI agents that need scoped, auditable access to a fleet of servers across multiple networks. The security model — private keys with no operation-level permissions, inbound ports on every target, no native audit trail — is a poor fit for the agent era.
remotecmd is purpose-built for this. Relay-based architecture, outbound-only connections, token authentication, automatic fleet discovery, parallel file transfers, and a single binary that runs everywhere. Open source, self-hosted, no lock-in.
Your agents don't need SSH keys. They need remotecmd.
→ GitHub: github.com/javimosch/remotecmd
→ Connect with me on LinkedIn