Web-based terminal: run any shell command in a PTY, accessed through the browser over WebSocket.
Project description
tunnelterm
A web-based terminal: spawn any shell command in a real PTY and interact with it from the browser over WebSocket.
- Password-protected (per-IP rate limited)
- Single-active-session per token
- Origin allow-list for the WebSocket handshake
- Token TTL + LRU eviction
- Themes, fonts, search, web-links, WebGL renderer
- Persistent preferences in the browser
Install
uv tool install tunnelterm
# or
uv pip install tunnelterm
Run
--command is required. Examples:
TUNNELTERM_PASSWORD=hunter2 tunnelterm --command bash
TUNNELTERM_PASSWORD=hunter2 tunnelterm --command "zsh -l"
TUNNELTERM_PASSWORD=hunter2 tunnelterm --command htop --port 4242
Open http://127.0.0.1:4200 and log in with the password.
CLI options
| Option | Description | Default |
|---|---|---|
--command |
Command to run in the PTY. Required. | — |
--host |
Host to bind to | 127.0.0.1 |
--port |
Port to bind to | 4200 |
--password-env |
Env var name to read the password from | TUNNELTERM_PASSWORD |
--config |
Path to TOML config file | ~/.config/tunnelterm/config.toml |
--allowed-origin |
Allowed Origin header (repeat to allow multiple) |
accept all |
--log-level |
Log level: DEBUG, INFO, WARNING, ERROR | INFO |
--version |
Print version and exit | — |
Environment variables
| Variable | Description |
|---|---|
TUNNELTERM_PASSWORD |
Password for authentication (REQUIRED) |
TUNNELTERM_HOST |
Bind host |
TUNNELTERM_PORT |
Bind port |
TUNNELTERM_COMMAND |
PTY command (if --command not given) |
TUNNELTERM_ALLOWED_ORIGINS |
Comma-separated Origin allow-list |
LOG_LEVEL |
Log level fallback |
Config file
~/.config/tunnelterm/config.toml:
password = "hunter2"
command = "bash"
host = "127.0.0.1"
port = 4200
allowed_origins = ["https://terminal.example.com"]
chmod 600 is recommended — tunnelterm logs a warning if the file is group-
or world-readable.
Behind nginx with HTTPS
Plaintext ws:// is fine for 127.0.0.1 only. For any other deployment,
terminate TLS at a reverse proxy. Minimal nginx example:
server {
listen 443 ssl http2;
server_name terminal.example.com;
ssl_certificate /etc/letsencrypt/live/terminal.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/terminal.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:4200;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 1h;
}
}
Then run tunnelterm with --allowed-origin https://terminal.example.com.
Systemd
sudo cp systemd/tunnelterm.service /etc/systemd/system/
# Edit User=, WorkingDirectory=, and ExecStart=.
sudo mkdir -p /etc/tunnelterm
sudo cp systemd/env.example /etc/tunnelterm/env
sudo chmod 600 /etc/tunnelterm/env
sudo $EDITOR /etc/tunnelterm/env # set TUNNELTERM_PASSWORD
sudo systemctl daemon-reload
sudo systemctl enable --now tunnelterm
sudo journalctl -u tunnelterm -f
Security notes
- Tokens are sent via
Sec-WebSocket-Protocol(not the URL), so reverse-proxy access logs do not capture them. - Each token may only be active in one WebSocket connection at a time.
- Tokens expire after 24h by default; LRU-evicted at 64 outstanding.
- Five failed auth attempts in 15min lock the source IP out for 5min.
- Origin allow-list prevents cross-site WebSocket hijacking.
- All HTTP responses ship CSP, X-Content-Type-Options, X-Frame-Options.
- xterm assets are vendored locally; no third-party CDN trust.
Development
uv sync
uv run pytest
uv run ruff check
uv run pyright
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tunnelterm-0.1.2.tar.gz.
File metadata
- Download URL: tunnelterm-0.1.2.tar.gz
- Upload date:
- Size: 7.4 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
09244e3087eed85aa2cb48350d8cc34f7d5b513d0105face2fada263cc4b7b17
|
|
| MD5 |
c49b76079b1601d06d51e94601604105
|
|
| BLAKE2b-256 |
8e6215a65d3cf949d3435fab7305e2d9e9282701efd859774ed407e30e730296
|
Provenance
The following attestation bundles were made for tunnelterm-0.1.2.tar.gz:
Publisher:
release.yml on nimitbhardwaj/tunnelterm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tunnelterm-0.1.2.tar.gz -
Subject digest:
09244e3087eed85aa2cb48350d8cc34f7d5b513d0105face2fada263cc4b7b17 - Sigstore transparency entry: 1655448778
- Sigstore integration time:
-
Permalink:
nimitbhardwaj/tunnelterm@c03e5b5608317dff7425cfc7b793081c5d843979 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/nimitbhardwaj
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c03e5b5608317dff7425cfc7b793081c5d843979 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tunnelterm-0.1.2-py3-none-any.whl.
File metadata
- Download URL: tunnelterm-0.1.2-py3-none-any.whl
- Upload date:
- Size: 7.4 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1aff5ad0efc39493d6e48dfad2ce53a9283afb3e4b3c426e087b353b91871059
|
|
| MD5 |
2bfe214e620f29fbcd669a4544e0e088
|
|
| BLAKE2b-256 |
7937a4ad6afcc8491f97cd6bb26de5ed584a353c65d82ec285c3b2b29c2c6dbc
|
Provenance
The following attestation bundles were made for tunnelterm-0.1.2-py3-none-any.whl:
Publisher:
release.yml on nimitbhardwaj/tunnelterm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tunnelterm-0.1.2-py3-none-any.whl -
Subject digest:
1aff5ad0efc39493d6e48dfad2ce53a9283afb3e4b3c426e087b353b91871059 - Sigstore transparency entry: 1655448946
- Sigstore integration time:
-
Permalink:
nimitbhardwaj/tunnelterm@c03e5b5608317dff7425cfc7b793081c5d843979 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/nimitbhardwaj
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c03e5b5608317dff7425cfc7b793081c5d843979 -
Trigger Event:
push
-
Statement type: