Loading...

How to Secure and Install Claude Code via SSH

Read this before you start. Claude Code can run shell commands, write files, and modify your Git setup on its own — that's the point of it. But it means running it on a server, especially one exposed to messaging platforms, isn't something to leave loosely configured. Before you connect any external webhook to it, lock down permissions, put a real reverse proxy in front of it, and validate every incoming request.
Note: Anthropic now offers a built-in Channels feature (research preview) that connects Claude Code sessions directly to Telegram, Discord, and iMessage through official plugins, with sender pairing and allowlisting handled for you — no custom webhook bridge required. Teams that only need Slack can also use native Claude in Slack (@Claude mentions), a separate and simpler integration. The custom Express bridge in this guide is still useful for platforms without an official channel yet, or for teams that need full control over the request path.

Claude Code is a command-line coding agent that can read your files, run commands, and make changes on its own. That's powerful on a laptop where you're watching every step — it's a different story on a server that's also taking requests from Slack, Telegram, or Discord. Before you wire messaging platforms into a live agent, you need real isolation: a locked-down user account, a proxy that terminates TLS, and validation on every incoming request. This guide walks through that setup end to end.

Operating System Compatibility & Requirements

Claude Code ships as a native binary for macOS, Windows, and Linux. It runs best on Unix-like systems since it's built around interacting directly with the shell, the filesystem, and dev tools like compilers and Git. One thing worth clearing up: you don't need Node.js installed to run Claude Code. That's only a requirement if you install it through npm instead of the native installer — more on that in Step 4.

System Requirements

Here's what Anthropic actually documents as requirements for running Claude Code:

Resource Requirement
Operating System Ubuntu 20.04+, Debian 10+, or Alpine Linux 3.19+ (this guide uses Ubuntu LTS)
Processor x64 or ARM64
Memory (RAM) 4 GB or more
Network Active internet connection to reach Anthropic's API
Node.js (npm install only) Node.js 22 or later — not required if using the native installer

There's no published minimum for disk space or CPU cores — size your VPS based on what your workload actually needs.

Prerequisites

Before you get started, make sure you have:

  1. A Self-Managed VPS or VDS Instance running an active Ubuntu LTS configuration.
  2. An Active Anthropic API Key configured with sufficient billing limits.
  3. A Domain Name with an A record mapped directly to your VPS IP to provision automated HTTPS.

Step 1: Connect to Your VPS via SSH

Start by connecting to your server so you can run the setup commands directly.

  1. Open a terminal on your local machine.
  2. Connect over SSH, swapping in your VPS's actual IP address:
ssh root@your_server_ip
  1. Log in with your credentials. You should land at a root prompt:
root@vps-server-node:~#

Step 2: Harden the Linux Host

Security starts at the OS level, before Claude Code ever enters the picture. A few basic steps here go a long way toward keeping automated scans and brute-force attempts out.

Install a Text Editor

Minimal cloud server images often skip basic tools like a text editor. Update your package list and install nano:

apt update && apt install nano -y

Example Output:

Reading package lists... Done Building dependency tree... Done The following NEW packages will be installed: nano 0 upgraded, 1 newly installed, 0 to remove.

Create a Non-Root User

Letting an automated agent run commands as root is asking for trouble. Create a dedicated, unprivileged user to run Claude Code and the bridge process instead:

adduser claudeadmin

Add it to the sudo group so it can still run administrative commands when needed:

usermod -aG sudo claudeadmin

Switch to Key-Based SSH Authentication

Open the SSH daemon config file:

sudo nano /etc/ssh/sshd_config

Update these lines to turn off password logins and root login entirely, so only SSH keys work:

PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes

Save the file (Ctrl+O, then Enter, then Ctrl+X) and restart SSH so the changes take effect:

sudo systemctl restart ssh

Set Up the Firewall

Claude Code reaches out to Anthropic's API over outbound connections, and your bridge will need inbound web traffic for the webhook. Everything else should stay closed. Here's a basic UFW setup for that:

sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable

Install Fail2Ban

Fail2Ban watches for repeated failed login attempts and bans the source automatically. Install it:

sudo apt install fail2ban -y

Enable it so it starts on boot:

sudo systemctl enable --now fail2ban

From here on, switch to the claudeadmin user you created and run the rest of the steps from there:

su - claudeadmin

Example Output:

claudeadmin@vps-server-node:~$

Step 3: Install Node.js (Optional)

Skip this step if you're using the native installer in Step 4 — it doesn't need Node.js at all. Only come back to this if you decide to install Claude Code via npm instead, or if other tools in your workspace depend on Node. Here's how to get a current version from NodeSource:

sudo apt update && sudo apt install -y curl curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - sudo apt install -y nodejs build-essential git

Check that both installed correctly:

node -v && npm -v

Step 4: Install Claude Code

The native installer is the simplest route — it doesn't touch Node.js at all:

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

Check that it installed correctly:

claude --version

Alternative: Install via npm

If you'd rather manage Claude Code through npm alongside your other global packages, that works too. Just skip sudo here — running npm installs as root tends to cause permission headaches down the line:

npm install -g @anthropic-ai/claude-code

This route needs Node.js 22 or newer. If you set up Node in Step 3, double-check the version first:

node -v

Step 5: Set Up Your Workspace

Decide how much isolation you need before you start wiring up messaging platforms.

Option A: A Plain Git Workspace

For most setups, a simple Git-tracked directory works fine. Claude Code uses Git to understand project boundaries:

mkdir ~/workspace && cd ~/workspace git init git config --global user.name "Claude Agent" git config --global user.email "[email protected]"

Option B: A Sandboxed Container Workspace

If your webhook is going to accept requests from people you don't fully trust, run Claude Code inside a container instead of directly on the host:

sudo apt install docker.io -y sudo usermod -aG docker claudeadmin newgrp docker

Step 6: Add Your API Key

Claude Code needs your Anthropic API key available in the environment. Open your shell config:

nano ~/.bashrc

Add this line to the bottom of the file, with your actual key:

export ANTHROPIC_API_KEY="sk-ant-api03-YOUR_KEY_HERE"

Then reload your shell so the change takes effect:

source ~/.bashrc

Step 7: Build the Webhook Bridge

Claude Code is built as an interactive CLI tool — it doesn't listen for web requests on its own. To connect it to Slack, Telegram, or Discord, you need a small server sitting in front of it that validates incoming webhooks, pulls out the message, runs Claude Code in non-interactive mode (-p), and sends the result back.

  1. Create a directory for the bridge and install dependencies:
mkdir ~/claude-bridge && cd ~/claude-bridge npm init -y npm install express body-parser dotenv
  1. Create the main server file:
nano index.js
  1. Paste in the following:
const express = require('express'); const { execFile } = require('child_process'); const crypto = require('crypto'); require('dotenv').config(); const app = express(); app.use(express.json()); const PORT = process.env.PORT || 3000; const MESSENGER_SECRET = process.env.MESSENGER_SECRET; // Check that the request actually came from the messaging platform, // not just anyone who found the URL. function verifySignature(req, res, next) { const signature = req.headers['x-messenger-signature']; if (!signature) return res.status(401).send('Missing signature header'); const hmac = crypto.createHmac('sha256', MESSENGER_SECRET); const digest = hmac.update(JSON.stringify(req.body)).digest('hex'); if (signature !== digest) { return res.status(403).send('Signature mismatch'); } next(); } app.post('/webhook/claude', verifySignature, (req, res) => { const userPrompt = req.body.command; if (!userPrompt) { return res.status(400).send({ error: 'No command provided' }); } // execFile passes the prompt as an argument, not through a shell string, // so there's no injection risk from special characters in the input. // -p runs Claude Code non-interactively; --allowedTools pre-approves // the tools this workflow needs so a request never hangs on a prompt. execFile( 'claude', ['-p', userPrompt, '--allowedTools', 'Read,Edit,Bash'], { cwd: '/home/claudeadmin/workspace' }, (error, stdout, stderr) => { if (error) { console.error(`Claude Code error: ${error}`); return res.status(500).send({ output: 'Something went wrong running the agent.' }); } res.status(200).send({ response: stdout || stderr }); } ); }); app.listen(PORT, () => { console.log(`Claude Code bridge listening on port ${PORT}`); });
  1. Create a .env file:
nano .env
  1. Add your port and a long, random secret — this is what verifies incoming webhooks:
PORT=3000 MESSENGER_SECRET=SuperSecureRandomPassphraseThatValidatesIncomingHooks
  1. Start the bridge. For anything beyond quick testing, run it under a process manager like pm2 or a systemd service so it survives reboots and crashes — a bare background process won't:
node index.js &

Step 8: Put Caddy in Front of Your Bridge

You don't want to expose port 3000 directly to the internet. Caddy handles TLS certificates automatically and proxies requests to your bridge over HTTPS.

  1. Install Caddy:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list sudo apt update && sudo apt install caddy -y
  1. Edit Caddyfile to map your domain address:
sudo nano /etc/caddy/Caddyfile

Insert your configuration block (replace domain accordingly):

claude.yourdomain.com { reverse_proxy 127.0.0.1:3000 }
  1. Restart Caddy. It'll automatically request a Let's Encrypt certificate for your domain:
sudo systemctl restart caddy

Step 9: Connect Your Messaging Platform

Your webhook is now live at https://claude.yourdomain.com/webhook/claude. Pick the platform you want to connect:

Check the official option first. Anthropic's Channels feature (currently in research preview) already covers Telegram, Discord, and iMessage through official plugins, with sender pairing and allowlisting handled for you out of the box. If you're on Slack, native Claude in Slack (@Claude mentions) may get you there without building anything. The steps below are for platforms without an official channel yet, or if you need more control over the request path than those options give you.

Scenario A: Slack Workspace Commands

  1. Go to Slack API App Management Dashboard, create a new App.
  2. Select Slash Commands and create a new command.
  3. Configure the trigger (e.g. /claude) and set Request URL to your public proxy endpoint.
  4. Copy your App's Signing Secret and update MESSENGER_SECRET in your bridge's .env.
  5. Refer to Slack Bolt Integration Docs.

Scenario B: Telegram Private Automation Bots

  1. Use @BotFather in Telegram and send /newbot.
  2. Follow prompts to receive your Bot Token.
  3. Bind webhook with curl:
curl -X POST "https://api.telegram.org/botYOUR_TELEGRAM_BOT_TOKEN/setWebhook?url=https://claude.yourdomain.com/webhook/claude"
  1. See Telegram Webhook Bot API Documentation for details.

Scenario C: Discord Application Interactions

  1. Access Discord Developer Portal, create an app.
  2. Enable Bot and message gateway permissions.
  3. Paste endpoint URL in Interactions Endpoint URL box.
  4. Refer to Discord Interactions Security Guide.

Summary

With key-based SSH, a non-root user running the agent, a signature-verified webhook bridge, and Caddy handling TLS in front of it, you've got a reasonably hardened setup for running Claude Code behind a messaging platform. Keep an eye on Anthropic's official Channels feature as it matures — it may eventually replace the custom bridge here for supported platforms.

Loading...