Run Claude Code safely on a server
Claude Code can run any command your user can. What the skip permissions flag changes, and how to contain the blast radius, from sandbox to disposable VPS.
What running Claude Code safely on a server means
To run Claude Code safely on a server, keep its permission prompts on, run it as a dedicated unprivileged user, and give unattended runs a real boundary instead of trust: the built-in sandbox, a container, or a disposable VPS that holds nothing you care about. The --dangerously-skip-permissions flag removes the approval step between the model and your shell. That trade can be reasonable for unattended work, but only inside a boundary that limits what one bad command can reach. This guide explains what the flag actually changes, and how to build that boundary in rungs of increasing isolation.
What Claude Code can do on your box
Claude Code is a coding agent that runs in your terminal. It reads files, writes files, and runs shell commands as the user who started it. That is the whole value of the tool: it can clone a repository, edit code, run the tests, read the failure, and fix the code in a loop, without you typing each command. If you have not set it up on a server yet, running Claude Code on a VPS with tmux covers the install and the session handling. This page covers the power you hand it once it is there.
The risk is the same sentence read a second time. A process that runs shell commands as your user can do anything your user can do. It can read ~/.ssh/id_ed25519, ~/.aws/credentials, and every .env file your user can open. It can run curl and send data to any host the server can reach. It can run git push --force. The agent has no motive of its own. The danger is that a task goes wrong, or that text it read while working carried instructions written by someone else: a web page it fetched, or a comment in an issue it was asked to fix. That second case is called prompt injection, and it is why "the model is usually sensible" is not a security plan. You plan for the bad run, not the average one.
The permission system in plain words
Out of the box, Claude Code asks before it acts. Reading files inside the project happens silently, but editing a file or running a shell command shows you the exact edit or command first and waits for a yes. You can approve one action, or approve that kind of action for the rest of the session. Those approvals are session-scoped: quit the CLI, and the next session starts cautious again. For rules you want to keep, the settings file holds persistent allow, ask, and deny lists. For example: allow git status, ask on git push, deny reads of .env. Deny rules always win.
This design assumes a human is watching the terminal, and on a laptop that is true. On a server, the point is often that nobody is watching. You start a long task inside tmux and go to bed, and an agent that stops to ask a question at 2 a.m. makes no progress until morning. That is the honest reason people reach for the skip flag on servers, and the problem it solves is real. The rest of this guide is about solving it without giving up every guardrail.
What --dangerously-skip-permissions changes
claude --dangerously-skip-permissions turns the approval step off. Edits happen without a prompt. Shell commands run without a prompt. The protected-path checks that normally guard sensitive locations are skipped too. Your explicit deny rules still apply, and a few extreme actions still stop to ask, but the working summary is simple: whatever the model decides to run, runs.
Two facts about the flag matter on a server. First, it is blocked when Claude Code runs as root or under sudo on Linux and macOS, because root with no prompts can change any file or service on the machine. The agent needs its own unprivileged account anyway, and the flag enforces that. Second, the flag does not change the model's behaviour in any way. It removes the human from the loop and changes nothing else, so every mistake a prompt would have caught now executes.
So here is the honest calculus. If you skip permissions, the security question changes from "will the agent do something bad" to "how much damage can one bad action do". You stop trying to control every decision and start controlling the blast radius. Containment is the answer, and it comes in rungs.
The built-in Claude Code sandbox
Before the rungs, know that Claude Code now ships an OS-level sandbox for the commands it runs, and it removes most of the reasons people reached for the skip flag. On Linux it uses bubblewrap for filesystem isolation, plus socat to route network traffic through a proxy. Inside the sandbox, a command can write only to the project directory and a session temp directory, and it can reach the network only through a proxy that checks every domain against an allow list. The first time a command wants a new domain, Claude Code asks you.
Turn it on with the /sandbox command inside a session. On Ubuntu and Debian, install the two packages it needs first:
sudo apt install bubblewrap socat
On Ubuntu 24.04 and later, the default AppArmor policy stops bubblewrap from creating the user namespaces it needs. The sandbox panel tells you when something is missing, and the Claude Code sandboxing documentation carries the short AppArmor profile that fixes it.
The sandbox has an auto-allow mode: sandboxed commands run without any prompt, because the enforced boundary now does the work the prompt used to do. Commands that cannot run inside the sandbox fall back to the normal permission flow, so the genuinely unusual actions still ask. For most server workflows this is the correct replacement for the skip flag, because you get far fewer questions with an OS-enforced boundary instead of none.
Be honest about its limits. By default a sandboxed command can still read most of the filesystem, including credential files, unless you deny those paths; the sandbox.credentials setting exists exactly for that. The network proxy checks domain names and does not inspect the traffic itself, so a broad allow such as github.com still leaves room to move data out. Docker does not work inside it. The sandbox raises the floor a lot. It is not a complete isolation boundary, which is why the rungs below still matter.
The containment ladder
Three rungs, in increasing order of isolation. Pick the lowest one that matches what else lives on the box.
Rung 1: a dedicated unprivileged user. The agent gets its own account, its own home directory, its own project directory, and no sudo:
sudo adduser --disabled-password --gecos "" agent
The account boundary keeps the agent out of your files: your SSH keys and every other project on the machine. It also makes the skip flag usable at all, since the flag refuses to run as root. This is the same principle as running every service as an unprivileged user, applied to an agent. What rung 1 does not bound: the network, and anything on the box that is world-readable.
Rung 2: a container. Anthropic publishes a reference devcontainer that runs Claude Code as a non-root user, with firewall rules that limit which hosts the agent can reach, and a container you build yourself does the same job. The filesystem shrinks to the volumes you mount, and egress shrinks to what the container's rules allow. This is the right middle rung when the server hosts other services you care about. Its limit is that containers share the host kernel, and one careless mount undoes the boundary; hand the container /var/run/docker.sock and it can reach the whole host.
Rung 3: a dedicated VPS. The strongest rung is the bluntest: give the agent an entire machine that contains nothing you care about. A small VPS costs a few dollars a month. Set it up with the first ten minutes on a new VPS runbook, snapshot the clean state, and let the agent work. Nothing else lives there. No personal SSH key, only a deploy key scoped to the one repository. No cloud credentials, no production data. When a run goes wrong, or when you simply want a clean slate, restore the snapshot or destroy and rebuild the box in minutes. The blast radius is the rent. This is the setup where --dangerously-skip-permissions stops being frightening, because the worst realistic outcome is a rebuilt server and one revoked token.
The rungs stack. A sandboxed agent, running as an unprivileged user, on a disposable VPS, costs almost nothing extra and makes the failure stories boring. Boring is the goal.
Protect the credentials
The rule that pays for everything else: the agent's user must not be able to read secrets that belong to anything else.
Give the API key to the agent and to nothing else. Put it in a file owned by the agent's user with mode 600, and load it when a shell starts:
install -m 600 /dev/null /home/agent/claude.env
echo 'export ANTHROPIC_API_KEY=your-key-here' >> /home/agent/claude.env
echo 'source ~/claude.env' >> /home/agent/.bashrc
Then close the other direction. On Debian and Ubuntu, home directories are often created readable by every user on the box, so tighten your own: chmod 750 /home/youruser. Check with ls -ld /home/* and fix anything the agent's account can list.
Scope every token. A fine-grained GitHub token limited to one repository, or a per-repository deploy key, means a leaked credential loses one project and not your whole account. If you use the sandbox, add its credential settings so ~/.ssh and ~/.aws are denied even for reads. And keep production credentials off the box entirely, because an agent cannot leak a secret that was never there.
Git is the safety net
Every change the agent makes should be reviewable and revertable, and git gives you both for free if the agent works on a branch:
git switch -c agent/refactor-auth
Review the run afterwards with git diff main...agent/refactor-auth, merge what is good, and delete the branch if the run went nowhere. Protect the main branch on the forge side, so the agent's token cannot push to it and cannot force-push anywhere. The commit history doubles as an audit log of what happened while you slept, which is worth more than any amount of terminal scrollback.
The network is part of the blast radius
An agent can run curl. That sentence is the whole egress problem: whatever the agent can read, it can also send somewhere, and a prompt-injected agent might. A plain unprivileged user does not bound this at all, because any user can reach anything the server can reach. The sandbox bounds it by domain through its proxy. A container can bound it with its own firewall rules. A dedicated VPS bounds what there is to leak in the first place, which is the most robust answer of the three.
Do not try to solve egress with ufw alone. ufw allows all outgoing traffic by default, and writing outbound rules that still permit apt, npm, git, and the Claude API is fiddly work that breaks quietly. Choose the boundary at the sandbox, container, or machine level instead, where a domain allow list or a bare machine does the same job cleanly.
If you are building your own agent against the API rather than running Claude Code, the same thinking applies unchanged. Building an AI agent with Claude on a VPS covers that path, and its agent deserves the same dedicated user, the same scoped tokens, and the same disposable box.
Harden the box first
Whichever rung you choose, the machine itself still needs the basics before the agent moves in: SSH keys only, no root login, a default-deny firewall, automatic security updates. Generate your checklist here and work down it once:
FAQ
Is --dangerously-skip-permissions safe to use on a server?
Not by itself. The flag removes every approval prompt, so the first bad command runs the moment the model produces it. It becomes a defensible trade when the blast radius is contained: a dedicated unprivileged user at minimum, and for genuinely unattended work a container or a disposable VPS that holds one project and one scoped token. Never use it on a machine that holds production credentials or data you cannot lose.
Does Claude Code have a sandbox?
Yes. Claude Code ships a built-in sandbox for shell commands, opened with the /sandbox command. It uses bubblewrap on Linux and Seatbelt on macOS, limits writes to the project directory, and routes network access through a proxy that only permits approved domains. Its auto-allow mode runs sandboxed commands without prompts, so it cuts interruptions the way the skip flag does while keeping an OS-enforced boundary. It is not a complete isolation boundary, so pair it with a dedicated user or a dedicated machine for unattended runs.
Why does the skip flag refuse to run as root?
Because root with no permission prompts can modify any file and any service on the system, Claude Code blocks --dangerously-skip-permissions when it runs as root or under sudo on Linux and macOS. The fix is not to fight the check. Create an unprivileged user for the agent and run it there; that account boundary is the first and cheapest layer of containment.
Can Claude Code read my SSH keys and .env files?
It can read whatever the user it runs as can read, and even the sandbox's default policy allows reads of credential paths until you deny them. So run the agent as its own user, keep your own home directory at mode 750 or tighter, deny credential paths in the sandbox settings, and keep production secrets off the machine entirely. A secret the box never held cannot be read or leaked.
What is the safest way to run Claude Code unattended?
A cheap dedicated VPS used only for agent work: hardened in ten minutes, snapshotted clean, running Claude Code under an unprivileged user with the sandbox on, a mode-600 file holding the API key, a per-repository deploy key, and all work on branches you review before merging. If a run goes wrong, you revoke one token and restore the snapshot, and nothing else you own is affected.