家里的路由器跑着 OpenWrt,上面装了 Passwall 做流量管理。用着还不错,就是偶尔会挂掉——不崩溃、不报错,就是静悄悄地停止工作,要手动重启服务才能恢复。
解决方法很简单,加一个 cron 任务,每五分钟检查一次,挂了就自动重启:
*/5 * * * * curl -s --max-time 5 https://www.google.com || /etc/init.d/passwall restart
加完,测了一下,正常。路由器重启,再看 crontab——空了。
第一反应:overlay 文件系统的问题
OpenWrt 用的是 overlay 文件系统,系统分区只读,改动写在另一层上。理论上 crontab 的修改应该能持久化,但我第一反应还是怀疑这里出了问题。
检查了一遍,overlay 空间充足,写入没有问题。排除。
又怀疑是 cron 服务本身的问题,重启顺序不对,导致任务没保存。反复测试,也不是。
折腾了一圈,发现每次 crontab 消失的时间点都在 Passwall 服务启动之后。
原来是它自己删的
翻了一下 Passwall 的源码,找到了这个函数:
clean_crontab() {
sed -i "/etc\/init\.d\/passwall/d" /etc/crontabs/root
}
每次 Passwall 启动,它都会扫描 crontab,把所有包含 /etc/init.d/passwall 的行全部删掉。
我加的那行命令里恰好有 /etc/init.d/passwall restart——完美匹配,每次都被清理得干干净净。
Passwall 这么设计,本意是清理它自己安装时写入的定时任务,防止重复。逻辑上没问题,但顺手把我的任务也删了。
绕过去的方法很简单
把命令放进一个独立的脚本文件:
#!/bin/sh
curl -s --max-time 5 https://www.google.com || /etc/init.d/passwall restart
保存为 /usr/bin/check_passwall.sh,给执行权限。然后 crontab 里只写:
*/5 * * * * /usr/bin/check_passwall.sh
这一行不含 /etc/init.d/passwall,Passwall 的清理函数匹配不到,任务就留下来了。
问题解决。整个过程花了大概两个小时。
想到的一件事
事后想了想,这件事有点意思。
我写了一行命令,目的是在 Passwall 出问题的时候重启它。Passwall 每次启动,都会把这行命令删掉。不是因为它知道我要干什么,只是因为这行命令里有它认识的关键词。
最后的解法,是把真正的意图藏进一个脚本里,让 crontab 里的那行看起来跟 Passwall 没关系。
某种意义上,这是在和一个程序玩文字游戏。它按规则行事,我改变了表达方式,规则就不再适用了。
技术问题很多时候就是这样——不是硬碰硬,是找到那个关键词,然后绕开它。
My home router runs OpenWrt, with Passwall installed for traffic management. It mostly works well, except for one habit: occasionally it would go silent — no crash, no error message, just quietly stop doing its job. A manual service restart always fixed it.
The solution seemed obvious: a cron job, checking every five minutes, restarting if something’s wrong.
*/5 * * * * curl -s --max-time 5 https://www.google.com || /etc/init.d/passwall restart
Added it, tested it, worked fine. Rebooted the router, checked crontab again — gone.
First assumption: the overlay filesystem
OpenWrt uses an overlay filesystem where the base system is read-only and changes are written to a separate layer. My first instinct was that something was going wrong with persistence at that level.
I checked: overlay space was fine, writes were working. Not that.
I suspected the cron service itself — maybe the startup order was off and the task wasn’t saving properly. Tested that too. Also not it.
After going in circles, I noticed that the crontab entries were disappearing specifically after the Passwall service started.
It was deleting them itself
I dug into Passwall’s source code and found this function:
clean_crontab() {
sed -i "/etc\/init\.d\/passwall/d" /etc/crontabs/root
}
Every time Passwall starts, it scans crontab and deletes every line containing /etc/init.d/passwall.
My entry contained /etc/init.d/passwall restart — a perfect match. Cleaned up every single time.
Passwall does this intentionally, to remove its own scheduled tasks from previous runs and avoid duplicates. Reasonable design. It just happened to take mine with it.
The fix was straightforward
Move the command into a standalone script:
#!/bin/sh
curl -s --max-time 5 https://www.google.com || /etc/init.d/passwall restart
Save it as /usr/bin/check_passwall.sh, make it executable. Then the crontab entry becomes:
*/5 * * * * /usr/bin/check_passwall.sh
This line contains no mention of /etc/init.d/passwall. Passwall’s cleanup function doesn’t recognize it. The job survives.
Problem solved. The whole thing took about two hours.
A small observation
Thinking about it afterward, there’s something slightly funny about what happened.
I wrote a command whose purpose was to restart Passwall when it failed. Every time Passwall started up, it deleted that command — not because it understood what I was trying to do, but because the line contained a string it had been told to look for.
The fix was to hide the actual intent inside a script, so that the crontab entry no longer looked like anything Passwall would recognize.
In a way, I was playing a word game with a program. It follows its rules literally. I changed how I expressed the same thing, and the rules stopped applying.
A lot of technical problems work this way — not brute force, but finding the exact keyword the system is pattern-matching on, then stepping around it.