
Termux 里跑起来的 sshd、定时任务、自己写的 Python 小服务只要手机重启一次全部得手动再拉一遍——这个痛点几乎所有把 Termux 当轻量服务器用的人都撞过。我前后在三四台设备上折腾过 Termux 服务自启动从最早在 bashrc 里塞一堆 if 判断的土办法到后来把 Termux:Boot 和 termux-services 两条线彻底分开处理中间踩的坑足够写满一页笔记。这篇就聊聊怎么让 Termux 里的服务在开机后自己站起来以及为什么大部分人的第一次尝试都会失败。需要先说清楚定位这篇文章不解决Termux 能不能当服务器的问题只解决服务怎么在重启后自动恢复这一个环节。适合已经能在 Termux 里手动跑起 sshd 或者自定义脚本、但每次重启都要重新登录手动敲命令的人。如果你刚装好 Termux 还没跑通任何服务建议先把服务手动跑通再回来处理自启动否则排查问题时变量太多。1. Termux 的进程活不过一次重启这件事得先讲清楚1.1 Android 是怎么对待 Termux 这类后台终端的很多人对 Termux 的第一误解是把它当成一台手机上的 Linux 服务器。实际上 Termux 是运行在 Android 应用沙箱里的一个普通 App它没有 root也不注册系统服务所有进程都挂在 Termux 这个应用进程的生命周期下面。Android 在重启后会重建整个系统Termux 的进程自然全部消失$PREFIX/var/run里的 pid 文件、socket 文件也都跟着没了。这不是 bug是设计如此。更重要的是Android 对后台应用有一套自己的管控逻辑。系统会把长期处于后台、又不属于前台服务的应用判定为可以回收在内存紧张或者进入 Doze 模式时直接冻结甚至杀掉。这意味着哪怕你不重启手机只是锁屏放了几个小时Termux 里的服务也可能被挂起。所以服务自启动这件事在 Termux 上其实有两个层面一是重启后怎么自动拉起来二是拉起来之后怎么让它别被系统掐死。后面会分别讲。从系统层面看Termux 唯一能拿到的开机信号来自 Android 的BOOT_COMPLETED广播。普通应用想接收这个广播必须在清单文件里静态注册接收器而 Termux 主程序本身并没有做这件事——做这件事的是它的配套插件 Termux:Boot。理解了这一点后面所有方案的设计思路就顺了要么借助插件在开机广播里执行脚本要么在 Termux 内部用一个常驻的进程管理器维持服务要么两者结合。1.2 不自启动会带来哪些真实的麻烦我在最初用 Termux 跑一个简单的文件同步脚本时根本没在意自启动。当时的用法是每天晚上手动打开 Termux敲一行命令启动用完就关。直到有一次出差三天回来发现同步停了三天数据对不上才开始认真处理这个问题。服务不自启动的代价往往是隐性的你不会立刻发现它挂了等到需要用到的时候才发现某个端口连不上、某个定时任务没执行。具体来说常见的麻烦有这么几类。第一种是远程连接类服务比如 sshd重启后端口不再监听外面的连接直接被拒绝而且因为服务没起来你也没法通过它进去排查。第二种是定时任务类crond 没起来所有 cron 条目静默失效日志里连报错都没有。第三种是长驻后台任务比如一个轮询接口的小脚本、一个本地 Web 服务它挂了就是挂了没有任何提示。还有一个容易被忽略的点即使你用脚本把服务拉起来了如果启动顺序不对服务也可能因为依赖没就绪而立刻退出。比如你的脚本依赖某个目录挂载完成或者依赖网络可用在开机阶段这些条件可能还不满足。这也是为什么我不建议把所有东西都塞进一个开机脚本里一把梭而是分层处理。1.3 为什么把命令写进 bashrc是个坏主意网上能搜到的最简方案是在~/.bashrc或者~/.profile里加一段判断如果服务没跑就启动它。这个方案看起来很省事但问题很致命。首先Termux 的 shell 配置文件只在交互式登录时才会被读取也就是说你得先手动打开 Termux 一次脚本才会执行——这根本不叫自启动叫手动启动的自动化。其次每次打开新终端窗口都可能重复触发启动逻辑容易造成端口冲突和进程重复。我早期就踩过这个坑把 sshd 的启动命令写进 bashrc结果每次开新会话都尝试启动一次虽然 sshd 有端口占用保护不会真的起两个但sv之类的服务管理器会被反复调用日志里刷满了重复记录排查真正的问题时非常干扰。所以我的建议很明确shell 配置文件只用来设置环境变量和别名不要用它做进程管理。2. 先选路线Termux:Boot、termux-services、外部触发器各自适合谁2.1 Termux:Boot 做的其实是重启时跑一次脚本Termux:Boot 是一个独立的 APK需要单独安装它的职责非常单一在系统开机完成后按顺序执行~/.termux/boot/目录下的所有可执行脚本。它不负责保持服务存活也不负责崩溃重启就是开机跑一遍。正因为职责单一它反而是最可靠的一环——它挂在系统广播上不受 Termux 内部进程状态影响。这里有个关键前提Termux:Boot 安装之后必须手动打开一次应用图标让系统完成接收器的注册。这一步很多人会漏掉装完就等着重启结果开机毫无反应。原因是 Android 对于从未启动过的应用在很多定制系统上会限制其接收开机广播。手动打开一次相当于告诉系统这个应用是活的之后它才能收到BOOT_COMPLETED。它的执行环境也需要注意。脚本运行时的工作目录不是$HOMEPATH 也可能不完整所以脚本里所有命令最好写绝对路径或者开头统一export PATH$PREFIX/bin:$PATH。另外~/.termux/boot/里的脚本必须加可执行权限chmod x这一步漏掉的话文件会被静默跳过不报任何错。2.2 termux-services 管的是进程活着并持续活着termux-services 是 Termux 官方提供的一个包内部用的是 runit 这套轻量进程管理方案。它解决的问题和 Termux:Boot 完全不一样它不管你什么时候启动只管一个服务被sv-enable之后是不是一直处于运行状态如果进程意外退出它会自动重新拉起。这对那些会崩、会退出的服务来说非常关键。runit 的模型很简单每个服务在$PREFIX/var/service/下有一个自己的目录目录里放一个叫run的可执行脚本脚本负责用exec启动前台进程。runit 监控这个进程进程一退出就重新执行run。日志则交给同目录下的log/run脚本处理默认会落到$PREFIX/var/log/sv/下面。我个人的用法是把这两者组合起来Termux:Boot 负责在开机后把整体环境准备好然后调用sv up把关键服务拉起来termux-services 负责后续的存活与重启。两条线各管一段职责清晰出问题的时候也容易定位是哪一段没起作用。2.3 三条路线的对照与组合方式除了上面两条主线还有一种外部触发器思路用系统的自动化工具或者从别的设备发一条消息过来触发启动。这种方式我不太推荐作为主力因为它引入了外部依赖网络不通、对方设备关机都会导致触发失败但在调试阶段可以用来验证服务本身能不能跑起来。方案触发时机是否能保活适合场景主要短板Termux:Boot系统开机后否初始化环境、拉起脚本需手动首次打开不保活termux-services手动或脚本调用是sshd、crond、常驻进程依赖 Termux 进程存活外部触发器任意时刻否远程调试、兜底补救依赖网络和第三方选型上的经验是不要指望单一方案解决所有问题。最稳的组合是 Termux:Boot 做初始化 termux-services 做托管 系统电池优化白名单做保活三层叠起来我能做到一周不手动干预服务基本都在。3. 把 Termux:Boot 跑通从安装到脚本能被执行的每一步3.1 安装之后必须做的那一次手动打开安装 Termux:Boot 的渠道稳妥的是从官方发布的 APK 源获取注意版本要和你当前 Termux 主程序的签名来源保持一致——如果主程序和插件来自不同的签名源两者会互相不认脚本不会被执行。这一点在混装不同来源包的时候特别容易出问题我见过有人因为主程序和一个插件来自不同渠道折腾一晚上找不到原因。装完之后点开 Termux:Boot 的图标界面通常是一闪而过或者显示一个简单说明这就够了说明系统已经登记了这个应用。接下来去系统设置里找到 Termux 和 Termux:Boot 的电池管理把允许后台活动打开把省电策略设为无限制。不同厂商的定制系统叫法不一样有的叫自启动管理有的叫后台运行权限位置一般在应用详情页或者安全中心里。最后一步是验证。先手动在~/.termux/boot/下放一个最简单的脚本内容就是往一个文件里写一行时间戳。然后重启手机重启后不用打开 Termux直接用文件管理器或者下次打开 Termux 后查看那个文件有没有更新。这一步能确认整条链路是通的之后再往里加真正的服务启动逻辑。3.2 ~/.termux/boot/ 目录下的脚本规范脚本执行顺序是按文件名的字典序来的所以如果你有多个脚本且存在依赖关系建议用00-、10-、20-这样的前缀编号让执行顺序一目了然。我习惯把环境准备类的放前面网络等待类的放中间服务启动类的放后面这样出问题时按顺序看日志就能定位到哪一段断了。一个典型的启动脚本大概长这样注意每一行的意图#!/data/data/com.termux/files/usr/bin/bash # 00-prestart.sh开机后先准备环境不启动任何服务 export PATH/data/data/com.termux/files/usr/bin:/data/data/com.termux/files/usr/bin/applets:$PATH export HOME/data/data/com.termux/files/home cd $HOME || exit 1 # 申请唤醒锁避免刚起来就被系统挂起 termux-wake-lock # 等网络可用最多等 60 秒 for i in $(seq 1 60); do if ping -c 1 -W 1 1.1.1.1 /dev/null 21; then break fi sleep 1 done # 记录时间戳方便确认脚本确实执行过 date $HOME/boot.log关于 shebang直接写#!/usr/bin/env bash在开机环境下有可能失败因为那个时刻 PATH 还没配好。写完整的$PREFIX/bin/bash绝对路径是最保险的。同理脚本里所有外部命令要么写绝对路径要么在最前面把 PATH 导出完整。权限方面chmod x是必须的。另外还要注意脚本所在目录是~/.termux/boot/不是~/.termux/新建目录的时候别建错层级。文件名不要带后缀其实也行但为了可读性我一般保留.sh。3.3 唤醒锁、重定向与脚本执行了但你看不到的问题termux-wake-lock这个命令值得单独说。它由 Termux 主程序提供作用是申请一个唤醒锁让 CPU 在锁屏状态下不完全休眠。对于 Termux:Boot 脚本来说它几乎是必需品开机后系统很快会进入低功耗状态如果此时你的脚本还在等待网络或者启动服务很可能被直接挂起表现就是服务有时候起来有时候不起来非常随机。另一个常见问题是脚本执行了但没有输出所以你看不到任何反馈。原因很简单开机脚本的标准输出没有连接到任何终端全都丢掉了。解决办法是在脚本里显式重定向。我通常这么处理exec $HOME/boot.log 21放在脚本开头这样后面所有的输出和报错都会追加到日志文件里。排查的时候只需要tail -f ~/boot.log非常直观。要注意这个日志文件会一直增长长期运行的话加个简单的轮转或者在脚本里定时清空。还有一类问题是脚本前半段跑通了后半段没跑。这通常是因为脚本中途遇到了返回非零的命令如果脚本开头写了set -e后面所有内容都会直接终止。开机脚本里我建议不要用set -e而是对关键步骤手动判断返回值并记录保证一个环节失败不会拖垮整个流程。4. 用 termux-services 托管常驻服务sshd、crond 和自定义进程4.1 安装与初始化为什么要重启一次 Termux安装很简单一条命令pkg install termux-services装完之后要做的是完全退出 Termux 再重新打开。注意完全退出指的是从最近任务列表里划掉或者用exit退出所有会话而不是简单地切到后台。原因是 termux-services 需要启动一个常驻的service-daemon进程这个进程由 shell 登录时的启动钩子拉起只有重新进入 Termux 才会触发。很多人装完之后直接敲sv命令发现报错八成就是没重启 Termux。验证 daemon 是否在跑可以用ps aux | grep service-daemon看一下或者直接sv status看有没有输出。如果 daemon 没起来sv命令要么提示找不到服务目录要么干脆无响应。这个前置条件必须确认否则后面所有 sv 操作都是白费力气。还有一点需要明白termux-services 的进程本身也活在 Termux 应用进程里。它能在 Termux 被系统杀掉之后自己复活吗不能。所以它必须配合 Termux:Boot 使用由开机脚本在环境准备好之后重新把整个服务体系拉起来。这是两层结构缺一不可。4.2 sv 命令族与 sv-enable 的实际差别sv系列的用法看着多其实记住几个就够命令作用使用频率sv-enable 服务名创建启用标记让 daemon 自动拉起高sv-disable 服务名取消启用标记中sv up 服务名立即启动一次高sv down 服务名立即停止中sv restart 服务名重启中sv status 服务名查看状态和运行时长高这里的关键差别在sv-enable和sv up上。sv up只是当下启动一次重启或者 daemon 重启之后不会自动恢复sv-enable是在服务目录下建立一个启用标记daemon 每次启动时会去扫描这些标记把带标记的服务拉起来。所以如果你希望服务在每次开机后自动运行必须用sv-enable只做sv up是不够的。实际操作里我会先sv up验证服务能正常起来确认端口监听和日志都没问题之后再执行sv-enable把它固化下来。这个顺序能避免服务本身有问题却被自动反复重启日志被刷爆。4.3 自己写一个 run 脚本结构、退出码与重启策略如果 termux-services 里没有现成的服务定义比如你要托管一个自己写的 Python 脚本就得手动建服务目录mkdir -p $PREFIX/var/service/mypy cd $PREFIX/var/service/mypy然后创建run文件内容大致如下#!/data/data/com.termux/files/usr/bin/sh exec 21 exec /data/data/com.termux/files/usr/bin/python \ /data/data/com.termux/files/home/apps/mypy/main.py关键点是最后那个exec。runit 要求run脚本把服务进程替换成前台进程也就是说脚本执行完之后进程要变成你的服务本身而不是脚本的子进程。如果写成不 exec 的形式脚本跑完就退出了runit 会认为服务结束然后不停地重新执行表现就是服务反复重启日志里全是重复的启动记录。exec 21那一行是把标准错误合并到标准输出方便日志被统一收集。日志目录的默认位置是$PREFIX/var/log/sv/mypy/日志工资会自己创建。如果你需要自定义日志处理可以在服务目录下再建一个log/run。关于重启策略runit 的行为是进程退出后立即重新执行 run 脚本中间有很短的间隔。如果你的服务因为配置错误一直在秒退就会形成快速重启循环日志飞速膨胀。所以第一次托管新服务时我建议先不加sv-enable只用sv up观察几分钟的日志确认稳定了再固化。5. 服务没起来时我一般按这个顺序往下查5.1 第一步永远是确认脚本有没有被执行服务没起来的时候不要急着去改服务本身的配置先确认最外层——开机脚本到底跑了没有。方法就是在脚本开头加一行写时间戳的命令重启后看这个时间戳是不是最新的。如果没更新问题在 Termux:Boot 这一层跟你的服务代码没有任何关系。这一层的排查顺序是确认应用是否手动打开过、确认后台运行权限是否放开、确认脚本是否有可执行权限、确认脚本所在目录是否正确。这四点覆盖了我遇到的绝大多数完全没反应的情况。其中最容易忽略的是第二点尤其在国内定制系统上默认的后台限制非常严格不手动放开的话开机广播根本送不到应用。另外可以留意一下系统日志里是否有相关记录。部分设备可以通过开发者选项或者系统日志工具看到广播的分发情况如果能看到 Termux:Boot 收到广播但没有执行脚本那问题就锁定在脚本本身而不是权限上。5.2 进程起来了又消失Doze、电池优化与内存回收这个现象非常典型重启后你打开 Termux看到服务在跑锁屏放一晚上第二天早上服务没了。这不是自启动失败而是保活失败。Android 的 Doze 模式会在设备静止且屏幕关闭一段时间后限制后台应用的网络和 CPU 活动普通的后台进程会被冻结。冻结时间长了系统可能直接回收。应对办法有几步。首要的是把 Termux 加入电池优化白名单路径一般在系统设置的电池或者应用管理里名称类似不受限制允许后台活动。其次是在服务运行期间保持唤醒锁也就是让脚本在启动服务之前先调用termux-wake-lock。唤醒锁的代价是耗电增加但对于需要长期在线的小服务来说这个代价值得。需要说明的是即使做了这些也不能保证 100% 不被回收。不同厂商的系统策略差异很大有的设备对后台进程特别激进。我的经验是如果服务本身允许中断就接受偶尔被回收的现实配合一个外部的健康检查机制定期确认如果服务绝对不能中断那手机本身就不适合作为承载设备应该换到更稳定的环境。5.3 端口冲突、重复拉起与残留进程还有一个隐藏问题服务在重启前的旧进程并没有真正消失时新启动的进程会因为端口被占而失败。虽然在设备重启的场合下这种情况很少但在手动重启 Termux 应用或者从后台恢复的场景下就会出现因为那时旧进程可能还挂在系统里。排查方式是看端口监听情况ss -lntp或者netstat -lntp都可以注意观察同一端口是否有多个监听项或者是否有属于已退出进程的僵尸监听。如果是 termux-services 托管的服务还要注意不要既用了sv-enable又在开机脚本里手动调用一次启动命令两者叠加会导致同一服务的两个实例互相抢端口。我自己就犯过这个错先用 termux-services 托管了服务又在开机脚本里写了一遍启动命令结果就是其中一路永远起不来日志里一堆地址已被占用。后来统一约定凡是被 termux-services 托管的服务开机脚本里只调用sv up不直接执行服务本身的启动命令。6. 让自启动真正稳下来的一些加固手段6.1 日志落到文件别指望终端里能看到开机阶段的输出不会被任何地方接收所以日志必须主动落盘。我的做法是在两个层面都做重定向Termux:Boot 的开机脚本里统一把输出追加到~/boot.logtermux-services 托管的服务则由 runit 自动收集到$PREFIX/var/log/sv/下。这两份日志的职责不同前者记录开机流程走到哪一步后者记录服务运行状态和崩溃原因。查看日志的时候有个小技巧runit 的日志目录下通常会有current这个文件它是当前正在写入的日志直接tail -f它比看轮转后的文件更及时。如果服务反复重启current里会密集出现重复的启动信息一眼就能看出来是快速重启循环。日志长期不清会撑爆存储尤其是那些启动失败后疯狂重试的服务。我一般在服务稳定之后加一条 cron 定时清理或者干脆在开机脚本里做一次简单的日志截断保留最近一定行数。6.2 延迟启动与依赖等待开机阶段最容易出的问题是启动太快网络还没就绪、存储还没挂载完、系统还在忙着跑其他初始化任务这时候拉起的服务可能拿不到需要的资源就直接失败了。解决办法是在脚本里加入等待逻辑而不是写一个固定长度的sleep。固定 sleep 的问题是两头不讨好设短了不够设长了拖慢整体启动。我更倾向于写轮询等待比如前面那段等待网络连通的循环最多等 60 秒一旦通了就立刻继续。对于依赖本地文件的场景就是轮询判断文件是否存在对于依赖某个端口的场景就是循环尝试连接。这种写法比盲等要可靠得多而且失败的时候日志里能明确记录等了多久还是没等到方便判断是资源问题还是超时设置不合理。对于服务之间的依赖比如 A 服务必须先起、B 服务才能起可以在 B 的服务定义里加一个前置检查或者在开机脚本里先确认 A 的状态再拉起 B。runit 本身对依赖的处理比较弱复杂场景下我倾向于在脚本层面用手动判断来解决。6.3 路径、环境变量和存储权限的坑Termux 的路径结构和普通 Linux 差别很大$PREFIX指向的是应用私有目录$HOME也是一个应用内的路径。这些路径在开机脚本里如果写成硬编码一旦遇到设备变更或者应用重装就可能失效。所以脚本里我尽量用变量并且在脚本开头把PREFIX、HOME、PATH都显式导出一次。涉及外部存储的服务还要特别注意权限。较新的 Android 版本对应用访问外部存储有严格限制Termux 需要单独申请存储权限而这个权限申请是交互式的重启之后不会自动恢复。如果你的服务依赖外部存储目录里的文件最好的做法是把数据放在应用私有目录里避开这个权限问题。实在需要外部存储的话要提前在应用里执行一次权限授予操作并确认开机后权限仍然有效。还有一个和路径相关的小坑部分脚本在交互式 shell 里能跑通放到开机脚本里就报命令找不到原因往往就是 PATH 不同。解决办法前面已经提过统一导出 PATH 或者写绝对路径。这个坑我踩过不止一次现在养成了习惯——写开机脚本时把所有外部命令的路径都先用which查清楚然后写死在脚本里。6.4 Proot 容器内的服务要不要单独处理顺带聊一个常被问到的问题如果服务是跑在 proot 环境里的发行版里面自启动该怎么做。答案是容器内部不需要、也没办法自己处理开机启动因为 proot 只是一个用户态的路径转换层里面没有 init 系统容器里的进程本质上还是宿主机 Termux 进程的子进程。所以正确的做法是由宿主机的开机脚本负责把容器环境拉起来再在里面执行启动命令。我的处理方式是在开机脚本里写一段类似这样的逻辑先启动 proot 容器并进入执行容器内的服务启动命令然后保持这个进程前台运行交给 termux-services 托管。如果要让容器内的服务也能崩溃重启就需要在外层用 termux-services 包一层而不是在容器内部搞一套进程管理那样在 proot 环境下是跑不起来的。6.5 一次完整的验证流程改完配置之后我不建议直接重启设备验证太慢了。更高效的方式是分三段验证。第一段在 Termux 里手动执行一次开机脚本看服务能不能正常起来这验证脚本逻辑本身是否正确。第二段手动停掉服务再重启 Termux 应用看 termux-services 的 daemon 有没有按预期把服务拉起来这验证托管配置。第三段才是真正重启设备验证 Termux:Boot 这条链路。这三段分开验证的好处是每一步失败时你都知道是哪一层的问题不用在重启的漫长等待里反复猜测。我现在的习惯是每次调整配置后都走一遍这三段虽然多花几分钟但省下来的排查时间远不止这点。顺便说个我自己一直在用的小技巧让开机脚本每次执行时都写一行标记内容包括执行时间和当时的服务状态摘要。这样积累一段时间之后我只要看一眼这个日志就能知道过去几天里设备重启了几次、每次服务是不是都正常起来了。这个习惯帮我发现过一次隐藏的问题——某段时间服务其实每天都会重启一次只是我平时不打开 Termux 所以没注意到日志一看就露馅了。