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:
ssh-keygen -t ed25519 -C "claude-server"
Press Enter three times to accept defaults and skip a passphrase. Then print the public key:
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):
ssh-keygen -t ed25519 -C "claude-server"
Press Enter three times. Then:
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
- Open Telegram, search for @BotFather
- Send
/newbot - Choose a display name (e.g. "My Claude Bot")
- Choose a username ending in
bot(e.g.myclaudehelper_bot) - 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
- Search for @userinfobot in Telegram
- Send any message
- 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:
ssh root@YOUR_SERVER_IP
Windows (PowerShell):
ssh root@YOUR_SERVER_IP
Which username? Hetzner and DigitalOcean use
rootfor the first login. Oracle Cloud usesubuntu(if you chose Ubuntu) oropc(if you chose Oracle Linux). Ifrootdoesn't work, tryubuntuoropc.
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.
useradd -m -s /bin/bash claudebot
Give it permission to run admin commands (like installing software) without needing a password:
echo "claudebot ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/claudebot
chmod 440 /etc/sudoers.d/claudebot
Why NOPASSWD? Without this, every
sudocommand 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:
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:
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:
sudo ufw allow OpenSSH
sudo ufw --force enable
Verify:
sudo ufw status
Should show Status: active with SSH allowed.
Step 3: Update the system
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
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -
sudo apt install -y nodejs
Verify:
node --version
Should print v22.x.x.
Step 5: Install Claude Code
Configure npm to use a user-writable directory (avoids permission issues):
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:
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:
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:
- Copy the entire URL from the terminal (it's long -- get all of it)
- Open that URL in a browser on your local computer (laptop/phone/anywhere)
- Sign in to your Claude account if prompted
- Claude.com will show a page titled "Authentication Code" with a long string underneath and a "Copy Code" button
- Copy that code
- Go back to the server terminal -- you'll see
Paste code here if prompted > - Paste the code and press Enter
- The terminal will show "Login successful. Press Enter to continue..."
- Press Enter, then type
/exitto leave Claude Code
Verify it works:
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
/loginflow -- 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.
- Go to console.anthropic.com
- Sign in → click API Keys → Create Key
- Copy the key (starts with
sk-ant-) -- it's only shown once
Then on the server:
claude config set api-key YOUR_ANTHROPIC_API_KEY
Verify:
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
sudo apt install -y python3 python3-venv python3-pip
Create the bot directory and a virtual environment:
mkdir -p ~/claude-telegram-bot
python3 -m venv ~/claude-telegram-bot/venv
source ~/claude-telegram-bot/venv/bin/activate
Install packages inside the venv:
pip install "python-telegram-bot==20.*" openai
Verify:
python -c "import telegram; print(telegram.__version__)"
Should print a version starting with 20..
Step 8: Create the inbox directory
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:
(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:
crontab -l
Step 10: Write the bot script
Claude will generate this file with your credentials already filled in. Run:
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:
wc -l ~/claude-telegram-bot/telegram_bot.py
Should print a number greater than 100.
Step 11: Create the systemd service
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:
sudo systemctl daemon-reload
sudo systemctl enable claude-telegram
sudo systemctl start claude-telegram
Step 12: Verify it's running
sudo systemctl status claude-telegram
Should show Active: active (running).
Check the live logs:
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:
sudo systemctl status claude-telegram
View recent logs:
journalctl -u claude-telegram --no-pager -n 50
Follow live logs:
journalctl -u claude-telegram -f
Restart after making changes:
sudo systemctl restart claude-telegram
Stop the bot:
sudo systemctl stop claude-telegram
Start it again:
sudo systemctl start claude-telegram
Updating the bot script
To change credentials, add MCP tools, or modify any settings:
- SSH into the server:
ssh claudebot@YOUR_SERVER_IP - Open the bot file:
nano ~/claude-telegram-bot/telegram_bot.py nanois 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.- Restart the service:
sudo systemctl restart claude-telegram - 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:
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
| Problem | Fix |
|---|---|
| Bot is silent after sending a message | Check logs: journalctl -u claude-telegram --no-pager -n 50 |
| "Credits have run out" message | This 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 testing | Run 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 restarting | Check 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 server | Verify the IP. If using SSH keys, confirm the correct key: ssh -i ~/.ssh/id_ed25519 claudebot@YOUR_IP |
| Voice transcription not working | Check OPENAI_API_KEY is set correctly in the script and account has credits at platform.openai.com |
| Bot stopped responding after days | Check 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 credentials | Edit 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