Install
openclaw skills install @ctz168/sshtunnelExpose local SSH servers to the public internet via aitun TCP tunnel with SSH-over-TLS routing. Each subdomain gets its own SSH endpoint on port 22 with perfect isolation via SNI. Perfect for AI agents that need to provide remote SSH access behind NAT/firewall.
openclaw skills install @ctz168/sshtunnelUse this skill when:
Do NOT use this skill when:
curl -fsSL https://aitun.cc/install.sh | bash
Windows (PowerShell):
irm https://aitun.cc/install.ps1 | iex
Or verify it is already installed:
which aitun
Verify the local SSH daemon is running and accessible:
# Check if sshd is running
ps aux | grep sshd
# Or check if port 22 is listening
ss -tlnp | grep :22
# Test local SSH connection
ssh localhost echo "SSH OK"
If sshd is not running, install and start it:
# Ubuntu/Debian
sudo apt install openssh-server -y
sudo systemctl start sshd
# CentOS/RHEL
sudo yum install openssh-server -y
sudo systemctl start sshd
# macOS (usually pre-installed)
sudo systemsetup -setremotelogin on
SSH uses TCP port 22. Use aitun's --tcp-ports flag to forward this port. TCP forwarding requires an auth token (register at https://aitun.cc):
aitun -k YOUR_TOKEN --tcp-ports 22 &
AITUN_PID=$!
sleep 3
Or with HTTP tunnel alongside SSH:
aitun -k YOUR_TOKEN -p 8080 --tcp-ports 22 &
AITUN_PID=$!
sleep 3
The output will show:
[TCP] ssh -> localhost:22 (subdomain: yourname.t.aitun.cc:22)
On the remote client machine (the one connecting TO your SSH server), add this to ~/.ssh/config:
Host *.t.aitun.cc
ProxyCommand aitun ssh-proxy %h %p
This tells SSH to route connections through aitun ssh-proxy, which wraps SSH in TLS with the correct SNI for subdomain routing.
From any machine with aitun installed:
# Direct SSH — just like a normal server!
ssh user@yourname.t.aitun.cc
# With SSH key
ssh -i ~/.ssh/id_rsa user@yourname.t.aitun.cc
# With verbose output for debugging
ssh -v user@yourname.t.aitun.cc
When done, stop the tunnel:
kill $AITUN_PID 2>/dev/null
When executing multiple commands over an aitun SSH tunnel, you MUST reuse the SSH connection. Creating a new SSH connection for every command is extremely slow because:
Add to ~/.ssh/config:
Host *.t.aitun.cc
ProxyCommand aitun ssh-proxy %h %p
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
Then create the socket directory:
mkdir -p ~/.ssh/sockets
The first ssh command creates the master connection. All subsequent commands reuse it automatically — no additional handshake needed. ControlPersist 600 keeps the connection alive for 10 minutes after the last session closes.
When using Python (paramiko) to SSH over the aitun TLS tunnel, keep the Transport object alive and open new Channel sessions on it:
import paramiko, ssl, socket
def create_ssh_transport(host, port, sni, username, key_path):
"""Create a persistent SSH-over-TLS transport (do this ONCE)."""
key = paramiko.RSAKey.from_private_key_file(key_path)
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
raw_sock = socket.create_connection((host, port), timeout=10)
tls_sock = context.wrap_socket(raw_sock, server_hostname=sni)
transport = paramiko.Transport(tls_sock)
transport.set_keepalive(30)
transport.connect(username=username, pkey=key)
return transport
def run_command(transport, cmd, timeout=30):
"""Execute a command on an existing transport (REUSE the transport!)."""
session = transport.open_session()
session.settimeout(timeout)
session.exec_command(cmd)
stdout, stderr = b'', b''
while True:
if session.recv_ready():
stdout += session.recv(65536)
if session.recv_stderr_ready():
stderr += session.recv_stderr(65536)
if session.exit_status_ready():
while session.recv_ready():
stdout += session.recv(65536)
while session.recv_stderr_ready():
stderr += session.recv_stderr(65536)
break
exit_code = session.recv_exit_status()
session.close()
return exit_code, stdout.decode('utf-8', errors='replace'), stderr.decode('utf-8', errors='replace')
# Usage: create transport ONCE, run MANY commands
transport = create_ssh_transport(
host='43.160.208.156', port=22,
sni='yourname.t.aitun.cc',
username='mojo',
key_path='/path/to/ssh_key'
)
# First command (~1-2s, includes handshake)
ec, out, err = run_command(transport, 'echo hello')
# Subsequent commands (~200ms, reuses connection!)
ec, out, err = run_command(transport, 'uname -a')
ec, out, err = run_command(transport, 'df -h')
# When completely done:
transport.close()
| Approach | Per-command latency | Notes |
|---|---|---|
| New SSH connection each time | 1.5-5 seconds | Wasteful, slow |
| Reused SSH connection | ~200ms | Recommended |
When reusing SSH connections over the tunnel:
aitun v4.7.0 uses SSH-over-TLS for perfect multi-tenant SSH on shared ports:
ssh user@acer.t.aitun.cc
↓
ProxyCommand: aitun ssh-proxy wraps SSH in TLS (SNI=acer.t.aitun.cc)
↓
Server terminates TLS, sees SNI → routes to "acer" tunnel
↓
Decrypted SSH stream → tunnel client → localhost:22
Why TLS? SSH is a plaintext protocol that doesn't send hostname information. Without TLS, there's no way to tell which subdomain an SSH connection is targeting. By wrapping SSH in TLS, we get SNI (Server Name Indication) which tells the server exactly which subdomain to route to.
Result: Every subdomain can have its own SSH on port 22 — no conflicts, no ambiguity, no --tcp-default needed.
aitun -k YOUR_TOKEN --tcp-ports 22,3306 &
AITUN_PID=$!
sleep 3
# If SSH is running in a Docker container on a non-standard port
aitun -k YOUR_TOKEN --tcp-ports 2222 &
# Or with HTTP alongside:
# aitun -k YOUR_TOKEN -p 8080 --tcp-ports 2222 &
Add to ~/.ssh/config on the remote client:
Host *.t.aitun.cc
ProxyCommand aitun ssh-proxy %h %p
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
Host my-remote-dev
HostName yourname.t.aitun.cc
User username
IdentityFile ~/.ssh/id_rsa
Then simply:
ssh my-remote-dev
If you don't want to modify ssh config:
ssh -o "ProxyCommand=aitun ssh-proxy %h %p" user@yourname.t.aitun.cc
The aitun command (installed via curl -fsSL https://aitun.cc/install.sh | bash) accepts these flags:
| Flag | Description |
|---|---|
-p PORT | Local HTTP service port (optional; omit for TCP-only mode with --tcp-ports) |
-k TOKEN | Auth token for registered subdomain (required for TCP forwarding) |
--host HOST | Local service address (default: localhost) |
--tcp-ports PORTS | TCP forwarding ports, comma-separated (e.g., 22,3306; requires -k). Use without -p for TCP-only mode |
--p2p | Enable P2P direct connection (default: enabled) |
--no-p2p | Disable P2P, force server relay mode |
--daemon | Run as background daemon |
--stop | Stop running daemon |
Subcommand:
| Command | Description |
|---|---|
aitun ssh-proxy <host> [port] | SSH ProxyCommand — wraps SSH in TLS for SNI routing |
-k token — free tunnels do not support TCPProxyCommand aitun ssh-proxy %h %p to your ~/.ssh/config--daemon for persistent background operation