Guides
Telegram bot

Telegram bot — cloud server setup guide

Set up a Claude Code Telegram bot running 24/7 on a cheap cloud server (~$4–6/month).

What this sets up

A Python Telegram bot running on a cheap cloud server (~$4--6/month) that:

  • Forwards messages to Claude Code CLI and streams responses back in real time
  • Supports text, images, voice messages (transcribed via OpenAI Whisper), audio files, and documents
  • Persists conversation sessions across bot restarts
  • Reply context -- replying to a previous message includes the quoted text
  • MCP integrations -- optionally connects to Notion, Figma, and Slack
  • Runs 24/7 as a systemd service -- survives reboots, restarts automatically on failure
  • Only responds to you -- everyone else is silently ignored

Key difference from the local setup: the bot runs on a server in a data centre, not your computer. It's always on. You reach it from anywhere, even when your laptop is closed or off. The tradeoff: it can't see files on your local machine -- it only has access to the server's own filesystem.


Step 0: Collect everything before touching the server

Ask the user all of these before issuing any commands. Do not proceed until you have answers.


Question 1: Set up SSH keys (do this first)

You'll need an SSH key to securely connect to the server. Most server providers ask you to paste your public key during server creation, so generate it now -- before picking a provider.

Do this on your local computer (not the server).

Mac/Linux:

bash
ssh-keygen -t ed25519 -C "claude-server"

Press Enter three times to accept defaults and skip a passphrase. Then print the public key:

bash
cat ~/.ssh/id_ed25519.pub

Copy the entire output -- it starts with ssh-ed25519. You'll paste this into the server provider's "SSH key" field in the next step.

Windows (PowerShell):

powershell
ssh-keygen -t ed25519 -C "claude-server"

Press Enter three times. Then:

powershell
type $HOME\.ssh\id_ed25519.pub

Copy the output. You'll paste this into the server provider's "SSH key" field in the next step.


Question 2: Choose a server provider and create the server

Three good options. If you're not sure, pick Hetzner -- it's cheap, simple, and reliable.

Option A -- Hetzner (recommended -- best balance of price and simplicity)

  • €3.79/month ($4)
  • Go to hetzner.com, create an account
  • New Project → Add Server → Location: closest to you → Ubuntu 24.04 → CX22 (cheapest) → Paste your SSH key from Question 1 → Create
  • Note the public IP address shown after creation

Option B -- Oracle Cloud Free Tier (free, but more complex signup)

  • Genuinely free, no monthly bill
  • Go to cloud.oracle.com, create an account (requires a credit card for verification but is not charged)
  • Create a VM instance: Ubuntu 22.04 (not Oracle Linux), VM.Standard.E2.1.Micro shape (free tier eligible)
  • Note the public IP after creation
  • Heads up: Oracle's signup process is more confusing than other providers, and free tier instances can occasionally be reclaimed under heavy regional demand. It's truly free, but you get what you pay for in terms of support and simplicity.

Option C -- DigitalOcean

  • $6/month, slightly more expensive but very beginner-friendly documentation
  • Go to digitalocean.com → Create → Droplets → Ubuntu 24.04 → Basic → $6/month → Paste your SSH key from Question 1 → Create
  • Note the public IP address (called "ipv4")

Ask which option they want. Guide them through creating the server. Make sure they have the public IP address before proceeding.


Question 3: How do you want to authenticate Claude?

Two options -- ask the user which they prefer.

Option A -- OAuth login (recommended): Uses your existing Claude.ai Pro subscription. No separate API bill. Claude Code supports headless OAuth on servers -- you'll copy a URL to your local browser and paste a code back during Step 6. You'll sign in with your Claude.ai account at that point -- no credentials needed right now.

Option B -- API key: Create a key at console.anthropic.com. Billed separately from your Pro subscription (~$2--10/month for a personal bot). Good if you want server usage isolated on its own billing account, or don't have a Pro subscription.

Note which they choose. No credentials needed now -- collected at Step 6.


Question 4: Get your Telegram bot token

  1. Open Telegram, search for @BotFather
  2. Send /newbot
  3. Choose a display name (e.g. "My Claude Bot")
  4. Choose a username ending in bot (e.g. myclaudehelper_bot)
  5. BotFather replies with a token like 1234567890:ABCdefGHIjklMNO...

Keep this secret. Ask the user to paste their token. Save it.


Question 5: Get your Telegram user ID

  1. Search for @userinfobot in Telegram
  2. Send any message
  3. It replies with a number like 123456789

Ask the user to paste their user ID. Save it.


Question 6: Voice transcription? (Optional)

Voice uses OpenAI's Whisper API. Completely optional -- the bot works without it.

  • If yes: go to platform.openai.com, create an API key, paste it here. Save it.
  • If no: skip. The bot will send a clear message if you try to send a voice note.

Connecting to the server

Once the server is created and you have its IP address:

Mac/Linux:

bash
ssh root@YOUR_SERVER_IP

Windows (PowerShell):

powershell
ssh root@YOUR_SERVER_IP

Which username? Hetzner and DigitalOcean use root for the first login. Oracle Cloud uses ubuntu (if you chose Ubuntu) or opc (if you chose Oracle Linux). If root doesn't work, try ubuntu or opc.

Type yes if asked to confirm the fingerprint. You should land at a terminal prompt on the server.


Server Setup

Run these steps in order. Each one must succeed before moving on.


Step 1: Create a dedicated bot user

Running everything as root means Claude has unrestricted access to the entire server -- one wrong command could break everything. Instead, create a dedicated user that has just enough permissions to run the bot and install packages.

bash
useradd -m -s /bin/bash claudebot

Give it permission to run admin commands (like installing software) without needing a password:

bash
echo "claudebot ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/claudebot
chmod 440 /etc/sudoers.d/claudebot

Why NOPASSWD? Without this, every sudo command would prompt for a password -- and since Claude runs commands automatically, it would get stuck. NOPASSWD lets the bot user install packages and manage services smoothly. This is standard practice for automated service accounts.

Copy your SSH key so you can log in directly as this user in the future:

bash
mkdir -p /home/claudebot/.ssh
cp ~/.ssh/authorized_keys /home/claudebot/.ssh/
chown -R claudebot:claudebot /home/claudebot/.ssh
chmod 700 /home/claudebot/.ssh
chmod 600 /home/claudebot/.ssh/authorized_keys

Switch to the new user for all remaining steps:

bash
su - claudebot

Your prompt should change to show claudebot@.... From here on, everything runs as this user.


Step 2: Harden the server

Set up a basic firewall -- allow SSH, block everything else:

bash
sudo ufw allow OpenSSH
sudo ufw --force enable

Verify:

bash
sudo ufw status

Should show Status: active with SSH allowed.


Step 3: Update the system

bash
sudo DEBIAN_FRONTEND=noninteractive apt update
sudo DEBIAN_FRONTEND=noninteractive apt upgrade -y

DEBIAN_FRONTEND=noninteractive prevents the upgrade from hanging on prompts about kernel updates or service restarts.


Step 4: Install Node.js

bash
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -
sudo apt install -y nodejs

Verify:

bash
node --version

Should print v22.x.x.


Step 5: Install Claude Code

Configure npm to use a user-writable directory (avoids permission issues):

bash
mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
npm install -g @anthropic-ai/claude-code

Verify:

bash
claude --version

Step 6: Authenticate Claude Code

Two ways to do this. Choose one.


Option A -- OAuth login (uses your Claude Pro subscription, no separate API bill)

This is the recommended approach if you have a Claude Pro subscription. Claude Code's /login command works fine on headless servers -- it just requires one manual step instead of a browser opening automatically.

Start Claude Code:

bash
claude

Inside the session, type:

/login

The browser won't open -- that's expected on a server. Instead you'll see a message like:

Browser didn't open? Use the url below to sign in (c to copy)

https://claude.com/cai/oauth/authorize?code=true&client_id=...

What to do:

  1. Copy the entire URL from the terminal (it's long -- get all of it)
  2. Open that URL in a browser on your local computer (laptop/phone/anywhere)
  3. Sign in to your Claude account if prompted
  4. Claude.com will show a page titled "Authentication Code" with a long string underneath and a "Copy Code" button
  5. Copy that code
  6. Go back to the server terminal -- you'll see Paste code here if prompted >
  7. Paste the code and press Enter
  8. The terminal will show "Login successful. Press Enter to continue..."
  9. Press Enter, then type /exit to leave Claude Code

Verify it works:

bash
claude -p "say hello"

Claude should respond. If it does, you're authenticated and using your Pro subscription with no separate API costs.

How long does this last? OAuth sessions last around 90 days. If the bot stops responding and the logs show auth errors, SSH in and repeat this /login flow -- it takes two minutes.


Option B -- API key (alternative, billed separately from your Pro subscription)

Use this if you don't have Claude Pro, or if you prefer keeping server usage isolated on its own billing account.

  1. Go to console.anthropic.com
  2. Sign in → click API KeysCreate Key
  3. Copy the key (starts with sk-ant-) -- it's only shown once

Then on the server:

bash
claude config set api-key YOUR_ANTHROPIC_API_KEY

Verify:

bash
claude -p "say hello"

Note: API key usage is billed separately from your Claude Pro subscription, typically $2--10/month for a personal bot. You can set a spend limit at console.anthropic.com under Billing → Limits.


Step 7: Install Python and set up a virtual environment

bash
sudo apt install -y python3 python3-venv python3-pip

Create the bot directory and a virtual environment:

bash
mkdir -p ~/claude-telegram-bot
python3 -m venv ~/claude-telegram-bot/venv
source ~/claude-telegram-bot/venv/bin/activate

Install packages inside the venv:

bash
pip install "python-telegram-bot==20.*" openai

Verify:

bash
python -c "import telegram; print(telegram.__version__)"

Should print a version starting with 20..


Step 8: Create the inbox directory

bash
mkdir -p ~/.claude/telegram_inbox

Step 9: Set up automatic inbox cleanup

Files sent to the bot (images, voice, documents) accumulate in the inbox. Without cleanup, the disk fills up over time. Add a daily cron job to delete files older than 7 days:

bash
(crontab -l 2>/dev/null; echo "0 3 * * * find /home/claudebot/.claude/telegram_inbox -type f -mtime +7 -delete") | crontab -

Verify the cron was added:

bash
crontab -l

Step 10: Write the bot script

Claude will generate this file with your credentials already filled in. Run:

bash
cat > ~/claude-telegram-bot/telegram_bot.py << 'ENDOFSCRIPT'
PASTE_COMPLETE_SCRIPT_HERE
ENDOFSCRIPT

(Claude: replace PASTE_COMPLETE_SCRIPT_HERE with the full bot script from the "Bot Script" section below, with the user's actual credentials substituted.)

Verify the file was written:

bash
wc -l ~/claude-telegram-bot/telegram_bot.py

Should print a number greater than 100.


Step 11: Create the systemd service

bash
sudo tee /etc/systemd/system/claude-telegram.service << 'EOF'
[Unit]
Description=Claude Telegram Bot
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=claudebot
Group=claudebot
WorkingDirectory=/home/claudebot/claude-telegram-bot
ExecStart=/home/claudebot/claude-telegram-bot/venv/bin/python3 /home/claudebot/claude-telegram-bot/telegram_bot.py
Environment=HOME=/home/claudebot
Environment=PATH=/home/claudebot/.npm-global/bin:/usr/local/bin:/usr/bin:/bin
Restart=on-failure
RestartSec=10
StartLimitIntervalSec=60
StartLimitBurst=5

[Install]
WantedBy=multi-user.target
EOF

Enable and start it:

bash
sudo systemctl daemon-reload
sudo systemctl enable claude-telegram
sudo systemctl start claude-telegram

Step 12: Verify it's running

bash
sudo systemctl status claude-telegram

Should show Active: active (running).

Check the live logs:

bash
journalctl -u claude-telegram -f

You should see "Bot started. Polling for messages..." Press Ctrl+C to exit the log view.

Test it now: Open Telegram and send your bot the message hello. You should see "Thinking..." appear within a few seconds, followed by a response from Claude. If that happens -- you're done. The bot is live and will stay running 24/7.


Day-to-day use

The bot runs in the background permanently. You don't need to do anything day to day -- just open Telegram and message it.

About credits: Claude Pro includes a generous daily usage quota. Under heavy use, you may hit the limit and see a message like "credits have run out." This resets automatically -- wait a few hours or until the next day, and it'll work again. This is normal, not a bug.

OAuth sessions last about 90 days. If the bot stops responding and logs show an auth error, SSH in and re-run the /login flow from Step 6. Takes two minutes.

Useful commands

Check if the bot is running:

bash
sudo systemctl status claude-telegram

View recent logs:

bash
journalctl -u claude-telegram --no-pager -n 50

Follow live logs:

bash
journalctl -u claude-telegram -f

Restart after making changes:

bash
sudo systemctl restart claude-telegram

Stop the bot:

bash
sudo systemctl stop claude-telegram

Start it again:

bash
sudo systemctl start claude-telegram

Updating the bot script

To change credentials, add MCP tools, or modify any settings:

  1. SSH into the server: ssh claudebot@YOUR_SERVER_IP
  2. Open the bot file: nano ~/claude-telegram-bot/telegram_bot.py
  3. nano is a terminal text editor -- it looks different from what you're used to. Just make your changes, then press Ctrl+O to save, Enter to confirm the filename, and Ctrl+X to exit.
  4. Restart the service: sudo systemctl restart claude-telegram
  5. Confirm it's running: sudo systemctl status claude-telegram

Optional: Adding MCP integrations

To let Claude access Notion, Figma, or Slack, add their tool patterns to ALLOWED_TOOLS in the script:

python
ALLOWED_TOOLS = ",".join([
    "Edit", "Write", "Read", "Bash", "Glob", "Grep",
    "Agent", "WebFetch", "WebSearch",
    "mcp__claude_ai_Notion*",
    "mcp__claude_ai_Figma*",
    "mcp__claude_ai_Slack*",
])

MCP servers are configured in your Claude Code settings (~/.claude/settings.json). To manage them, SSH in and run claude, then type /mcp to see available integrations. After changing the script, run sudo systemctl restart claude-telegram.


Troubleshooting

ProblemFix
Bot is silent after sending a messageCheck logs: journalctl -u claude-telegram --no-pager -n 50
"Credits have run out" messageThis is normal -- Claude Pro has a daily quota. Wait a few hours and try again. If using an API key, check your balance at console.anthropic.com
claude: command not found when testingRun source ~/.bashrc to reload the PATH, then try again
claude -p "say hello" returns an auth error (OAuth)Session expired (~90 days) -- SSH in, run claude then /login again, repeat the URL + code flow
claude -p "say hello" returns an auth error (API key)Key is wrong or missing -- re-run claude config set api-key YOUR_KEY
pip install fails with "externally-managed-environment"Make sure you activated the venv: source ~/claude-telegram-bot/venv/bin/activate
Service won't start / keeps restartingCheck logs for the actual error. If it hit the crash-loop limit: sudo systemctl reset-failed claude-telegram && sudo systemctl start claude-telegram
"Another instance is already running"sudo systemctl stop claude-telegram && rm ~/claude-telegram.lock && sudo systemctl start claude-telegram
Can't SSH into serverVerify the IP. If using SSH keys, confirm the correct key: ssh -i ~/.ssh/id_ed25519 claudebot@YOUR_IP
Voice transcription not workingCheck OPENAI_API_KEY is set correctly in the script and account has credits at platform.openai.com
Bot stopped responding after daysCheck disk space: df -h. If inbox is full, the cleanup cron should handle it, but manually clear with: find ~/.claude/telegram_inbox -type f -mtime +1 -delete
Want to rotate credentialsEdit the script (nano ~/claude-telegram-bot/telegram_bot.py), update the values, restart: sudo systemctl restart claude-telegram

What's next?

Your bot works. It answers questions, reads files, writes code, and handles voice messages. But right now it forgets everything between sessions and doesn't connect to any of your other tools.

A follow-up guide (coming soon) covers how to make it truly useful:

  • CLAUDE.md -- teach Claude who you are, how you work, and what to remember
  • Persistent memory -- Claude remembers context across conversations
  • MCP integrations -- connect Notion, Slack, Google Calendar, and more
  • Custom commands -- add your own workflows (content drafting, daily briefs, etc.)
  • Backups -- so you don't lose everything if the server dies