Client
anywhere on the internet
A browser or Matrix app opens an HTTPS request to your domain.
Self-hosting · v1.0.0 · stable
pocket-homeserver turns a spare Android phone (Termux on a proot‑Debian userland) into an always-on server. It opens no inbound ports — all traffic is outbound-only through a Cloudflare Tunnel, so it works behind CGNAT or mobile data with no public IP and no port-forwarding.
One ./pocket.sh control panel installs and runs
the whole stack: a Matrix homeserver (continuwuity) with Element, the Caddy web
server, the Cloudflare Tunnel client, and around thirty optional self-hosted apps.
pocket-homeserver — control panel
──────────────────────────────────────
domain : my.example.org
services : 5 up / 6 supervised
──────────────────────────────────────
1) Configure / reconfigure (re-run the setup wizard)
2) Install / bring up the stack (resumes; safe to re-run)
3) Re-run everything (force) (redo every install step)
4) Status (what is installed & running)
5) Restart a service
6) Backups & restore
7) View logs
8) Stop / panic
9) Rotate credentials
10) Update components (versions + safe rollback)
11) Doctor / diagnostics (read-only health + preflight)
q) quit
Choose: This is a looping demonstration of the pocket-homeserver text interface. It is decorative; the same menus are documented in the docs section. The demo prints the control-panel banner for the example domain my.example.org, then the main menu (Configure, Install, Re-run, Status, Restart a service, Backups and restore, View logs, Stop / panic, Rotate credentials, Update components, Doctor / diagnostics, quit), and walks through each sub-menu with example services such as caddy, cloudflared, continuwuity, auth-gateway, memos and linkding shown as RUNNING or DOWN.
Overview
The ./pocket.sh control panel, the
outbound-only request flow, and everything that runs on a single phone —
in one short video.
Request flow
The phone never accepts an inbound connection. It dials out to Cloudflare once and keeps that tunnel open; every request rides back down it to a reverse proxy that listens only on loopback. The design works behind CGNAT, on mobile data, on any network — with nothing exposed to scan.
anywhere on the internet
A browser or Matrix app opens an HTTPS request to your domain.
anycast, near the visitor
Public TLS is terminated here; WAF and anycast routing run at the edge.
phone-initiated, outbound only
The encrypted hop the phone dialed out — there is no DNS A record and no open port to reach.
in the proot-Debian userland
Holds the one outbound tunnel and forwards each request to local loopback.
127.0.0.1:8443 (loopback)
The single front door: routes by hostname/path, adds security headers, gates protected apps.
127.0.0.1 only
Matrix (127.0.0.1:8448) or an app — each binds loopback and is reachable only through Caddy.
Why a phone
The hardware is already in a drawer: an always-on ARM64 computer with storage, a battery for a free UPS, and — paired with a Cloudflare Tunnel — a way to serve real traffic from anywhere. This is productized from a deployment that did exactly that.
months of uptime, no static IP, no monthly bill
One retired handset ran a live Matrix homeserver for months — public HTTPS over an outbound tunnel, on hardware that was already in a drawer.
0 inbound ports
Outbound-only Cloudflare Tunnel — nothing to scan, nothing to forward.
0 root for the core
Runs unprivileged inside Termux + a proot-Debian userland.
$0 monthly bill
A phone you already own; you supply a domain + a free Cloudflare account.
A retired phone is a low-power ARM64 computer that rides through power blips on its own battery — left plugged in, it just stays up.
A spare mid-range phone with ~3 GB RAM and a roomy SD card is enough. No VPS to rent, no new box to buy, no recurring cost.
Termux plus a proot-Debian chroot gives the server software a real /etc, /var, and package manager — entirely in userspace, no root for the core stack.
Behind CGNAT or on mobile data, the outbound Cloudflare Tunnel still serves public HTTPS — the phone needs no routable address and opens no port.
Real screens
Neutral demo captures — the web admin panel, plus a few of the optional apps you can switch on. Everything here runs on the phone, behind the same outbound tunnel.
Operating the stack
Day-two operations are the hard part of self-hosting — and they're first-class here: one menu, resumable installs, self-healing services, and one-command backups and key rotation, each a real script in the repo.
./pocket.sh is a plain-text numbered menu —
no extra packages, works over SSH and in Termux as-is. Every item just
runs a script you could run by hand, so nothing is hidden: configure,
install, status, restart a service, backups, logs, and a panic stop.
pocket-homeserver — control panel
────────────────────────────────────
domain : my.example.org
services : 6 up / 6 supervised
────────────────────────────────────
1) Configure / reconfigure
2) Install / bring up the stack
3) Re-run everything (force)
4) Status
5) Restart a service
6) Backups & restore
7) View logs
8) Stop / panic
q) quit Completed steps are marked done — re-runs skip them, and an interrupted install resumes where it stopped.
Three layers: a supervisor respawns crashed services, a Termux:Boot
launcher re-runs bring-up after a reboot
(ENABLE_BOOT=true), and a ~15-min watchdog
revives anything Android’s low-memory killer reaps.
Snapshot the database and full userland — each with a
.sha256 sidecar and optional
age encryption, on weekly/monthly retention.
Restore is dry-run unless you pass the exact phrase:
restore.sh --confirm=ERASE-AND-RESTORE
Each script rotates one credential, writes it
0600, and prints it — no hand-edited config.
No inbound ports, sha256-pinned downloads,
secrets in 0600 files (never on argv), and
forward_auth-gated apps — under a documented threat model.
Beyond the Matrix core, the stack ships a catalog of self-hosted apps
and subsystems. Every one is off by default, turned on
with its own ENABLE_* flag. A representative
slice:
Notes and quick capture. SQLite-backed, persists on the data volume.
ENABLE_MEMOS=1Tasks, kanban, and GTD. Accounts are admin-created.
ENABLE_VIKUNJA=1A self-hosted bookmark manager. Sign-up is off; the first superuser is yours.
ENABLE_LINKDING=1An RSS/Atom feed reader with native form login.
ENABLE_FRESHRSS=1A private metasearch engine. No accounts, no stored history.
ENABLE_SEARXNG=1Client-side encoders, converters, and generators. No backend.
ENABLE_ITTOOLS=1An uptime dashboard that probes your services and shows their history.
ENABLE_GATUS=1File sharing via expiring links. Uploads persist on the data volume.
ENABLE_PINGVIN=1ENABLE_AUTH_GATEWAY ENABLE_ADMIN ENABLE_CLOUD_BOTS · ENABLE_EXOBOT ENABLE_STICKERS ENABLE_ADMINBOT ENABLE_EMAIL ENABLE_MCP ENABLE_HONEYPOT ENABLE_USER_FILTER · ENABLE_MEDIA_FILTER ENABLE_BACKUP_DAEMON ENABLE_VAULTWARDEN ENABLE_RADICALE ENABLE_TRILIUM ENABLE_WALLABAG ENABLE_FORGEJO ENABLE_ADGUARD ENABLE_TAILSCALE ENABLE_PROXY_ROUTES Threat model
The design assumes a small, invite-only server reached only through a Cloudflare Tunnel, with federation off. Two structural facts shrink the attack surface before any single setting: no inbound ports and no federation. Everything below is in the documented threat model — guidance, not an audit.
Ship it
Clone it into Termux, run ./pocket.sh, and
answer a short wizard. You bring a domain and a free Cloudflare account;
the phone opens nothing inbound and keeps an outbound
tunnel instead. In a few minutes you own the whole stack.
v1.0.0 First stable release. MIT-licensed, dependency-pinned, and run in production on the maintainer's own phone.