Replaced the tmux+ttyd web terminal with actual kitty running through Xpra's HTML5 streaming. Full GPU rendering, native kitty tabs/splits, persistent sessions, and multi-client support.
kitty-web
Run the real kitty terminal in your browser. Mobile-friendly, GPU-accelerated, with full tab and split support.
Powered by Xpra — serves kitty as an HTML5 application via its built-in web client. This is not a terminal emulator in JavaScript; it's the actual kitty running on your server, streamed to your browser.
Features
- Real kitty — GPU rendering, ligatures, image protocol, all of it
- Kitty tabs and splits — native
ctrl+shift+t, splits, layouts - Persistent session — close the browser, reconnect later, everything is still there
- Multi-client — multiple browsers can view/interact with the same session
- Mobile-friendly — Xpra's HTML5 client handles touch input, keyboard, and scaling
- Push notifications — optional notification API for long-running commands
Architecture
Browser -> Caddy (HTTPS + Auth) -> Xpra HTML5 (port 7681) -> kitty
Xpra runs kitty inside a virtual X display (Xvfb) and streams the rendered output to browsers via WebSocket. The HTML5 client handles input, clipboard, and display scaling.
Quick Start
Prerequisites
- Debian/Ubuntu (tested on Debian 13 Trixie)
- kitty (
apt install kitty) - Xpra (
https://xpra.org/— add their repo for latest version)
Install
# Add Xpra repo (Debian example)
curl -sL https://xpra.org/xpra.asc | sudo tee /usr/share/keyrings/xpra.asc
echo "deb [signed-by=/usr/share/keyrings/xpra.asc] https://xpra.org/ $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/xpra.list
sudo apt update && sudo apt install -y xpra kitty
# Create a service user (optional)
sudo useradd -m -s /bin/bash rdp
# Install systemd service
sudo cp systemd/kitty-web.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now kitty-web
Manual Start
xpra start --bind-ws=0.0.0.0:7681 \
--start="kitty" \
--html=on \
--sharing=yes \
--no-daemon
Then open http://YOUR_IP:7681 in a browser.
Configuration
Edit systemd/kitty-web.service to customize:
| Option | Description |
|---|---|
--bind-ws=HOST:PORT |
WebSocket listen address |
--start="CMD" |
Application to launch (default: kitty) |
--sharing=yes |
Allow multiple clients to connect |
--readonly=no |
Allow keyboard/mouse input |
Reverse Proxy (Caddy)
terminal.example.com {
# Add your auth here (OAuth2 Proxy, Authentik, etc.)
reverse_proxy YOUR_SERVER:7681
}
WebSocket support is required. Caddy handles this automatically. See caddy-example.conf for a full example with authentication options.
Kitty Config
Place your kitty config at ~/.config/kitty/kitty.conf for the service user. See config/kitty.conf for a dark-themed example.
Optional: Push Notifications
The notify-server.py and kitty-notify command provide a simple browser notification system:
# Install
sudo cp notify-server.py /opt/kitty-web/
sudo cp kitty-notify /usr/local/bin/
sudo cp systemd/kitty-notify.service /etc/systemd/system/
sudo systemctl enable --now kitty-notify
# Usage
kitty-notify "Build complete!"
echo "done" | kitty-notify
Requires proxying /api/* to port 7682 — see caddy-example.conf.
Files
kitty-web/
README.md
LICENSE
install.sh # Automated installer
caddy-example.conf # Reverse proxy config template
notify-server.py # Push notification HTTP API (optional)
kitty-notify # CLI notification command (optional)
manifest.json # PWA manifest
icon-192.png # PWA icon
icon-512.png # PWA icon
config/
tmux.conf # tmux config (for optional tmux-inside-kitty usage)
kitty.conf # Kitty terminal config (dark theme)
systemd/
kitty-web.service # Xpra + kitty systemd unit
kitty-notify.service # Notification API systemd unit
License
MIT