1) Prerequisites (run once)
- Install Termux from F‑Droid.
- Install Termux:Boot from F‑Droid and open it once.
- Install the companion app Termux:API from F‑Droid (enables termux-wake-lock).
- On first run, grant Termux storage access when prompted.
For stability, disable battery optimization for Termux in Android settings. A wake‑lock is used to reduce background kills.
2) One‑shot Setup Script (copy and run in Termux)
This will: update/upgrade, install PHP, set up storage, create supervised services (runit) for auto‑restart, add helper scripts, enable boot autostart, and start everything on port 8090.
#!/data/data/com.termux/files/usr/bin/bash
set -euo pipefail
SERVER_PATH="/storage/shared"
PORT="8090"
ENABLE_CLOUDFLARED="1" # 1: also enable internet access via Cloudflare; 0: LAN/localhost only
echo "[1/9] Updating Termux packages..."
pkg update -y && pkg upgrade -y
echo "[2/9] Installing core packages..."
pkg install -y php termux-api termux-services coreutils procps openssl-tool curl wget
echo "[3/9] Enabling shared storage (grant permission if prompted)..."
termux-setup-storage || true
echo "[4/9] Preparing directories..."
PREFIX_DIR="$PREFIX"
mkdir -p "$HOME/bin" "$HOME/.termux/boot" "$HOME/.logs" "$PREFIX_DIR/var/service"
echo "[5/9] Creating runit service for PHP server..."
# Service: php-8090
mkdir -p "$PREFIX_DIR/var/service/php-${PORT}/log"
cat >"$PREFIX_DIR/var/service/php-${PORT}/run" <<'EOF'
#!/data/data/com.termux/files/usr/bin/sh
export SERVER_PATH="/storage/shared"
export PORT="8090"
exec 2>&1
cd "$SERVER_PATH" || exit 1
exec php -S 0.0.0.0:"$PORT" -t "$SERVER_PATH"
EOF
chmod +x "$PREFIX_DIR/var/service/php-${PORT}/run"
cat >"$PREFIX_DIR/var/service/php-${PORT}/log/run" <<'EOF'
#!/data/data/com.termux/files/usr/bin/sh
# Log with timestamps to ~/.logs/php-PORT
LOGDIR="$HOME/.logs/php-8090"
mkdir -p "$LOGDIR"
exec svlogd -tt "$LOGDIR"
EOF
chmod +x "$PREFIX_DIR/var/service/php-${PORT}/log/run"
echo "[6/9] Creating helper scripts..."
cat >"$HOME/bin/start-php-server.sh" <<EOF
#!/data/data/com.termux/files/usr/bin/bash
set -euo pipefail
SERVER_PATH="$SERVER_PATH"
PORT="$PORT"
LOG="\$HOME/.php-server.log"
PIDFILE="\$HOME/.php-server.pid"
# Keep CPU awake
command -v termux-wake-lock >/dev/null 2>&1 && termux-wake-lock || true
if command -v sv >/dev/null 2>&1 && [ -d "$PREFIX_DIR/var/service/php-\${PORT}" ]; then
sv up "php-\${PORT}" || true
else
# Fallback: unmanaged background
if [ -f "\$PIDFILE" ] && kill -0 "\$(cat "\$PIDFILE")" 2>/dev/null; then
kill "\$(cat "\$PIDFILE")" || true
sleep 1
fi
pkill -f "php -S 0.0.0.0:\${PORT}" 2>/dev/null || true
cd "\$SERVER_PATH"
nohup php -S 0.0.0.0:"\$PORT" -t "\$SERVER_PATH" >> "\$LOG" 2>&1 &
echo \$! > "\$PIDFILE"
fi
IP=\$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++){if(\$i=="src"){print \$(i+1); exit}}}')
: "\${IP:=127.0.0.1}"
{
echo "Local: http://127.0.0.1:\${PORT}"
echo "LAN: http://\${IP}:\${PORT}"
} | tee "\$HOME/.php-server.urls"
echo "PHP server is up."
EOF
chmod +x "$HOME/bin/start-php-server.sh"
cat >"$HOME/bin/stop-php-server.sh" <<EOF
#!/data/data/com.termux/files/usr/bin/bash
set -euo pipefail
PORT="$PORT"
PIDFILE="\$HOME/.php-server.pid"
if command -v sv >/dev/null 2>&1 && [ -d "$PREFIX_DIR/var/service/php-\${PORT}" ]; then
sv down "php-\${PORT}" || true
else
if [ -f "\$PIDFILE" ] && kill -0 "\$(cat "\$PIDFILE")" 2>/dev/null; then
kill "\$(cat "\$PIDFILE")" || true
rm -f "\$PIDFILE"
fi
pkill -f "php -S 0.0.0.0:\${PORT}" 2>/dev/null || true
fi
echo "PHP server stopped."
EOF
chmod +x "$HOME/bin/stop-php-server.sh"
# Optional Cloudflare tunnel service and helpers
if [ "$ENABLE_CLOUDFLARED" = "1" ]; then
echo "[7/9] Installing Cloudflared (optional for public URL)..."
if ! pkg install -y cloudflared; then
echo "Cloudflared package not available; internet tunnel will be disabled."
ENABLE_CLOUDFLARED="0"
fi
fi
if [ "$ENABLE_CLOUDFLARED" = "1" ] && command -v cloudflared >/dev/null 2>&1; then
echo "Creating runit service for Cloudflared..."
mkdir -p "$PREFIX_DIR/var/service/cf-${PORT}/log"
cat >"$PREFIX_DIR/var/service/cf-${PORT}/run" <<'EOF'
#!/data/data/com.termux/files/usr/bin/sh
PORT="8090"
exec 2>&1
exec cloudflared tunnel --no-autoupdate --url "http://127.0.0.1:${PORT}"
EOF
chmod +x "$PREFIX_DIR/var/service/cf-${PORT}/run"
cat >"$PREFIX_DIR/var/service/cf-${PORT}/log/run" <<'EOF'
#!/data/data/com.termux/files/usr/bin/sh
LOGDIR="$HOME/.logs/cf-8090"
mkdir -p "$LOGDIR"
exec svlogd -tt "$LOGDIR"
EOF
chmod +x "$PREFIX_DIR/var/service/cf-${PORT}/log/run"
fi
cat >"$HOME/bin/start-cloudflared.sh" <<EOF
#!/data/data/com.termux/files/usr/bin/bash
set -euo pipefail
PORT="$PORT"
LOG="\$HOME/.cloudflared.log"
PIDFILE="\$HOME/.cloudflared.pid"
if ! command -v cloudflared >/dev/null 2>&1; then
echo "cloudflared is not installed."
exit 0
fi
if command -v sv >/dev/null 2>&1 && [ -d "$PREFIX_DIR/var/service/cf-\${PORT}" ]; then
sv up "cf-\${PORT}" || true
sleep 3
# Try to extract latest URL from runit log
LATEST_LOG=\$(ls -1t "\$HOME/.logs/cf-\${PORT}/" 2>/dev/null | head -n1 || true)
if [ -n "\$LATEST_LOG" ]; then
PUB=\$(grep -aoE 'https://[a-zA-Z0-9.-]+trycloudflare\.com' "\$HOME/.logs/cf-\${PORT}/\$LATEST_LOG" | tail -n1 || true)
fi
else
# Fallback unmanaged background
if [ -f "\$PIDFILE" ] && kill -0 "\$(cat "\$PIDFILE")" 2>/dev/null; then
kill "\$(cat "\$PIDFILE")" || true
sleep 1
fi
pkill -f "cloudflared tunnel --url http://127.0.0.1:\${PORT}" 2>/dev/null || true
nohup cloudflared tunnel --no-autoupdate --url "http://127.0.0.1:\${PORT}" >> "\$LOG" 2>&1 &
echo \$! > "\$PIDFILE"
sleep 3
PUB=\$(grep -oE 'https://[a-zA-Z0-9.-]+trycloudflare\.com' "\$LOG" | tail -n1 || true)
fi
if [ -n "\${PUB:-}" ]; then
echo "Public: \${PUB}" | tee -a "\$HOME/.php-server.urls"
echo "Cloudflared public URL: \${PUB}"
else
echo "Cloudflared started. Check logs for the public URL."
fi
EOF
chmod +x "$HOME/bin/start-cloudflared.sh"
cat >"$HOME/bin/stop-cloudflared.sh" <<EOF
#!/data/data/com.termux/files/usr/bin/bash
set -euo pipefail
PORT="$PORT"
PIDFILE="\$HOME/.cloudflared.pid"
if command -v sv >/dev/null 2>&1 && [ -d "$PREFIX_DIR/var/service/cf-\${PORT}" ]; then
sv down "cf-\${PORT}" || true
else
if [ -f "\$PIDFILE" ] && kill -0 "\$(cat "\$PIDFILE")" 2>/dev/null; then
kill "\$(cat "\$PIDFILE")" || true
rm -f "\$PIDFILE"
fi
pkill -f "cloudflared tunnel --url http://127.0.0.1:\${PORT}" 2>/dev/null || true
fi
echo "Cloudflared stopped."
EOF
chmod +x "$HOME/bin/stop-cloudflared.sh"
echo "[8/9] Creating boot autostart script (requires Termux:Boot)..."
cat >"$HOME/.termux/boot/00-start-php-at-boot.sh" <<EOF
#!/data/data/com.termux/files/usr/bin/bash
# Runs on device boot (after unlock) via Termux:Boot
export PATH="\$HOME/bin:\$PATH"
ENABLE_CLOUDFLARED="$ENABLE_CLOUDFLARED"
# Keep CPU awake to reduce background kills
command -v termux-wake-lock >/dev/null 2>&1 && termux-wake-lock || true
if command -v sv >/dev/null 2>&1; then
sv up "php-$PORT" || true
if [ "\$ENABLE_CLOUDFLARED" = "1" ] && command -v cloudflared >/dev/null 2>&1 && [ -d "$PREFIX_DIR/var/service/cf-$PORT" ]; then
sv up "cf-$PORT" || true
fi
else
"\$HOME/bin/start-php-server.sh"
if [ "\$ENABLE_CLOUDFLARED" = "1" ] && command -v cloudflared >/dev/null 2>&1; then
"\$HOME/bin/start-cloudflared.sh"
fi
fi
EOF
chmod +x "$HOME/.termux/boot/00-start-php-at-boot.sh"
echo "[9/9] Starting services now..."
"$HOME/bin/start-php-server.sh"
if [ "${ENABLE_CLOUDFLARED}" = "1" ] && command -v cloudflared >/dev/null 2>&1; then
"$HOME/bin/start-cloudflared.sh" || true
fi
LAN_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++){if($i=="src"){print $(i+1); exit}}}')
echo
echo "All set!"
echo "Localhost: http://127.0.0.1:${PORT}"
echo "On LAN: http://${LAN_IP:-<your_LAN_IP>}:${PORT}"
echo "Public: See ~/.logs/cf-${PORT}/ or ~/.cloudflared.log (if enabled) for the trycloudflare.com URL."
echo "Autostart: ~/.termux/boot/00-start-php-at-boot.sh (Termux:Boot required)."
echo "Root path: ${SERVER_PATH} (directory is listed if no index.php/index.html)."
Server Root: /storage/shared
Port: 8090
3) Quick controls
$HOME/bin/start-php-server.sh
# Optional: public URL (https://*.trycloudflare.com)
$HOME/bin/start-cloudflared.sh
$HOME/bin/stop-php-server.sh
$HOME/bin/stop-cloudflared.sh
4) Status and logs
# Show saved URLs
cat ~/.php-server.urls 2>/dev/null || echo "No URL file yet."
# Tail PHP logs (runit)
tail -f ~/.logs/php-8090/current
# Tail Cloudflared logs (if enabled and supervised)
tail -f ~/.logs/cf-8090/current
# Fallback unmanaged logs
tail -f ~/.php-server.log ~/.cloudflared.log 2>/dev/null
# Check processes
ps -A | grep -E 'php -S|cloudflared' | grep -v grep
Directory listing is automatic if there is no index.php or index.html in a folder, including the server root.
5) Change port or path
Defaults: /storage/shared, port 8090. To change:
- Edit variables at top of the setup script and re‑run; or
- Update:
- $PREFIX/var/service/php-8090/run
- $HOME/bin/start-php-server.sh and stop-php-server.sh
- If using Cloudflared: $PREFIX/var/service/cf-8090/run and $HOME/bin/start-cloudflared.sh
6) Uninstall / disable
# Stop services
$HOME/bin/stop-cloudflared.sh 2>/dev/null || true
$HOME/bin/stop-php-server.sh 2>/dev/null || true
# Remove runit services
rm -rf "$PREFIX/var/service/php-8090" "$PREFIX/var/service/cf-8090"
# Remove boot autostart
rm -f "$HOME/.termux/boot/00-start-php-at-boot.sh"
# Remove helper scripts
rm -f "$HOME/bin/start-php-server.sh" "$HOME/bin/stop-php-server.sh" \
"$HOME/bin/start-cloudflared.sh" "$HOME/bin/stop-cloudflared.sh"
echo "Removed services and scripts. Packages remain installed."
Tips
- LAN access: open the LAN URL from devices on the same Wi‑Fi.
- Internet access: Cloudflared provides an HTTPS URL without port forwarding.
- If Android still kills background apps aggressively: keep Termux in the “Not optimized” battery list, allow background activity, and keep a small charging session when serving long‑term.