宝塔面板 chattr +i 锁住 cron 文件:root 也写不进去
凌晨上线,需要给生产服务器加一条定时任务——一个每分钟跑一次的调度入口,负责关单、退款轮询。SSH 进去,crontab -e 编辑,保存,报错。换 echo >> /var/spool/cron/crontabs/root,还是报错。cat > /etc/cron.d/myjob,依然报错。
ls -la 看了一遍,目录是 root 所有,权限 700,文件 600,一切正常。但我是 root。这不合理。
现象
在宝塔面板(aaPanel)管理的 Linux 服务器上,以下所有操作均失败:
crontab -e # 保存时报 /var/spool/cron/: mkstemp: Operation not permitted
crontab /tmp/new_crontab # 同上
echo "* * * * * ..." >> /var/spool/cron/crontabs/root # Operation not permitted
cat > /etc/cron.d/myjob # Operation not permitted
cp /tmp/myjob /etc/cron.d/ # Operation not permitted注意错误是 Operation not permitted,不是 Permission denied。这是两个完全不同的错误:后者是 Unix DAC(文件权限位)拒绝,前者是内核能力层拒绝,即便 root 也无法绕过。
根因
宝塔面板在安装时用 chattr +i 给多个 cron 路径打上**不可变(immutable)**属性。这是一种内核级扩展属性,存在于 ext2/ext4 文件系统,与 Unix 权限位无关。lsattr 可以看到:
lsattr /var/spool/cron/crontabs
# ----i---------e------- /var/spool/cron/crontabs
lsattr /var/spool/cron/crontabs/root
# ----i---------e------- /var/spool/cron/crontabs/root输出第 5 位是 i → 不可变锁生效。ls 完全看不到这个标志,这就是为什么权限看起来一切正常。
宝塔这么做的目的是防止绕过面板 UI 篡改 cron,属于有意为之的安全设计,不是 bug。
crontab(1) 写入 crontab 的机制是先在父目录创建临时文件(mkstemp),再原子替换。如果只 chattr -i 了目标文件,父目录 /var/spool/cron/crontabs/ 仍然是 immutable,mkstemp 依然失败,crontab -e 依然报 "Operation not permitted"。
必须同时解锁文件和父目录。
还有另一个坑:修改完重新上锁后,service cron reload 会安静地说"ok",但新任务永远不触发。
unlock → 写入 → relock 这个操作序列发生在毫秒内。cron 守护进程通过 inotify 监听文件变更,但 chattr +i 重新上锁的那一刻可能吃掉了 inotify 的 CHANGE 事件。结果:crontab -l 能看到新条目,lsattr 显示已重新锁定,service cron reload 报成功——但 /var/log/syslog 里新任务从来不出现。
只有 systemctl restart cron 强制守护进程从磁盘重新读取 crontab,才能让新条目生效。
正解
# 1. 先备份
cp /var/spool/cron/crontabs/root /tmp/crontab_root_$(date +%s).bak
# 2. 同时解锁文件和父目录(两行缺一不可)
chattr -i /var/spool/cron/crontabs/root
chattr -i /var/spool/cron/crontabs
# 3. 确认解锁(输出中不应再出现 i)
lsattr /var/spool/cron/crontabs/root
lsattr -d /var/spool/cron/crontabs
# 4. 追加 cron 条目
echo "* * * * * cd /www/wwwroot/myapp && /usr/bin/php think schedule:run >> /var/log/schedule.log 2>&1" \
>> /var/spool/cron/crontabs/root
# 5. 重新锁定,保留宝塔安全策略
chattr +i /var/spool/cron/crontabs
chattr +i /var/spool/cron/crontabs/root
# 6. restart(不是 reload)
systemctl restart cron || service cron restart || service crond restart
# 7. 验证条目存在
crontab -l | tail -5/etc/cron.d/ 也是同类情况,解锁目录后再写文件:
chattr -i /etc/cron.d
cat > /etc/cron.d/myjob <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin
* * * * * root /usr/bin/myscript >> /var/log/myjob.log 2>&1
EOF
chmod 644 /etc/cron.d/myjob
chattr +i /etc/cron.d/myjob
chattr +i /etc/cron.d
service cron reload # /etc/cron.d 用 reload 即可,不需要 restart三步缺一不可:解锁父目录、解锁文件;修改;重新锁定;最后 systemctl restart cron(不是 reload)。
操作完用 grep CRON /var/log/syslog | tail -5 等一分钟确认新条目出现在 syslog 里,才算真正生效。
排查速查
遇到 Operation not permitted 但权限看起来正确时,第一反应就跑 lsattr:
lsattr /etc/cron.d 2>&1
lsattr /var/spool/cron/crontabs 2>&1
lsattr /var/spool/cron/crontabs/root 2>&1输出含 i → 宝塔锁,走本文流程。输出无 i → 另找别的原因(SELinux、AppArmor、mount options 等)。
一句话外卖
宝塔面板用
chattr +i锁住 cron 的文件和目录两层,ls完全看不出来;解锁必须同时解父目录和文件,写完重新锁定后必须restart cron而非reload。