On this page

The website you're reading right now is not on AWS, not on Vercel and not on a Raspberry Pi. It runs on a Samsung Galaxy S20+ with a broken screen, lying in a drawer at my home in Vienna. In this post I'll show you exactly how I turned an old Android phone into a home server: no root, no open ports on my router, a real domain with HTTPS, a Next.js site, a PostgreSQL database and a few self-hosted tools on top. I'll also tell you what didn't work, because that part cost me more evenings than the rest together.

Samsung Galaxy S20+ smartphone in Cloud Blue standing next to its retail box
The Galaxy S20+, the phone this site runs on. Mine is the same model, minus a working screen. Image: NeerajBhateja, CC BY-SA 4.0, resized.

Why an old phone and not a Raspberry Pi?

Honestly, it started as a "can I?" question. The screen of my S20+ was cracked and repairing it would have cost more than the phone is worth. But everything else was fine, and when I looked at the specs I felt a bit silly about leaving it in a drawer:

Galaxy S20+ (SM-G985F)Typical Raspberry Pi 4 setup
CPU8 cores, Exynos 990, 64-bit ARM4 cores, Cortex-A72, 64-bit ARM
RAM8 GB1–8 GB
Storage128 GB internal flashmicroSD or USB SSD (extra)
Backup powerBuilt-in battery, works as a UPSNeeds a separate UPS HAT
NetworkWi-Fi 6Gigabit Ethernet, Wi-Fi 5
Cost for me€0, already ownedBoard, case, power supply, storage

A phone also brings its own uninterruptible power supply. When the power goes out for a minute, the server doesn't even notice. And it uses very little power at idle, because saving battery is what phones are built for.

Raspberry Pi 4 in a black aluminium case with fans sitting on a TP-Link network switch
The classic choice for a small home server: a Raspberry Pi 4. Great hardware, but I already had something faster in a drawer. Image: RAIL P (RAIL.PHOTOGRAPHY), CC0, resized.

The downsides are real too, and I'll come back to them: Android is not Linux in the way a server distro is, there is no Docker, and the system really wants to kill anything that runs in the background for too long.

The architecture in one picture

Visitor ── HTTPS ──► Cloudflare edge (TLS, WAF, rate limiting)
                          │
                          │  outbound-only tunnel, no open ports at home
                          ▼
┌─────────────── Samsung Galaxy S20+ · Android 13 · Termux, no root ───────────────┐
│ cloudflared ──► Next.js blog        :3000   (udocker, node:22)                     │
│             ──► solar dashboard     :3001   (udocker, node:22)                     │
│             ──► Umami analytics     :3002   (udocker)                              │
│ PostgreSQL 18 on 127.0.0.1 · cron backups at 03:00 · sshd :8022 (LAN, key only)    │
│ Termux:Boot starts everything after a reboot                                       │
└────────────────────────────────────────────────────────────────────────────────────┘

The key idea: the phone opens a connection out to Cloudflare, and visitors come back in through that connection. My router has no port forwarding at all, my home IP is never exposed, and I don't need a static IP or dynamic DNS.

Step 1: get rid of the screen problem

Close-up of a smartphone with a badly cracked glass screen
A cracked screen makes a phone useless as a phone, but a server doesn't need one. Image: Sygmoral, CC BY-SA 4.0, resized.

With a broken touchscreen you can't do much on the device itself, so the first goal is to control it from your computer. If the touch layer still works partly, enable Developer options (tap "Build number" seven times) and USB debugging. Then scrcpy mirrors the screen to your laptop and lets you use mouse and keyboard:

brew install scrcpy
brew install --cask android-platform-tools
adb devices        # accept the prompt on the phone once
scrcpy             # the phone's screen in a window on your computer

After that, wireless debugging means you don't need the cable any more. From here on I never touched the phone again; everything happened over adb and later over SSH.

Step 2: install Termux (from F-Droid, not Google Play)

Termux gives you a real Linux userland on Android: a shell, a package manager and thousands of packages compiled for ARM. Install it from F-Droid or GitHub, because the Google Play version is old and doesn't get package updates. Install Termux:Boot from the same source; you'll need it to start everything after a reboot.

Termux terminal on a Samsung Galaxy phone with Android 13 showing neofetch output with 8 CPU cores and aarch64
Termux on a Galaxy S20 running Android 13: a normal Linux shell on an aarch64 phone, no root needed. Image: Termux developers, Neofetch: Dylan Araps, GPLv3, resized.

These are the packages my server runs on today:

pkg update && pkg upgrade
pkg install openssh rsync cronie termux-services postgresql cloudflared udocker

Step 3: SSH in and never use the phone keyboard again

Termux's SSH server listens on port 8022, and your user name is the Android app user (something like u0_a123, check it with whoami). I copied my public key over and switched password login off:

# on the phone
sshd
# on the Mac
ssh-copy-id -p 8022 [email protected]
ssh -p 8022 [email protected]

Then set PasswordAuthentication no in $PREFIX/etc/ssh/sshd_config. SSH stays on the home network only; it's not part of the tunnel.

Step 4: stop Android from killing your server

This was the first big surprise. Everything ran fine for a while, then the website was just gone. No error, no log line. Android 12 and newer have a phantom process killer that kills child processes of apps when there are "too many" of them, and a web server with a supervisor, a tunnel and a database is exactly that.

Three settings fixed it for me:

# from the computer, with adb connected (Android 12L/13)
adb shell "settings put global settings_enable_monitor_phantom_procs false"
# in Termux: keep the CPU awake while Termux runs
termux-wake-lock

And in Android's settings: set Termux to Battery → Unrestricted, so it isn't put to sleep. On Android 14 and newer there's a switch in Developer options called "Disable child process restrictions" that does the same as the adb command.

Battery tip: a phone that's plugged in 24/7 is not great for the battery. On Samsung, turn on Protect battery so it stops charging at 85 %. If the battery ever starts to swell, take the phone out of service.

Step 5: run Node.js and Next.js with udocker

There is no real Docker on an unrooted phone, because Docker needs kernel features Android doesn't give apps. udocker is the trick: it pulls normal Docker images and runs them with proot, completely in user space. So my site runs in the official node:22 image, the same one I'd use anywhere else:

udocker pull node:22-bookworm-slim
udocker create --name=nextjs node:22-bookworm-slim
udocker run -v ~/sites/main:/app -w /app nextjs \
    /bin/sh -c "PORT=3000 HOSTNAME=127.0.0.1 node server.js"

I don't build on the phone. The Next.js site uses output: "standalone", and a small deploy.sh on my Mac builds it inside a linux/arm64 container with Podman (same CPU architecture as the phone), then rsyncs the result over SSH and restarts it. A full deploy takes about as long as a coffee.

podman run --rm --platform linux/arm64 -v "$PWD:/src:ro" -v "$PWD/.out:/out" node:22-bookworm-slim \
    sh -c "cp -r /src /build && cd /build && npm ci && npm run build && cp -r .next/standalone/. /out/"
rsync -az --delete -e "ssh -p 8022" .out/ [email protected]:sites/main/

On the phone a tiny shell script supervises every site: it starts the process, writes its PID, and restarts it five seconds after it exits. Nothing fancy, but it's been enough.

udocker gotchas I hit

  • Android doesn't allow hard links in app storage. Images that use them (anything installed with pnpm, for example) fail in strange ways. I flatten such images on my Mac, export them as a single tarball and use udocker import.
  • udocker skips every path that contains dev/ when it unpacks a layer, because it looks like a device folder. That broke Umami until I copied those node_modules/.../dev folders in by hand.
  • Not everything works under proot. I wanted OpenObserve for logs and traces, and it simply hung on start. I switched to a native Jaeger binary instead, which runs fine.

Step 6: PostgreSQL on Android

This is the easy part. PostgreSQL 18 is a normal Termux package and runs natively, no container:

initdb $PREFIX/var/lib/postgresql
pg_ctl -D $PREFIX/var/lib/postgresql -l ~/postgres.log start
createdb main

It only listens on 127.0.0.1. A cron job runs pg_dumpall every night at 03:00 and keeps 14 days of backups, and every deploy from my Mac makes a fresh backup first and copies it off the phone. A server you can drop on the floor needs backups that don't live on it.

Step 7: a real domain with HTTPS, without opening ports

Cloudflare Tunnel is what makes this whole thing safe enough for me. cloudflared runs on the phone and keeps an outbound connection to Cloudflare. Visitors talk to Cloudflare's edge (with its TLS certificate, WAF and rate limiting), and Cloudflare forwards the request through the tunnel to localhost on the phone.

cloudflared tunnel login
cloudflared tunnel create phone
cloudflared tunnel route dns phone radionbalan.com

The config maps host names to local ports. Mine is generated by a script from one folder per site, so adding a new subdomain is one file and one command:

tunnel: <tunnel-id>
credentials-file: /data/data/com.termux/files/home/.cloudflared/<tunnel-id>.json
ingress:
  - hostname: radionbalan.com
    service: http://localhost:3000
  - hostname: solar.radionbalan.com
    service: http://localhost:3001
  - service: http_status:404

The last rule matters: any host name you didn't list gets a 404 instead of reaching something by accident.

Step 8: survive reboots with Termux:Boot

Phones reboot for updates, and sometimes for no reason. Termux:Boot runs every script in ~/.termux/boot/ once Android has started. Mine is short:

#!/data/data/com.termux/files/usr/bin/sh
termux-wake-lock
pg_ctl -D "$PREFIX/var/lib/postgresql" -l "$HOME/sites/postgres.log" start
crond
$HOME/bin/site start-all
sleep 20
$HOME/bin/tunnel

Open the Termux:Boot app once after installing it, otherwise Android never lets it run.

What runs on the phone today

ServiceWhat it isHow it runs
radionbalan.comThis blog, Next.js 16udocker, node:22
solar.radionbalan.comPrivate dashboard for my solar inverter and heat pump, reads the inverter over Modbus TCP every 10 sudocker, node:22
stats.radionbalan.comUmami, cookieless analyticsudocker, flattened image
JaegerOpenTelemetry traces, local onlynative binary
PostgreSQL 18Data for solar and Umaminative Termux package
Status pageSmall Python page with security checks and backups, local onlynative Python

The solar dashboard is my favourite part. The phone sits on the same Wi-Fi as my Huawei inverter, so it reads production data directly from the house, stores it in PostgreSQL and shows it on a page I can open from anywhere. If you're into that, I wrote about heating with surplus solar using an ESP32.

Security: what I did and why

Running a public website from your living room sounds scary, so here's the checklist I settled on:

  • No open ports on the router. All traffic comes through the Cloudflare tunnel.
  • Unknown host names return 404 in the tunnel config.
  • Every service listens on 127.0.0.1, never on the Wi-Fi interface, except SSH.
  • SSH is key-only and only reachable on the home network.
  • Admin tools without login (Jaeger, the status page) are not in the tunnel at all. I reach them with an SSH port forward like ssh -p 8022 -N -L 16686:127.0.0.1:16686 [email protected].
  • Secrets live in .env files on the phone and never in Git.
  • Backups leave the device on every deploy.

Is it fast enough?

For a personal website, yes, easily. The pages are static or close to it, Cloudflare caches the assets, and the phone mostly serves HTML. The heavier work (the build) happens on my Mac. The one thing you notice is that udocker adds overhead, because proot intercepts system calls; CPU-heavy work inside a container is slower than it would be natively. For a blog, a small dashboard and analytics with my traffic, the phone has plenty of headroom.

Limitations, honestly

  • It's a single device. If the phone dies, the site is down until I restore it on another one. That's why the backups and the deploy script matter more than any tuning.
  • No real Docker or Docker Compose. udocker covers a lot, but some images need work and some don't run at all.
  • Android updates can change the rules. Every new version tightens background limits a bit more.
  • Home internet upload speed is your bandwidth limit. For text and a few images that's fine; for video it isn't.
  • Remote admin needs extra work. Right now I deploy from home; SSH through the tunnel is next on my list.

FAQ

Can you use an old Android phone as a server without root?

Yes. Everything in this post runs without root: Termux for the Linux environment, udocker for containers, native packages for PostgreSQL and cloudflared. Root would make some things easier, but it's not needed.

Can I run Docker on Android?

Not the real Docker daemon on a normal unrooted phone, because it needs kernel features apps can't use. udocker runs most Docker images in user space instead, with some limits (no hard links, slower system calls).

How do I make my phone server reachable from the internet?

Use a tunnel, such as Cloudflare Tunnel. The phone connects out, so you need no port forwarding, no static IP and no dynamic DNS, and your home IP address stays private.

Will running a server damage the phone's battery?

Being on the charger all the time ages a battery faster. Limit the charge to 85 % (Samsung calls it Protect battery), keep the phone somewhere cool, and check it from time to time for swelling.

What can I host on an old phone?

A static or Next.js website, a small API, a database, analytics like Umami, home automation dashboards, a Git mirror, or a file sync service. Anything light that doesn't need special kernel features.

Conclusion

Turning an old Android phone into a home server took me two evenings for the basics and a few more for the annoying details: phantom process killer, udocker's quirks and making sure everything comes back after a reboot. In return I got a quiet, low-power server with a built-in UPS that cost me nothing, serves a real domain over HTTPS and doesn't expose my home network. If you have a phone with a broken screen in a drawer, give it a second life. Worst case, you learn a lot about Linux, Android and networking. Best case, you're reading its output right now.