How I Turned My Mom's Old Android Phone into a Server Using Termux
I had no idea how VPS even worked. So I used my mom's old Android phone, installed Termux, and figured it out the hard way — SSH, Discord bot, live dashboard, Cloudflare tunnel, and all the problems nobody warns you about.
How I Turned My Mom’s Old Android Phone into a Server Using Termux
So this was my mom’s old phone. She stopped using it, it was just sitting there doing nothing. And at the time I had zero idea how VPS actually works — like I had heard the word, seen people talk about “deploying to a server” and stuff, but I genuinely didn’t understand what that meant in practice.
I thought, okay, let me just try to run something on this phone. If it works, cool. If not, whatever.
It actually worked. I got a Python server running, set up SSH so I could control it from my own phone, added a live dashboard, a Discord bot, a firewall, auto-updates from GitHub — the whole thing. And I want to write about it because when I was figuring this out I couldn’t find one guide that explained everything from scratch without assuming you already know what you’re doing.
Before I get into the steps though — I need to be upfront about what this actually is and what it isn’t.
Honest Warning: This Is Not a Real VPS
I’m calling it a “VPS” in the title because that’s the closest thing people can relate it to, but let me be real with you.
A real VPS gives you a dedicated Linux environment, a static IP, guaranteed uptime, proper RAM and CPU resources, and you can run pretty much anything on it. This is not that.
Problems I ran into:
- Termux randomly stops running. Android is designed to kill background apps to save battery. Even with all the battery optimization settings turned off, I would sometimes come back to find Termux had been killed overnight. This is the biggest issue and there’s no perfect fix for it.
- The phone gets hot. Running stuff 24/7 on a phone that wasn’t designed for it means it runs warm constantly. You should keep it out of its case and somewhere ventilated.
- RAM is very limited. Most old phones have 2–3GB RAM total, and Android itself eats a chunk of that. You can’t run anything memory-heavy.
- No live domain — I didn’t actually try that part. Cloudflare Tunnel can theoretically give you a public URL and you could point a domain to it, but I didn’t test this myself. I’ll include the steps based on how it’s supposed to work, but if you try it and something breaks, that’s on you to figure out — I haven’t been there yet.
- Storage limitations. Old phones don’t have much internal storage. Don’t expect to run a database or store any serious amount of files.
- No static IP. Your home Wi-Fi IP changes sometimes. The Cloudflare tunnel works around this but it’s one more moving part that can break.
What this IS good for:
- Learning how servers actually work, hands-on
- Running small personal projects — a bot, a lightweight API, a static site
- Experimenting without spending money on a cloud server you might break anyway
- Understanding SSH, tmux, process management, and firewalls for the first time
For me, this was a good first experience. I understood what “running a server” actually means after doing this. Eventually you’ll want a real VPS for anything serious — DigitalOcean, Hetzner, whatever — but as a starting point to get your hands dirty, this works.
Okay. Now the actual guide.
What You Need
Before we start, here is everything you need:
- Phone 1 — the server. This is the phone that will run 24/7. Any Android phone running Android 7+ will work. The older and slower it is, the more creative you feel using it.
- Phone 2 — the controller. Your daily driver or any other Android phone. Used to SSH into Phone 1 and manage it remotely.
- A Wi-Fi connection — Phone 1 needs to stay connected. Ethernet via a USB OTG adapter works even better if you have one.
- A Cloudflare account — free tier, for exposing your server to the internet.
- A GitHub account — free, for the auto-update system.
That is genuinely it.
Understanding the Architecture
Before we run a single command, let me explain the full picture so nothing feels like magic.
[Internet]
↓
[Cloudflare Tunnel] ← Free, encrypted, no port forwarding needed
↓
[Phone 1 - Termux Server]
├── Flask Website (port 8000)
├── Live Dashboard (port 9000)
├── Discord Bot (Python)
├── UFW Firewall
└── GitHub Auto-Update (cron job)
↑
[Phone 2 - SSH Controller]
└── SSH via local Wi-Fi (port 8022)
Phone 1 runs everything. Phone 2 gives you a proper terminal to control it from. Cloudflare punches a secure tunnel through your network so the outside world can reach your server without you needing to touch router settings or open firewall ports.
Everything runs inside tmux sessions so nothing dies when you disconnect.
Step 1: Install Termux on Both Phones
Do not install Termux from the Play Store. That version is outdated and abandoned. Download it from F-Droid instead.
- Go to f-droid.org on both phones and install the F-Droid app.
- Search for Termux inside F-Droid and install it.
- Open Termux and run the initial update:
pkg update && pkg upgrade -y
This will take a few minutes on first run. Let it finish completely before moving on.
Step 2: Set Up SSH on Phone 1 (The Server)
We will install an SSH server on Phone 1 so Phone 2 can connect to it and control it remotely from a proper terminal.
On Phone 1:
# Install OpenSSH
pkg install openssh -y
# Set a password for your Termux user
passwd
# When prompted, enter a password. I used: shoudo123
# (In production, use something stronger!)
# Start the SSH daemon
sshd
Now find Phone 1’s local IP address:
pkg install net-tools -y
ifconfig | grep "inet "
Look for an IP that starts with 192.168. — this is your local IP. For example: 192.168.0.218.
You can also check it from your router’s admin panel at 192.168.0.1 or 192.168.1.1.
Make SSH Start Automatically
You do not want to manually run sshd every time Termux restarts. Fix that:
mkdir -p ~/.termux/boot
nano ~/.termux/boot/start-ssh.sh
Paste this inside:
#!/data/data/com.termux/files/usr/bin/sh
sshd
Save with Ctrl + X, then Y, then Enter. Now install the Termux:Boot add-on from F-Droid as well — it is what actually runs this script on device boot.
Step 3: Connect Phone 2 to Phone 1 via SSH
On Phone 2:
pkg install openssh -y
# Connect to Phone 1
# Replace 192.168.0.218 with your actual Phone 1 IP
ssh -p 8022 u0_a399@192.168.0.218
The username (u0_a399) might be different on your phone. To find yours, run whoami on Phone 1.
Enter the password you set in Step 2. If you see the Termux prompt, you are connected. You are now controlling Phone 1 from Phone 2.
Optional: Set Up SSH Key Authentication (Recommended)
Typing a password every time gets old fast. Set up key-based login:
# On Phone 2 — generate a key pair
ssh-keygen -t ed25519 -C "phone2-controller"
# Press Enter for all prompts (no passphrase for convenience)
# Copy the public key to Phone 1
ssh-copy-id -p 8022 u0_a399@192.168.0.218
Now you can SSH in with no password at all.
Step 4: Install Core Tools on Phone 1
From now on, all commands run on Phone 1 (either directly or via SSH from Phone 2).
# Essential tools
pkg install python git curl wget nano tmux -y
# Python package manager
pip install --upgrade pip
# Install Flask for our web server and dashboard
pip install flask
# Install psutil for system stats (more reliable than parsing /proc manually)
pip install psutil
Step 5: Set Up tmux for Persistent Sessions
This is critical. When you disconnect SSH, everything running in that terminal dies. tmux keeps processes running in the background, detached from your SSH session.
Think of tmux as having multiple persistent terminal tabs that survive disconnections.
# Start a new named tmux session
tmux new-session -s main
Inside tmux, you can:
- Create new windows:
Ctrl + b, thenc - Switch windows:
Ctrl + b, then window number (0, 1, 2…) - Detach (leave everything running):
Ctrl + b, thend - Reattach later:
tmux attach -t main
We will use separate tmux windows for each service. This way everything is organized and you can check on any service instantly.
Step 6: Run a Python Web Server
Let us start simple. Create a basic website to confirm everything works.
# Create a project folder
mkdir -p ~/server/website
cd ~/server/website
# Create a simple index page
nano index.html
Paste this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Phone Server</title>
<style>
body { font-family: monospace; background: #0d0d0d; color: #00ff88; padding: 40px; }
h1 { font-size: 2rem; }
</style>
</head>
<body>
<h1>🟢 Server is Online</h1>
<p>Running on an Android phone using Termux.</p>
<p>No cloud. No cost. Just vibes.</p>
</body>
</html>
Save and start the server:
# In tmux window 0
python -m http.server 8000
Detach with Ctrl + b, d. Your website is now running at http://192.168.0.218:8000.
Step 7: Live Server Dashboard
This is one of my favorite parts. A real-time dashboard that shows RAM usage, CPU load, uptime, disk space, and running processes — all accessible from a browser.
mkdir -p ~/server/dashboard
nano ~/server/dashboard/dashboard.py
Paste the full dashboard code:
from flask import Flask, render_template_string, request, redirect, url_for, session
import psutil
import subprocess
import os
from datetime import timedelta
app = Flask(__name__)
app.secret_key = "change_this_to_something_random"
PASSWORD = "shoudo123"
HTML = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="5">
<title>Server Dashboard</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Courier New', monospace; background: #0a0a0a; color: #e0e0e0; padding: 20px; }
h1 { color: #00ff88; margin-bottom: 20px; font-size: 1.5rem; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 20px; }
.card { background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 8px; padding: 15px; }
.card h3 { color: #888; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 8px; }
.card .value { color: #00ff88; font-size: 1.5rem; font-weight: bold; }
.card .sub { color: #555; font-size: 0.8rem; margin-top: 4px; }
.bar-bg { background: #2a2a2a; border-radius: 4px; height: 8px; margin-top: 8px; }
.bar-fill { background: #00ff88; border-radius: 4px; height: 8px; transition: width 0.3s; }
.bar-fill.warn { background: #ffaa00; }
.bar-fill.crit { background: #ff4444; }
table { width: 100%; border-collapse: collapse; background: #1a1a1a; border-radius: 8px; overflow: hidden; }
th { background: #2a2a2a; color: #888; font-size: 0.75rem; text-transform: uppercase; padding: 10px; text-align: left; }
td { padding: 8px 10px; border-bottom: 1px solid #2a2a2a; font-size: 0.85rem; color: #ccc; }
tr:last-child td { border-bottom: none; }
.tag { display: inline-block; padding: 2px 6px; border-radius: 3px; font-size: 0.7rem; }
.tag.run { background: #003322; color: #00ff88; }
.footer { color: #333; font-size: 0.75rem; margin-top: 15px; }
</style>
</head>
<body>
<h1>⚡ TERMUX SERVER DASHBOARD</h1>
<div class="grid">
<div class="card">
<h3>CPU Usage</h3>
<div class="value">{{ cpu }}%</div>
<div class="bar-bg"><div class="bar-fill {{ 'crit' if cpu|int > 80 else 'warn' if cpu|int > 50 else '' }}" style="width: {{ cpu }}%"></div></div>
</div>
<div class="card">
<h3>RAM Usage</h3>
<div class="value">{{ ram_used }} / {{ ram_total }}</div>
<div class="sub">{{ ram_pct }}% used</div>
<div class="bar-bg"><div class="bar-fill {{ 'crit' if ram_pct|int > 80 else 'warn' if ram_pct|int > 60 else '' }}" style="width: {{ ram_pct }}%"></div></div>
</div>
<div class="card">
<h3>Storage</h3>
<div class="value">{{ disk_used }} / {{ disk_total }}</div>
<div class="sub">{{ disk_pct }}% used</div>
<div class="bar-bg"><div class="bar-fill {{ 'crit' if disk_pct|int > 90 else 'warn' if disk_pct|int > 70 else '' }}" style="width: {{ disk_pct }}%"></div></div>
</div>
<div class="card">
<h3>Uptime</h3>
<div class="value" style="font-size: 1rem;">{{ uptime }}</div>
</div>
<div class="card">
<h3>Load Average</h3>
<div class="value" style="font-size: 1.1rem;">{{ load }}</div>
<div class="sub">1m / 5m / 15m</div>
</div>
<div class="card">
<h3>Network</h3>
<div class="sub">↑ Sent: {{ net_sent }}</div>
<div class="sub" style="margin-top: 4px;">↓ Recv: {{ net_recv }}</div>
</div>
</div>
<table>
<thead><tr><th>PID</th><th>Process</th><th>CPU%</th><th>RAM%</th><th>Status</th></tr></thead>
<tbody>
{% for p in processes %}
<tr>
<td>{{ p.pid }}</td>
<td>{{ p.name }}</td>
<td>{{ "%.1f"|format(p.cpu) }}%</td>
<td>{{ "%.1f"|format(p.mem) }}%</td>
<td><span class="tag run">{{ p.status }}</span></td>
</tr>
{% endfor %}
</tbody>
</table>
<p class="footer">Auto-refreshes every 5 seconds · {{ hostname }}</p>
</body>
</html>
"""
LOGIN_HTML = """
<!DOCTYPE html>
<html>
<head>
<title>Dashboard Login</title>
<style>
body { background: #0a0a0a; display: flex; justify-content: center; align-items: center; height: 100vh; font-family: monospace; }
.box { background: #1a1a1a; border: 1px solid #2a2a2a; padding: 30px; border-radius: 8px; width: 280px; }
h2 { color: #00ff88; margin-bottom: 20px; }
input { width: 100%; padding: 10px; background: #0a0a0a; border: 1px solid #333; color: #fff; border-radius: 4px; margin-bottom: 12px; font-family: monospace; }
button { width: 100%; padding: 10px; background: #00ff88; color: #000; border: none; border-radius: 4px; font-weight: bold; cursor: pointer; font-family: monospace; }
.err { color: #ff4444; margin-bottom: 12px; font-size: 0.85rem; }
</style>
</head>
<body>
<div class="box">
<h2>🔐 LOGIN</h2>
{% if error %}<div class="err">Wrong password</div>{% endif %}
<form method="post">
<input type="password" name="password" placeholder="Password" autofocus>
<button type="submit">Enter</button>
</form>
</div>
</body>
</html>
"""
def fmt_bytes(n):
for unit in ['B', 'KB', 'MB', 'GB']:
if n < 1024:
return f"{n:.1f} {unit}"
n /= 1024
return f"{n:.1f} TB"
@app.route("/login", methods=["GET", "POST"])
def login():
error = False
if request.method == "POST":
if request.form.get("password") == PASSWORD:
session["auth"] = True
return redirect("/")
error = True
return render_template_string(LOGIN_HTML, error=error)
@app.route("/logout")
def logout():
session.clear()
return redirect("/login")
@app.route("/")
def dashboard():
if not session.get("auth"):
return redirect("/login")
cpu = psutil.cpu_percent(interval=1)
mem = psutil.virtual_memory()
ram_used = fmt_bytes(mem.used)
ram_total = fmt_bytes(mem.total)
ram_pct = mem.percent
disk = psutil.disk_usage("/")
disk_used = fmt_bytes(disk.used)
disk_total = fmt_bytes(disk.total)
disk_pct = disk.percent
boot_time = psutil.boot_time()
import time
uptime_secs = int(time.time() - boot_time)
uptime = str(timedelta(seconds=uptime_secs))
load = " / ".join(f"{x:.2f}" for x in psutil.getloadavg())
net = psutil.net_io_counters()
net_sent = fmt_bytes(net.bytes_sent)
net_recv = fmt_bytes(net.bytes_recv)
processes = []
for p in sorted(psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent', 'status']), key=lambda x: x.info['cpu_percent'] or 0, reverse=True)[:10]:
try:
processes.append({
"pid": p.info["pid"],
"name": p.info["name"],
"cpu": p.info["cpu_percent"] or 0,
"mem": p.info["memory_percent"] or 0,
"status": p.info["status"]
})
except Exception:
pass
hostname = subprocess.check_output("hostname", shell=True).decode().strip()
return render_template_string(
HTML,
cpu=cpu, ram_used=ram_used, ram_total=ram_total, ram_pct=ram_pct,
disk_used=disk_used, disk_total=disk_total, disk_pct=disk_pct,
uptime=uptime, load=load, net_sent=net_sent, net_recv=net_recv,
processes=processes, hostname=hostname
)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=9000, debug=False)
Run it in a new tmux window:
# Press Ctrl+b, c to open a new tmux window
cd ~/server/dashboard
python dashboard.py
Your dashboard is live at http://192.168.0.218:9000. It auto-refreshes every 5 seconds and shows real-time CPU, RAM, disk, network, and the top 10 running processes.
Step 8: Expose Your Server to the Internet via Cloudflare Tunnel
This is where it gets really exciting. Right now your server is only accessible on your local Wi-Fi. We will use Cloudflare Tunnel to give it a public URL — no port forwarding, no router config, no static IP needed.
Install cloudflared:
# Download the ARM64 binary (for Android/Termux)
cd ~
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64 -O cloudflared
chmod +x cloudflared
mv cloudflared $PREFIX/bin/cloudflared
Quick Tunnel (No Account Needed):
For instant testing:
cloudflared tunnel --url http://localhost:8000
You will get a random URL like https://random-words.trycloudflare.com. Share it with anyone. This is real internet access to your phone server.
Permanent Named Tunnel (Recommended):
For a stable URL, set up a proper named tunnel:
# Log in to Cloudflare (opens a browser link)
cloudflared login
# Create a named tunnel
cloudflared tunnel create my-phone-server
# Create the config file
mkdir -p ~/.cloudflared
nano ~/.cloudflared/config.yml
Paste this (replace with your tunnel ID and domain):
tunnel: YOUR_TUNNEL_ID_HERE
credentials-file: /data/data/com.termux/files/home/.cloudflared/YOUR_TUNNEL_ID_HERE.json
ingress:
- hostname: yoursite.yourdomain.com
service: http://localhost:8000
- hostname: dashboard.yourdomain.com
service: http://localhost:9000
- service: http_status:404
# Route your domain to the tunnel
cloudflared tunnel route dns my-phone-server yoursite.yourdomain.com
# Run the tunnel
cloudflared tunnel run my-phone-server
Your server now has a real domain. Anyone on the internet can reach it.
Step 9: Discord Bot
Let us run a Discord bot on this phone. Create a bot at discord.com/developers and grab your token.
pip install discord.py
mkdir -p ~/server/bot
nano ~/server/bot/bot.py
import discord
from discord.ext import commands
import psutil
import subprocess
from datetime import timedelta
import time
TOKEN = "YOUR_BOT_TOKEN_HERE"
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix="!", intents=intents)
def fmt_bytes(n):
for unit in ['B', 'KB', 'MB', 'GB']:
if n < 1024:
return f"{n:.1f} {unit}"
n /= 1024
return f"{n:.1f} TB"
@bot.event
async def on_ready():
print(f"Logged in as {bot.user}")
await bot.change_presence(activity=discord.Activity(
type=discord.ActivityType.watching,
name="the server 👁️"
))
@bot.command()
async def status(ctx):
"""Check server status"""
cpu = psutil.cpu_percent(interval=1)
mem = psutil.virtual_memory()
disk = psutil.disk_usage("/")
uptime_secs = int(time.time() - psutil.boot_time())
uptime = str(timedelta(seconds=uptime_secs))
embed = discord.Embed(title="📱 Phone Server Status", color=0x00ff88)
embed.add_field(name="CPU", value=f"{cpu}%", inline=True)
embed.add_field(name="RAM", value=f"{mem.percent}% ({fmt_bytes(mem.used)}/{fmt_bytes(mem.total)})", inline=True)
embed.add_field(name="Disk", value=f"{disk.percent}% ({fmt_bytes(disk.used)}/{fmt_bytes(disk.total)})", inline=True)
embed.add_field(name="Uptime", value=uptime, inline=False)
embed.set_footer(text="Running on Termux · Android")
await ctx.send(embed=embed)
@bot.command()
async def ping(ctx):
await ctx.send(f"🏓 Pong! `{round(bot.latency * 1000)}ms`")
@bot.command()
async def shell(ctx, *, cmd):
"""Run a shell command (restrict this in production!)"""
if ctx.author.id != YOUR_DISCORD_USER_ID: # Replace with your Discord user ID
return await ctx.send("❌ Unauthorized")
try:
result = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, timeout=10).decode()
await ctx.send(f"```\n{result[:1900]}\n```")
except subprocess.CalledProcessError as e:
await ctx.send(f"```\n{e.output.decode()[:1900]}\n```")
except subprocess.TimeoutExpired:
await ctx.send("⏱️ Command timed out")
bot.run(TOKEN)
Run in a new tmux window:
cd ~/server/bot
python bot.py
Your bot is now online. !status will show a live embed with your phone’s stats right in Discord. The !shell command lets you run commands on your server directly from Discord — extremely useful for quick fixes.
Step 10: Firewall
We need to protect the server from random internet traffic hitting internal ports it should not access.
pkg install ufw -y
# Deny everything by default
ufw default deny incoming
ufw default allow outgoing
# Allow only what we need
ufw allow 8022 # SSH
ufw allow 8000 # Website
ufw allow 9000 # Dashboard (consider restricting this)
# Enable the firewall
ufw enable
# Check status
ufw status verbose
For extra security, restrict dashboard access to your IP only:
ufw allow from 192.168.0.0/24 to any port 9000
This allows dashboard access only from devices on your local network, not from the open internet.
Step 11: GitHub Auto-Update System
Every time you push code to GitHub, your server should automatically pull and restart. This turns your phone into a proper CI/CD-connected server.
mkdir -p ~/server/scripts
nano ~/server/scripts/auto_update.sh
#!/data/data/com.termux/files/usr/bin/bash
# Configuration
REPO_DIR="$HOME/server/website" # Change to your repo path
BRANCH="main"
LOGFILE="$HOME/server/scripts/update.log"
cd "$REPO_DIR" || exit 1
# Check if there are remote changes
git fetch origin "$BRANCH" >> "$LOGFILE" 2>&1
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse "origin/$BRANCH")
if [ "$LOCAL" != "$REMOTE" ]; then
echo "[$(date)] Update found: $REMOTE" >> "$LOGFILE"
git pull origin "$BRANCH" >> "$LOGFILE" 2>&1
# Restart your service here — adjust as needed
# Example: kill Flask and restart it
pkill -f "python app.py" 2>/dev/null
sleep 1
nohup python app.py >> "$LOGFILE" 2>&1 &
echo "[$(date)] Restart done." >> "$LOGFILE"
else
echo "[$(date)] No update." >> "$LOGFILE"
fi
Make it executable:
chmod +x ~/server/scripts/auto_update.sh
Now schedule it with cron to run every 5 minutes:
# Install cron
pkg install cronie -y
# Start cron daemon
crond
# Edit crontab
crontab -e
Add this line:
*/5 * * * * /data/data/com.termux/files/home/server/scripts/auto_update.sh
Now every 5 minutes, your server checks GitHub. If there is a new commit, it pulls and restarts automatically. Push code, wait 5 minutes, it is live.
To make cron start on boot, add it to your boot script:
nano ~/.termux/boot/start-ssh.sh
#!/data/data/com.termux/files/usr/bin/sh
sshd
crond
tmux new-session -d -s main
Step 12: Keep Everything Organized with a Master Startup Script
Instead of manually starting everything, one script starts all services in organized tmux windows.
nano ~/server/scripts/start_all.sh
#!/data/data/com.termux/files/usr/bin/bash
SESSION="server"
# Kill existing session if any
tmux kill-session -t $SESSION 2>/dev/null
# Start fresh session
tmux new-session -d -s $SESSION -n "ssh"
# Window 1: Website
tmux new-window -t $SESSION -n "website"
tmux send-keys -t $SESSION:website "cd ~/server/website && python -m http.server 8000" Enter
# Window 2: Dashboard
tmux new-window -t $SESSION -n "dashboard"
tmux send-keys -t $SESSION:dashboard "cd ~/server/dashboard && python dashboard.py" Enter
# Window 3: Discord Bot
tmux new-window -t $SESSION -n "bot"
tmux send-keys -t $SESSION:bot "cd ~/server/bot && python bot.py" Enter
# Window 4: Cloudflare Tunnel
tmux new-window -t $SESSION -n "tunnel"
tmux send-keys -t $SESSION:tunnel "cloudflared tunnel run my-phone-server" Enter
# Window 5: Logs
tmux new-window -t $SESSION -n "logs"
tmux send-keys -t $SESSION:logs "tail -f ~/server/scripts/update.log" Enter
echo "All services started. Run: tmux attach -t server"
chmod +x ~/server/scripts/start_all.sh
./~/server/scripts/start_all.sh
Now tmux attach -t server shows you every service in a separate named window. Switch between them with Ctrl + b, then the window name or number.
Keeping Phone 1 Alive 24/7
A few things you need to configure on Phone 1 to prevent Android from killing Termux:
- Disable battery optimization for Termux: Go to Settings → Battery → Battery Optimization → Find Termux → Set to “Don’t optimize”
- Keep the screen on (or set it to never sleep) — some phones aggressively kill background processes when the screen turns off
- Acquire a wake lock in Termux: In Termux, pull down the notification and tap “Acquire Wakelock”. This prevents the CPU from sleeping
- Keep it plugged in — this is a server, it should be on the charger. Use a good quality charger and cable to avoid battery wear
- Disable Wi-Fi sleep: Settings → Wi-Fi → Advanced → Keep Wi-Fi on during sleep → Always
What I Ended Up With
After all of this, Phone 1 is running:
- A Python website on port 8000, accessible on local Wi-Fi (and theoretically via Cloudflare with a domain, though I didn’t fully test the domain part)
- A live server dashboard on port 9000 with real-time CPU, RAM, disk and process stats
- A Discord bot that reports server health and accepts shell commands
- A UFW firewall blocking unauthorized access
- A GitHub auto-update cron job deploying new code every 5 minutes
- SSH for remote management from Phone 2
All of this, on an Android phone. Running 24/7. Costing nothing.
Performance Notes
It’s slow. It’s a phone. Manage your expectations.
For a bot, a simple Flask API, a static site — fine. I wouldn’t try to run a database or anything expecting real traffic. The phone also gets noticeably warm after running for a few hours, so keep it out of its case and somewhere it can breathe.
If you want more power, the same setup works on a newer Android phone or a cheap Android TV box.
Final Thoughts
This was a fun thing to build and I genuinely learned a lot from it. Before this I had no idea what SSH meant beyond “it’s how you connect to servers.” I didn’t know what tmux was, what a firewall actually does, or how a process stays running in the background. I know all of that now because I had to figure it out on a phone with no safety net.
Is it a real server? No. Will it randomly die at 3am? Probably yes. Is the Cloudflare domain stuff guaranteed to work exactly like I described? Honestly I’m not sure, try it and see.
But if you’re in the same position I was — curious about servers, no money to spend, just want to understand how any of this works — this is a legitimate way to start. My mom’s old phone taught me more than any tutorial I could have just read.
If something breaks or you get stuck on a specific step, drop a comment. I’ll try to help if I can.
If this was useful, share it with someone who’s curious but thinks they need a PC or a paid server to get started. You don’t.