One tree, one installer, three platform abstractions: a
platform adapter (packages, paths, firewall, TLS), a
service manager (systemd or the built-in
supervisor) and render modes (bind address, ports,
TLS).
| Debian 12 | supported — this host | systemd | certbot (letsencrypt) |
ufw + fail2ban |
| Ubuntu 22.04 / 24.04 | supported — same adapter | systemd | certbot | ufw + fail2ban |
| Termux → proot Debian | supported, recommended on Android | supervisor | tunnel or none | none (bind 127.0.0.1) |
| Termux native | experimental — needs on-device acceptance | supervisor | tunnel or none | none |
| Alpine / OpenRC | deferred — busybox + OpenRC differences, no demand yet | supervisor | tunnel | none |
| macOS / launchd | deferred — dev-only, no server story | supervisor | none | none |
“Deferred” means the adapter contract already supports it (≈60 lines
each) but nobody ships it. “Experimental” means the code paths exist and
are simulated-tested, but no real device has passed
deploy/test-termux.sh yet.
Unchanged from the server guide:
git clone --depth 1 https://git.webx11.duckdns.org/webx11.git /opt/webx11-src
cd /opt/webx11-src && cp deploy/wx11.conf deploy/wx11.conf.local # edit domain/user/email
sudo deploy/install.sh --conf deploy/wx11.conf.local --source /opt/webx11-src --tls --admin you@example.com
sudo deploy/verify.sh --conf deploy/wx11.conf.local
See FORK.md for the full
walkthrough, ports and secrets.
You need: Termux (F-Droid or GitHub build, not the stale Play Store one), ~3 GB free, a domain or DuckDNS name, and a tunnel for HTTPS.
# 1. On the phone, inside Termux
pkg update -y && pkg install -y proot-distro git curl termux-api
bash <(curl -fsSL https://webx11.duckdns.org/deploy/termux-bootstrap.sh)
Or from a clone:
git clone --depth 1 https://git.webx11.duckdns.org/webx11.git ~/webx11
bash ~/webx11/deploy/termux-bootstrap.sh # creates the Debian userland
Inside the Debian login, edit deploy/wx11.conf.local and
install:
WX11_DOMAIN="myphone.example.org"
WX11_USER="root" # proot has a single user; root is expected
WX11_ROOT="/root/webx11" # or any path inside the proot filesystem
WX11_DATA="/root/.local/share/webx11"
WX11_STATE="/root/.local/share/webx11/federation"
WX11_LOG_DIR="/root/.local/share/webx11/log"
WX11_PLATFORM="debian" # yes: inside proot this IS Debian
WX11_SERVICE_MANAGER="supervisor" # proot has no systemd
WX11_BIND_ADDR="127.0.0.1"
WX11_HTTP_PORT="8080"
WX11_TLS_MODE="tunnel"
WX11_TUNNEL_ID="<uuid from `cloudflared tunnel create`>"
WX11_ENABLE_UBAN="0" # optional: heavy for a phone
bash deploy/install.sh --conf deploy/wx11.conf.local --source /root/webx11-src --platform debian
bash deploy/verify.sh --conf deploy/wx11.conf.local
Then the tunnel (HTTPS terminates at Cloudflare, nginx stays on
127.0.0.1:8080):
cloudflared tunnel login
cloudflared tunnel create webx11 # prints the UUID → WX11_TUNNEL_ID
cloudflared tunnel route dns webx11 myphone.example.org
cloudflared tunnel --config /root/.local/share/webx11/cloudflared/config.yml run
Keep it alive: termux-wake-lock in the Termux session,
and disable battery optimisation for Termux in Android settings
(Settings → Apps → Termux → Battery → Unrestricted).
No proot. Everything lives in $PREFIX and
$HOME, nginx and Python come from pkg,
services run under the supervisor.
bash deploy/termux-bootstrap.sh --native
git clone --depth 1 https://git.webx11.duckdns.org/webx11.git ~/webx11-src
cd ~/webx11-src
cp deploy/wx11.conf deploy/wx11.conf.local
Minimal native config:
WX11_DOMAIN="myphone.example.org"
WX11_USER="$(whoami)" # the Termux app user
WX11_ROOT="$HOME/webx11"
WX11_DATA="$HOME/.local/share/webx11"
WX11_STATE="$HOME/.local/share/webx11/federation"
WX11_LOG_DIR="$HOME/.local/share/webx11/log"
WX11_PLATFORM="termux"
WX11_SERVICE_MANAGER="supervisor"
WX11_BIND_ADDR="127.0.0.1"
WX11_HTTP_PORT="8080"
WX11_HTTPS_PORT="8443" # unused in tunnel/none mode
WX11_TLS_MODE="none" # or tunnel
WX11_PREFIX="$PREFIX"
WX11_PHP="0" # strips the 7 php-fpm locations
WX11_ETC_DIR="$PREFIX/etc/webx11"
WX11_UBAN_DIR="$HOME/.local/share/webx11/uban"
WX11_MONITOR_DIR="$PREFIX/bin/webx11-monitor"
WX11_ENABLE_UBAN="0"
bash deploy/install.sh --conf deploy/wx11.conf.local --source "$HOME/webx11-src" --platform termux
bash deploy/lib/svc.sh --conf deploy/wx11.conf.local start
bash deploy/test-termux.sh # acceptance gate — must print TERMUX ACCEPTANCE PASSED
Before claiming support, run the one-page device runbook: TERMUX-CHECKLIST.md
— it has the exact commands, the pass criteria and a troubleshooting
table.
| Privileged ports (80/443) | yes | no | Android apps cannot bind < 1024 |
| certbot | yes | no | needs privileged ports + nginx plugin |
| fail2ban | yes | no | no iptables without root |
| ufw | yes | no | same |
| php-fpm | yes | no | 7 fastcgi_pass blocks stripped
(WX11_PHP=0) |
| systemd | yes | no | replaced by the supervisor |
| logrotate | yes | no | supervisor rotates at 5 MB |
| cron | /etc/cron.d |
no | supervisor runs the same 6-job schedule |
| bcrypt | apt package | maybe | pkg install python-bcrypt, else auth is
unavailable |
| Always-on | yes | fragile | Doze kills processes without a wake lock |
termux-wake-lock before svc.sh start;
termux-wake-unlock after svc.sh stop.~/.termux/boot/webx11.sh
(#!/data/data/com.termux/files/usr/bin/sh →
termux-wake-lock; ~/webx11/deploy/lib/svc.sh --conf ~/webx11/deploy/wx11.conf start),
or start it by hand.~/.local/share/webx11/log/supervisor.log — every
respawn is logged with a status.node.json.url must be
https://…. With WX11_TLS_MODE=none the
instance works locally but peers will refuse the URL — by design, the
SSRF guard requires https.degraded after 3
failed handshakes and recover on the next successful one — that is
expected behaviour, not a bug.# server
sudo deploy/verify.sh --conf deploy/wx11.conf.local
# anything without systemd (honest FAIL where the manager is absent)
bash deploy/verify.sh --conf deploy/wx11.conf.local --platform termux
# Android acceptance (the only proof that counts for native Termux)
bash deploy/test-termux.sh # see TERMUX-CHECKLIST.md for the full runbook
verify.sh never fakes a check: on a platform without
fail2ban/ufw it prints SKIP with the reason instead of
failing, and the service block follows the configured service
manager.
bash deploy/tests/supervisor-test.sh # real respawn, cron matching, log rotation
bash deploy/tests/termux-sim.sh # asserts the Termux adapter never calls root-only tools
They prove the platform-agnostic mechanics. They do
not prove Android — only
deploy/test-termux.sh on a device does that.