Run Claude Code on a VPS: Install, Secure, and Persist Sessions

10 min read·Matthieu|

Set up Claude Code on a remote Linux server with headless authentication, tmux session persistence, and proper security hardening. Every command verified.

Claude Code is a terminal-based AI coding agent. It reads your codebase, edits files, runs commands, and builds features from plain-language descriptions. Running it on a VPS gives you a persistent coding agent that stays alive 24/7, accessible from any device over SSH.

This guide takes you from a fresh Ubuntu VPS to a working Claude Code setup with session persistence. Every step includes a verification command so you know it worked.

What does Claude Code need from a VPS?

Claude Code sends your prompts to Anthropic's servers for inference. Your VPS only holds project files, dev tools, and the Claude Code process itself. This means you do not need a GPU or high-end hardware.

Requirement Minimum Recommended
OS Ubuntu 20.04 LTS Ubuntu 24.04 LTS
RAM 4 GB 8 GB
Disk 20 GB SSD 40 GB NVMe
CPU 2 vCPUs 4 vCPUs
Network Stable internet Low-latency connection
Dependencies None (native installer) git, ripgrep (bundled)

The native installer bundles ripgrep and requires no runtime dependencies like Node.js. A 4 GB VPS handles Claude Code and a medium-sized codebase. If you work with large monorepos or run additional services, go with 8 GB.

Account requirement: Claude Code requires a Claude Pro, Max, Teams, Enterprise, or Console account. The free Claude.ai plan does not include Claude Code access.

How do you secure a VPS before installing Claude Code?

Lock down your server before installing anything. A VPS gets hit by automated brute-force attacks within minutes of going live. These steps create a non-root user, enforce SSH key authentication, set up a firewall, and enable automatic security updates.

If you already have a hardened server, skip to How do you install Claude Code on a Linux server?. For a deeper walkthrough of each security step, see .

Create a non-root user

SSH into your server as root, then create a user with sudo privileges:

ssh root@YOUR_SERVER_IP
adduser claude
usermod -aG sudo claude

Copy your SSH key to the new user so you can log in without a password:

mkdir -p /home/claude/.ssh
cp /root/.ssh/authorized_keys /home/claude/.ssh/authorized_keys
chown -R claude:claude /home/claude/.ssh
chmod 700 /home/claude/.ssh
chmod 600 /home/claude/.ssh/authorized_keys

Verify you can log in as the new user from a second terminal before continuing. Do not close your root session yet:

ssh claude@YOUR_SERVER_IP

You should get a shell prompt as claude@yourserver. If this works, proceed.

Disable password authentication

Edit the SSH config to block password-based logins:

sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config

Restart SSH and verify the settings took effect:

sudo systemctl restart ssh
sudo sshd -T | grep -E 'passwordauthentication|permitrootlogin'

Expected output (order may vary):

permitrootlogin no
passwordauthentication no

Set up the firewall

UFW (Uncomplicated Firewall) blocks all incoming traffic except what you allow. On some minimal VPS images, you may need to install it first with sudo apt install -y ufw.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enable

Verify the rules:

sudo ufw status

Expected output:

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)

Install fail2ban

fail2ban bans IP addresses after repeated failed SSH login attempts:

sudo apt update && sudo apt install -y fail2ban

Create a local config so your settings survive package updates:

sudo tee /etc/fail2ban/jail.local > /dev/null << 'EOF'
[sshd]
enabled = true
port = ssh
filter = sshd
maxretry = 5
bantime = 3600
findtime = 600
EOF

Enable and verify:

sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban

Sharp eyes: the output should show active (running). The enable --now flag makes fail2ban start immediately and survive reboots.

sudo fail2ban-client status sshd

This shows the jail is active with 0 currently banned IPs (expected on a fresh setup).

Enable automatic security updates

Unattended-upgrades patches security vulnerabilities without manual intervention:

sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

Select "Yes" when prompted. Verify it is active:

sudo systemctl status unattended-upgrades

The output should show active (running).

How do you install Claude Code on a Linux server?

The native installer is the recommended method. It requires no dependencies, auto-updates in the background, and runs on any supported Linux distribution. The npm installation method is deprecated.

Run the installer as your non-root user (not root):

curl -fsSL https://claude.ai/install.sh | bash

This is the official Anthropic installer. If you prefer to verify before running, you can check the setup docs for SHA256 checksums and code signing details.

The installer places the binary at ~/.local/bin/claude and adds it to your PATH. Open a new shell or source your profile to pick up the change:

source ~/.bashrc

Verify the installation:

claude --version

You should see a version number like 2.x.x. Run the built-in diagnostic to check your setup:

claude doctor

This checks your system configuration, network connectivity, and authentication status. At this point, authentication will show as incomplete. That is expected.

How do you authenticate Claude Code on a headless VPS?

Claude Code normally opens a browser window for OAuth login. On a headless server with no GUI, you need a different approach. You have two options depending on your account type.

Option A: API key from Claude Console

If you have a Claude Console account (API-based billing), set the ANTHROPIC_API_KEY environment variable. This is the simplest method for headless servers.

Generate an API key at console.anthropic.com. Then store it securely on your VPS:

sudo mkdir -p /etc/claude-code
sudo tee /etc/claude-code/env > /dev/null << 'EOF'
ANTHROPIC_API_KEY=sk-ant-api03-your-key-here
EOF
sudo chmod 600 /etc/claude-code/env
sudo chown claude:claude /etc/claude-code/env

What this does: creates a dedicated file with restricted permissions (only your user can read it) instead of putting secrets in .bashrc where they show up in shell history and are readable by other processes.

Load it in your shell profile. Add this line to ~/.bashrc:

echo 'set -a; source /etc/claude-code/env; set +a' >> ~/.bashrc
source ~/.bashrc

Verify the key is loaded:

echo $ANTHROPIC_API_KEY | head -c 15

You should see sk-ant-api03-xx (the first 15 characters of your key).

Option B: OAuth token from Claude subscription

If you use a Claude Pro or Max subscription (not Console), generate an OAuth token on a machine with a browser, then transfer it to the VPS.

On your local machine (the one with a browser):

claude setup-token

This opens a browser for OAuth authentication and outputs a long-lived token starting with sk-ant-oat01-. The token is valid for one year.

Copy the token, then on your VPS, store it the same way:

sudo tee /etc/claude-code/env > /dev/null << 'EOF'
CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-your-token-here
EOF
sudo chmod 600 /etc/claude-code/env
sudo chown claude:claude /etc/claude-code/env

You also need to create ~/.claude.json to skip the onboarding wizard:

cat > ~/.claude.json << 'EOF'
{
  "hasCompletedOnboarding": true
}
EOF
chmod 600 ~/.claude.json

Load it in your shell profile:

echo 'set -a; source /etc/claude-code/env; set +a' >> ~/.bashrc
source ~/.bashrc

Verify authentication

Test that Claude Code can reach Anthropic's servers:

claude -p "Say hello in exactly 5 words"

The -p flag runs a one-shot prompt without entering interactive mode. If you see a five-word greeting, authentication works. If you get an authentication error, check that your environment variable is set correctly with env | grep -E 'ANTHROPIC_API_KEY|CLAUDE_CODE_OAUTH_TOKEN'.

How do you keep Claude Code running after SSH disconnect?

Use tmux. It creates terminal sessions that persist on the server independently of your SSH connection. Close your laptop, fly to another country, SSH back in from your phone. The session is still there, exactly where you left it.

Install tmux

sudo apt install -y tmux

Verify:

tmux -V

Configure tmux for Claude Code

Create a .tmux.conf with settings optimized for long coding sessions:

cat > ~/.tmux.conf << 'EOF'
# Increase scrollback buffer (default is 2000 lines)
set -g history-limit 50000

# Enable mouse support for scrolling and pane selection
set -g mouse on

# Start window numbering at 1
set -g base-index 1

# Reduce escape time for faster key response
set -sg escape-time 10

# Status bar with session name and time
set -g status-right '%H:%M %d-%b'

# Keep sessions alive if the shell exits unexpectedly
set -g remain-on-exit on
EOF

Start a Claude Code session

Create a named tmux session and launch Claude Code inside it:

tmux new -s claude

You are now inside a tmux session named "claude". Start Claude Code:

cd ~/your-project
claude

Claude Code is running. To detach from the session (leave it running in the background), press:

Ctrl+B, then D

You are back at your regular SSH prompt. Claude Code continues running inside tmux.

tmux quick reference

Action Command
Create named session tmux new -s claude
Detach from session Ctrl+B, then D
List sessions tmux ls
Reattach to session tmux attach -t claude
Kill session tmux kill-session -t claude
Scroll up in tmux Ctrl+B, then [, then arrow keys
Exit scroll mode q

How do you reconnect to a Claude Code session from another device?

SSH into your VPS from any device and reattach:

ssh claude@YOUR_SERVER_IP
tmux attach -t claude

Your Claude Code session is exactly where you left it. The conversation history, file context, and any running tasks are preserved. This works from a laptop, phone (using an SSH app like Termius), or another server.

If the session crashed or was terminated, tmux attach will fail. Start a new one:

tmux ls

If no sessions are listed, create a fresh one with tmux new -s claude.

How do you restrict what Claude Code can do on your server?

Claude Code has a permission system that controls which tools it can use. By default, it asks for confirmation before running bash commands or editing files. You can pre-approve safe commands and block dangerous ones using .claude/settings.json in your project directory.

Never use --dangerously-skip-permissions on a real server. It bypasses all safety prompts and lets Claude Code run any command without asking.

Set up a permission allowlist

Create a project-level settings file:

cd ~/your-project
mkdir -p .claude
cat > .claude/settings.json << 'EOF'
{
  "permissions": {
    "allow": [
      "Bash(npm run *)",
      "Bash(npm test *)",
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(git log *)",
      "Bash(git add *)",
      "Bash(git commit *)",
      "Bash(ls *)",
      "Bash(cat *)",
      "Read",
      "Bash(grep *)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Bash(git push --force *)",
      "Bash(chmod 777 *)",
      "Bash(curl * | bash)",
      "Read(./.env)",
      "Read(./.env.*)"
    ]
  }
}
EOF

What this does: the allow array lets Claude Code run those commands without asking each time. The deny array blocks dangerous commands entirely. Rules follow a deny-first precedence: if a command matches both allow and deny, the deny wins.

Verify the file was created with correct permissions:

ls -la .claude/settings.json

The file should be readable by your user. Since this file gets committed to git, it applies to everyone who clones the project. For personal overrides, use .claude/settings.local.json instead (automatically gitignored).

How do you monitor resources on your VPS?

Claude Code on a 4 GB VPS uses between 200-400 MB of RAM during typical operation. Large projects or multiple concurrent tasks push this higher. Setting up swap space prevents out-of-memory kills.

Add swap space

Check if swap already exists:

sudo swapon --show

If the output is empty, create a 2 GB swap file:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

Make it persist across reboots by adding it to /etc/fstab:

echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Verify:

free -h

You should see your swap space listed. Sharp eyes: look for the Swap: row showing 2.0Gi total.

Check resource usage

Monitor memory and CPU while Claude Code is running:

htop

If htop is not installed: sudo apt install -y htop. Watch the memory bar. Claude Code should hover around 200-400 MB. If it consistently hits 80%+ of total RAM, consider upgrading your VPS or closing other services.

Check disk usage:

df -h /

Claude Code itself uses minimal disk. Your project files and git history are the main consumers.

How much does it cost to run Claude Code on a VPS?

Claude Code runs on your existing Claude subscription with no additional fee. Your total monthly cost is the VPS plus the Claude plan.

VPS tier Monthly cost Claude plan Plan cost Total
2 vCPU, 4 GB RAM ~$5-10 Pro $20/mo $25-30/mo
4 vCPU, 8 GB RAM ~$10-20 Max $100/mo $110-120/mo
4 vCPU, 8 GB RAM ~$10-20 Console (API) Pay-per-token $10-20 + usage

The Pro plan includes limited Claude Code usage. The Max plan has higher rate limits suited for heavy use. Console (API) billing charges per token and suits teams that need fine-grained cost control.

For a persistent coding agent that runs around the clock, the 4 GB VPS + Pro plan at ~$25/month is the entry point.

Something went wrong?

Claude Code won't authenticate

Check your environment variable is set:

env | grep -E 'ANTHROPIC_API_KEY|CLAUDE_CODE_OAUTH_TOKEN'

If empty, re-source your profile: source ~/.bashrc. If the variable is set but authentication fails, verify the key is valid. OAuth tokens expire after one year. API keys can be revoked from the Console.

tmux session disappeared

tmux sessions are lost when the server reboots. After a reboot, start a new session:

tmux new -s claude

To check if the server rebooted recently:

uptime

Claude Code is slow or unresponsive

Check available memory:

free -h

If RAM is nearly full and swap is heavily used, Claude Code will slow down. Kill unused processes or upgrade your VPS. Also check your network latency to Anthropic's servers:

curl -o /dev/null -s -w '%{time_total}\n' https://api.anthropic.com

Anything under 1 second is normal. Higher values suggest network issues between your VPS and Anthropic.

Permission denied errors

If Claude Code cannot read or write files, check ownership:

ls -la ~/your-project

Files should be owned by your user (claude:claude if you followed this guide). Fix ownership:

sudo chown -R claude:claude ~/your-project

Check logs for more detail:

journalctl -u ssh -f

This shows SSH-related logs in real time. Useful for debugging connection issues.


Copyright 2026 Virtua.Cloud. All rights reserved. This content is original work by the Virtua.Cloud team. Reproduction, republication, or redistribution without written permission is prohibited.

Ready to try it yourself?

Deploy your own server in seconds. Linux, Windows, or FreeBSD.

See VPS Plans