A multi-user SSH proxy built with aiogram 3.x and asyncssh.
Deploy this bot on any internet-accessible server and let users SSH into their own remote machines through Telegram — even under strict network censorship.
User A ──► Telegram ──► [Bot Server] ──► SSH ──► Server A
User B ──► Telegram ──► [Bot Server] ──► SSH ──► Server B
- Each user gets a fully isolated session (keyed by
user_id). - A persistent shell channel (not
exec_command) keeps working directory, env variables, and aliases alive across messages. - All credentials live only in RAM and are wiped on disconnect or timeout.
tg-ssh-gateway/
├── bot.py # Entry point, aiogram dispatcher, startup/shutdown hooks
├── config.py # All settings from environment variables
├── session_manager.py # Per-user FSM, credential lifecycle, inactivity timer
├── ssh_client.py # asyncssh wrapper: persistent PTY shell, sentinel trick, ANSI strip
├── handlers.py # aiogram command + message handlers
├── utils.py # Telegram output chunking / file-attachment strategy
├── requirements.txt
└── .env.example
pip install -r requirements.txtcp .env.example .env
# Edit .env and set BOT_TOKEN to your token from @BotFatherpython bot.py| Command | Description |
|---|---|
/start |
Show welcome message and quick-start guide |
/connect |
Begin 4-step SSH credential collection and connect |
/disconnect |
Gracefully close SSH connection and wipe credentials |
/status |
Show current session state |
| (any text) | Execute as a shell command on the remote server |
All settings are read from environment variables (or a .env file):
| Variable | Default | Description |
|---|---|---|
BOT_TOKEN |
required | Telegram bot token from @BotFather |
CMD_TIMEOUT |
30 |
Seconds before a hanging command is killed with SIGINT |
SESSION_TIMEOUT |
1800 |
Seconds of inactivity before auto-disconnect (30 min) |
CHUNK_SIZE |
4000 |
Max chars per Telegram message |
MAX_OUTPUT_CHARS |
100000 |
Threshold above which output is sent as a .txt file |
- ✅ No local execution — zero calls to
subprocess,os.system,eval, orexec. - ✅ Credentials in RAM only — never written to disk, DB, or logs.
- ✅ Password auto-deletion — the password message is deleted from the Telegram chat immediately after reading.
- ✅ Per-user isolation — strict
user_idkeying; no session data is ever shared. - ✅ Inactivity auto-disconnect — idle sessions are wiped after
SESSION_TIMEOUTseconds. - ✅ Graceful shutdown — all SSH connections are closed and credentials wiped on
SIGINT/SIGTERM.
User types: cd /var/log && ls
Bot sends to remote shell stdin:
cd /var/log && ls
echo "§EXIT§:$?:§END§"
Bot reads stdout until it sees "§END§":
[command output lines]
§EXIT§:0:§END§
Bot parses exit code = 0, strips sentinel line, sends output to user.
Shell state (current directory, env vars, etc.) is preserved because we never close the channel.
| Output size | Telegram delivery |
|---|---|
| ≤ 4 000 chars | Single code-block message |
| 4 001 – 100 000 chars | Sequential chunked messages |
| > 100 000 chars | output.txt file attachment |
- Run with
systemdorscreen/tmuxto keep the bot alive. - The bot uses long-polling (no webhook setup needed).
- No external database required — all state is in RAM.
- To run as a systemd service, create
/etc/systemd/system/tg-ssh-bot.service:
[Unit]
Description=Telegram SSH Gateway Bot
After=network.target
[Service]
WorkingDirectory=/opt/tg-ssh-gateway
ExecStart=/usr/bin/python3 bot.py
EnvironmentFile=/opt/tg-ssh-gateway/.env
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target