Self-hosting · v1.0.0 · stable

A private server that lives on a phone in your drawer.

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.

  • No inbound ports
  • No monthly bill
  • No root required
./pocket.sh — the same menus you drive on the device.

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

A two-minute walkthrough

The ./pocket.sh control panel, the outbound-only request flow, and everything that runs on a single phone — in one short video.

Request flow

One outbound tunnel. No inbound ports.

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.

  1. Client

    anywhere on the internet

    A browser or Matrix app opens an HTTPS request to your domain.

  2. Cloudflare edge

    anycast, near the visitor

    Public TLS is terminated here; WAF and anycast routing run at the edge.

  3. Cloudflare Tunnel

    phone-initiated, outbound only

    The encrypted hop the phone dialed out — there is no DNS A record and no open port to reach.

  4. cloudflared

    in the proot-Debian userland

    Holds the one outbound tunnel and forwards each request to local loopback.

  5. Caddy

    127.0.0.1:8443 (loopback)

    The single front door: routes by hostname/path, adds security headers, gates protected apps.

  6. The service

    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

A spare phone is a quietly capable server.

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.

runs the whole stack
1 spare phone

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.

  • Always-on, battery-backed

    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.

  • Hardware you already own

    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.

  • A normal Linux userland

    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.

  • Real HTTPS from anywhere

    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

See what's running.

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.

The web admin panel Stack health, one-tap restarts, backups, live device metrics, and a guarded danger zone — reached over the tunnel.
Memos Notes and quick capture.
IT-Tools A client-side developer toolbox.
Gatus Uptime and health at a glance.

Operating the stack

Built to be run, not just installed.

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.

One control panel for the whole lifecycle

./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.

  • Configuresetup.sh
  • Install / bring upscripts/install.sh
  • Statusinstall.sh --status
  • Restart a serviceops/restart.sh <svc>
  • Stop / panicops/panic-*.sh

Resumable, status-aware installs

Completed steps are marked done — re-runs skip them, and an interrupted install resumes where it stopped.

--status --force --reset --check

Survives crashes and reboots

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.

Snapshots, retention & a dry-run restore

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

One-command credential rotation

Each script rotates one credential, writes it 0600, and prints it — no hand-edited config.

  • Matrix registration token
  • Admin-panel password
  • Cloudflare Tunnel token
  • OIDC signing key gated
  • Admin-bot token gated

Secure by construction

No inbound ports, sha256-pinned downloads, secrets in 0600 files (never on argv), and forward_auth-gated apps — under a documented threat model.

Read the security model
Optional surface

Add only what you'll use.

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:

08 popular web apps each on its own subdomain, loopback-bound behind the single tunnel

Advanced subsystems gated, off by default, with client-supplied secrets

More in the catalog full list with guides in the docs

Threat model

Secure by construction

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.

Request path
  1. untrusted internet
  2. Cloudflare edge WAF · rate limiting · TLS terminates here
  3. cloudflared / Caddy mutually-authenticated tunnel · loopback origin
  4. app login / optional SSO native login or Matrix-SSO forward-auth
  5. loopback services 127.0.0.1 only — nothing binds public

Ship it

Put a real server in your pocket.

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.