这一篇讲什么
「服务起不来」和「定时任务不执行」是 Linux 上最常见的两类「命令手工跑好好的,换个方式就不行」。本篇把 systemd 和 cron 的常见坑在三台机器上逐条实跑,每一条都贴原始输出,并标出三个版本哪里不一样。
实测环境同前两篇:
| 机器 | 版本 | systemd | cron |
|---|---|---|---|
| CentOS 7 | 7.9.2009(VMware) | 219 | cronie 1.4.11 |
| Rocky 9 | 9.8(LXD 虚拟机) | 252 | cronie 1.5.7 |
| Ubuntu 24.04 | 24.04.5(LXD 虚拟机) | 255 | cron 3.0pl1-184ubuntu2 |
⚠️ Rocky 9 和 Ubuntu 是「最小化 cloud 镜像 + 补齐标准服务器默认组件」,不是 ISO 标准安装;CentOS 7 是我自己用的虚拟机,root 的登录环境里装过 devtoolset 和 JDK。下文凡是受这两点影响的地方都会单独说明。
1. 服务起不来,先看 status ------ start 返回 0 不代表起来了 ✅
写一个最简单的服务:脚本打印自己的环境,然后常驻。故意不给脚本执行权限:
ini
# /etc/systemd/system/lab-app.service
[Unit]
Description=lab app
[Service]
ExecStart=/opt/lab/app.sh
[Install]
WantedBy=multi-user.target
ini
# ls -l /opt/lab/app.sh (Rocky 9)
-rw-r--r--. 1 root root 176 Sep 21 16:46 /opt/lab/app.sh
# systemctl start lab-app; echo "start 退出码=$?"; sleep 1; systemctl status lab-app --no-pager -l 2>&1 | grep -E 'Active:|Process:|status='
start 退出码=0
Active: failed (Result: exit-code) since Mon 2026-09-21 16:46:58 UTC; 1s ago
Process: 841 ExecStart=/opt/lab/app.sh (code=exited, status=203/EXEC)
Main PID: 841 (code=exited, status=203/EXEC)
🔴 systemctl start 的退出码是 0,服务却已经挂了。 三台机器都是这样。所以脚本里 systemctl start xxx && echo 成功 是靠不住的,要接一句 systemctl is-active xxx。
status=203/EXEC 的意思是「systemd 连程序都没能执行起来」:路径不对、没有执行权限,都是这个码。journalctl 里看到的也只有这些:
ini
# journalctl -u lab-app --no-pager -n 5 | tail -2 (Rocky 9)
Sep 21 16:46:58 rocky9 systemd[1]: lab-app.service: Main process exited, code=exited, status=203/EXEC
Sep 21 16:46:58 rocky9 systemd[1]: lab-app.service: Failed with result 'exit-code'.
chmod +x 之后就起来了:
shell
# chmod +x /opt/lab/app.sh; systemctl start lab-app; echo "start 退出码=$?"; sleep 1; systemctl is-active lab-app
start 退出码=0
active
2. 手工跑得好好的,做成服务就不行 ------ systemd 的环境是空的 ✅
还是那个脚本,它会打印 PATH、JAVA_HOME 和一个自定义变量 LABVAR。我在 /etc/profile.d/lab.sh 里写了:
bash
export LABVAR=from_profile JAVA_HOME=/opt/fakejdk
同一个脚本,两种跑法(Rocky 9):
javascript
# 作为 systemd 服务跑(journalctl 里看到的输出)
app start: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin | JAVA_HOME=<空> | LABVAR=<空> | 用户=root | 目录=/
# 在登录 shell 里手工跑:bash -lc '/opt/lab/app.sh once'
app start: PATH=/root/.local/bin:/root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin | JAVA_HOME=/opt/fakejdk | LABVAR=from_profile | 用户=root | 目录=/root
三台的服务里 PATH 都是 /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin,JAVA_HOME 和 LABVAR 都是空的,工作目录是 /。systemd 不读 /etc/profile、~/.bashrc,你在终端里能用的变量它一个都没有。
(CentOS 7 那台登录 shell 的 PATH 里还有 /opt/rh/devtoolset-7/... 和 /usr/java/bin,是我自己装的东西,道理一样:登录 shell 有,服务里没有。)
正确做法是在 unit 里显式写:
ini
[Service]
Environment=LABVAR=from_unit
ExecStart=/opt/lab/app.sh
但改完如果忘了 daemon-reload,改了等于没改 ------ 见第 4 节。
3. ExecStart 要不要写绝对路径?CentOS 7 要,新版本不用 ✅
把 ExecStart 改成相对写法 ExecStart=sleep 100000,daemon-reload 后重启:
CentOS 7(systemd 219)直接拒绝(节选):
kotlin
restart 退出码=1
Failed to restart lab-app.service: Unit is not loaded properly: Invalid argument.
See system logs and 'systemctl status lab-app.service' for details.
日志里说得很清楚:
ini
[/etc/systemd/system/lab-app.service:4] Executable path is not absolute, ignoring: sleep 100000
lab-app.service lacks both ExecStart= and ExecStop= setting. Refusing.
⚠️ 同一段输出里 systemctl is-active 还显示 active、Main PID: 4074 (sleep) ------ 那是重启前的老进程还在跑 。重启失败不会把老的停掉,只看 is-active 会以为改成功了。
Rocky 9(252)和 Ubuntu 24.04(255)照常启动:
yaml
restart 退出码=0
active
Main PID: 1017 (sleep)
所以「ExecStart 必须写绝对路径」是 CentOS 7 时代的规则。新版本能用,但为了一份 unit 在所有机器上都能跑,还是写绝对路径。
4. 改了 unit 文件却没生效 ------ 忘了 daemon-reload ✅
在 [Service] 下面加一行 Environment=LABVAR=from_unit,不 daemon-reload,直接 restart:
ruby
# CentOS 7
restart 退出码=0
app start: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin | JAVA_HOME=<空> | LABVAR=<空> | 用户=root | 目录=/
Warning: lab-app.service changed on disk. Run 'systemctl daemon-reload' to reload units.
# Rocky 9 / Ubuntu 24.04
Warning: The unit file, source configuration file or drop-ins of lab-app.service changed on disk. Run 'systemctl daemon-reload' to reload units.
restart 成功了,但 LABVAR 还是空的 ------ systemd 用的还是内存里的旧配置。它会打一行 Warning,但混在别的输出里很容易漏看。更可靠的检查方式:
ini
# systemctl show lab-app -p NeedDaemonReload
NeedDaemonReload=yes
daemon-reload 之后再 restart,才看到新变量:
ruby
app start: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin | JAVA_HOME=<空> | LABVAR=from_unit | 用户=root | 目录=/
5. 调试时连续 restart,第 6 次失败 ------ start-limit ✅
这一条是实测时自己撞上的:在 Ubuntu 上改一次配置、restart 一次,几轮之后 restart 突然报 Job for lab-app.service failed(当时没留下能确认原因的日志,推测是下面这个限制)。单独复现一下 ------ 一个正常的服务(ExecStart=/bin/sleep 500000),连续 restart 7 次:
shell
# for i in 1 2 3 4 5 6 7; do systemctl restart lab-ok; echo "第 $i 次 restart 退出码=$?"; done (Rocky 9)
第 1 次 restart 退出码=0
第 2 次 restart 退出码=0
第 3 次 restart 退出码=0
第 4 次 restart 退出码=0
第 5 次 restart 退出码=0
第 6 次 restart 退出码=1
第 7 次 restart 退出码=1
Active: failed (Result: start-limit-hit) since Mon 2026-09-21 16:52:15 UTC; 6ms ago
Sep 21 16:52:15 rocky9 systemd[1]: lab-ok.service: Failed with result 'start-limit-hit'.
三台都是第 6 次开始失败。CentOS 7 的措辞不一样:
sql
Active: failed (Result: start-limit) since Tue 2026-09-22 00:52:03 CST; 6ms ago
Sep 22 00:52:03 localhost.localdomain systemd[1]: start request repeated too quickly for lab-ok.service
原因是默认的启动频率限制:
ini
# systemctl show lab-ok -p StartLimitBurst -p StartLimitIntervalUSec -p StartLimitInterval (Rocky 9 / Ubuntu)
StartLimitIntervalUSec=10s
StartLimitBurst=5
# CentOS 7 的属性名是 StartLimitInterval=10000000(微秒)
10 秒内启动超过 5 次就拒绝。 而且 restart 本身也算启动。被限了以后:
bash
# systemctl reset-failed lab-ok # 清掉计数,之后就能正常 start 了
6. 程序会自己 fork 到后台 ------ Type= 选错,服务被直接清掉 ✅
模拟一个「传统守护进程」:脚本把真正干活的进程放到后台,自己立刻 exit 0。
bash
#!/bin/bash
setsid sleep 200000 </dev/null >/dev/null 2>&1 &
echo $! > /run/lab-daemon.pid
exit 0
用默认的 Type=simple 跑:
makefile
start 退出码=0
Active: inactive (dead)
后台进程:
(没有)
三台一样。主进程一退出,systemd 就认为服务结束了,顺手把同一个 cgroup 里的后台进程也杀掉了。 所以现象不是「进程在跑、状态显示 failed」,而是状态 inactive、进程也没了,看起来就像程序启动失败,日志里又什么都没有。
换成 Type=forking 并告诉它 PID 文件:
ini
Type=forking
PIDFile=/run/lab-daemon.pid
yaml
start 退出码=0
Active: active (running) since Mon 2026-09-21 16:49:15 UTC; 2s ago
Process: 3347 ExecStart=/opt/lab/daemon.sh (code=exited, status=0/SUCCESS)
Main PID: 3348 (sleep)
后台进程:
3348 sleep 200000
Main PID 变成了后台那个进程,stop 时也能正确停掉它。
7. Restart=on-failure:kill -9 会拉起来,普通 kill 不会 ✅
ini
ExecStart=/bin/sleep 300000
Restart=on-failure
RestartSec=2
ini
MainPID=1469
kill -9 之后 4 秒:active MainPID=1475
kill(SIGTERM)之后 4 秒:inactive MainPID=0
三台一致。被 kill(SIGTERM)结束,systemd 当成「正常退出」,on-failure 不会重启:
ini
Active: inactive (dead)
Result=success
ActiveState=inactive
要「怎么死都拉起来」得用 Restart=always。但 always 碰上一启动就崩的程序,会撞上一节的限流:
yaml
# ExecStart=/bin/false, Restart=always, RestartSec=100ms (Rocky 9)
Active: failed (Result: exit-code) since Mon 2026-09-21 16:47:20 UTC; 3s ago
Sep 21 16:47:20 rocky9 systemd[1]: lab-crash.service: Start request repeated too quickly.
CentOS 7 上这时再手工 start,会直接提示怎么办:
rust
Job for lab-crash.service failed because start of the service was attempted too often. See "systemctl status lab-crash.service" and "journalctl -xe" for details.
To force a start use "systemctl reset-failed lab-crash.service" followed by "systemctl start lab-crash.service" again.
开机后第一件事看 systemctl --failed,这类服务都会列在里面:
vbnet
UNIT LOAD ACTIVE SUB DESCRIPTION
● lab-crash.service loaded failed failed lab crash loop
8. enable 和 start 是两件事 ------ 重启实测 ✅
两个一模一样的服务:lab-a 只 start 不 enable,lab-b 只 enable 不 start,然后重启机器。
csharp
重启前:lab-a is-active=active is-enabled=disabled | lab-b is-active=inactive is-enabled=enabled
csharp
重启后:lab-a is-active=inactive is-enabled=disabled | lab-b is-active=active is-enabled=enabled (Rocky 9 / Ubuntu)
重启后:lab-a is-active=unknown is-enabled=disabled | lab-b is-active=active is-enabled=enabled (CentOS 7)
start 只管现在,重启就没;enable 只管开机,现在不启动。两个都要就用 enable --now:
csharp
enable --now 后 is-active=active is-enabled=enabled
(CentOS 7 上没加载的 unit,is-active 显示 unknown 而不是 inactive,意思一样。)
8.1 少了 [Install] 段,enable 不报错
shell
# systemctl enable lab-noinst; echo "enable 退出码=$?"; systemctl is-enabled lab-noinst
enable 退出码=0
static
三台退出码都是 0。Rocky 9 和 Ubuntu 会多打印一大段说明,开头是:
ini
The unit files have no installation config (WantedBy=, RequiredBy=, Also=,
CentOS 7 什么都不说 。状态是 static,开机不会自启 ------ 脚本里只看退出码,会以为已经设好了。
9. 改系统自带的服务:systemctl edit、revert、mask ✅
不要直接改 /usr/lib/systemd/system/ 下的文件 ,用 systemctl edit 生成 override 片段。实测几个细节:
① 它需要终端。 在脚本或非交互环境里跑:
bash
非终端下 systemctl edit 退出码=1
Cannot edit units if not on a tty.
三台一样。自动化脚本里想加 override,直接写文件 /etc/systemd/system/<服务>.service.d/override.conf 再 daemon-reload,效果相同。
② 在终端里用它,结果是这样:
shell
/etc/systemd/system/lab-vendor.service.d/override.conf
# systemctl cat lab-vendor
# /usr/lib/systemd/system/lab-vendor.service
# /etc/systemd/system/lab-vendor.service.d/override.conf
Environment=LABVAR=from_override
重启后服务里 LABVAR=from_override,原文件一个字没动。
③ 撤销用 systemctl revert,但 CentOS 7 没有这个命令:
bash
# CentOS 7
Unknown operation 'revert'.
# Rocky 9 / Ubuntu
Removed "/etc/systemd/system/lab-vendor.service.d/override.conf".
Removed "/etc/systemd/system/lab-vendor.service.d".
CentOS 7 上撤销就是手工删那个 .d 目录再 daemon-reload。
④ mask 只对 /usr/lib 下的服务有效。 对厂商目录里的服务:
kotlin
mask 退出码=0
start 退出码=1
Created symlink /etc/systemd/system/lab-vendor.service → /dev/null.
Failed to start lab-vendor.service: Unit lab-vendor.service is masked.
对你自己写在 /etc/systemd/system/ 下的服务,mask 会失败 ------ 因为 mask 的原理就是在 /etc 下放一个指向 /dev/null 的同名链接,而那个位置已经被你的文件占了:
vbscript
Failed to mask unit: File /etc/systemd/system/lab-app.service already exists. (Rocky 9 / Ubuntu)
Failed to execute operation: Invalid argument (CentOS 7)
10. 进程排查里的三个小坑 ✅
10.1 ps | grep 会搜到自己,pkill -f 会杀掉自己
perl
== ps aux | grep 'sleep 777'
3607 sleep 777
3610 grep sleep 777
== ps aux | grep '[s]leep 777'
3607 sleep 777
[s]leep 这个写法能排除 grep 自己。更要命的是 pkill -f:它按完整命令行匹配,而执行它的那个 shell 的命令行里恰好也包含这个字符串:
shell
# sleep 555 & # 先起一个目标进程
# bash -c 'pkill -f "sleep 555"; echo "pkill 之后这个 bash 还活着"'; echo "bash -c 退出码=$?"
# pgrep -a sleep | grep -w 555 || echo "sleep 555 也没了"
bash -c 退出码=143
sleep 555 也没了
echo 那句根本没执行 ------ pkill 把跑它的 bash -c 也杀了 (143 = 被 SIGTERM 结束)。三台一样。用 ssh 主机 "pkill -f xxx; 后续命令" 这种一行命令时最容易踩,后续命令会静悄悄地不执行。同样的命令写进脚本文件里跑(命令行变成 bash 脚本名)就没事。我这次实测时就被它坑了好几回。
10.2 ps 的 %CPU 是平均值
一个进程先满载跑 5 秒,然后 sleep。8 秒后看:
yaml
== ps:
PID %CPU STAT CMD
2300 59.2 S sleep 600
== top(第二帧):
2300 root 20 0 6048 1872 1764 S 0.0 0.0 0:04.74 sleep
ps 说它占 59%,top 说 0%。ps 的 %CPU 是「启动以来的累计 CPU 时间 ÷ 存活时间」,看当前谁在占 CPU 要用 top(三台一致)。
10.3 僵尸进程杀不掉,要杀它爹
yaml
== 父进程 2337 的子进程:
PID PPID STAT CMD
2339 2337 Z [sleep] <defunct>
== kill -9 2339 之后:
PID PPID STAT CMD
2339 2337 Z [sleep] <defunct>
== 杀掉父进程 2337 之后:
PID STAT CMD
(僵尸 2339 已经没了)
下半篇:定时任务不执行
11. 先认清你的 cron:服务名、日志都不一样 ✅
| CentOS 7 | Rocky 9 | Ubuntu 24.04 | |
|---|---|---|---|
| 服务名 | crond |
crond |
cron |
| 执行记录 | /var/log/cron |
/var/log/cron |
/var/log/syslog |
| 没装 | at、tmux、screen |
同左 | 同左 |
第一步:cron 有没有尝试执行你的任务 ,看日志里有没有 CMD (...) 这一行:
bash
# grep CROND /var/log/cron | tail -1 (Rocky 9)
Sep 21 16:51:01 rocky9 CROND[4097]: (root) CMD (echo "LABVAR=$LABVAR" > /tmp/lab_noprofile)
# grep CRON /var/log/syslog | tail -1 (Ubuntu)
2026-09-21T16:51:01.616050+00:00 ubuntu2404 CRON[2872]: (root) CMD (echo "PATH=$PATH" > /tmp/lab_cron_path)
有这一行 = cron 执行了,问题在命令本身;没有 = 根本没触发,查服务、查语法、查下面第 15 节的格式问题。
下面的实验都是一次性写进 root 的 crontab,等它跑满一分钟,再逐个看结果文件。
12. PATH 和环境变量 ------ Ubuntu 和 RHEL 不一样 ✅
bash
* * * * * echo "PATH=$PATH" > /tmp/lab_cron_path
* * * * * echo "LABVAR=$LABVAR" > /tmp/lab_noprofile
* * * * * . /etc/profile; echo "LABVAR=$LABVAR" > /tmp/lab_profile
结果:
ruby
# CentOS 7 / Rocky 9
lab_cron_path 有 PATH=/usr/bin:/bin
lab_noprofile 有 LABVAR=
lab_profile 有 LABVAR=from_profile
# Ubuntu 24.04
lab_cron_path 有 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
lab_noprofile 有 LABVAR=
lab_profile 有 LABVAR=from_profile
🔴 「cron 的 PATH 只有 /usr/bin:/bin」在 Ubuntu 24.04 上不成立。 Ubuntu 的 cron 通过 PAM 读了 /etc/environment:
ini
# cat /etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"
# grep -n pam_env /etc/pam.d/cron
10:session required pam_env.so
但在 CentOS / Rocky 上,/usr/sbin 下的命令(ip、ss、useradd...)直接写名字就会找不到。/etc/profile.d/ 里的变量三台都读不到 ,需要的话在命令前面 . /etc/profile;,或者用绝对路径、在 crontab 顶部写 PATH=。
⚠️ 很多文章推荐用 env -i /bin/sh -c '命令' 模拟 cron 环境。实测它得到的 PATH 和 cron 并不一样:
ruby
env -i 下 PATH=/usr/local/bin:/usr/bin LABVAR=<空> (CentOS 7 / Rocky 9)
env -i 下 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin LABVAR=<空> (Ubuntu)
它只能用来确认「没有 profile 变量时能不能跑」,PATH 相关的问题要以 crontab 里 echo $PATH 的结果为准。
13. % 没转义 ------ 整条命令都没执行 ✅
bash
* * * * * echo "d=$(date +%F)" > /tmp/lab_cron_pct 2>&1
* * * * * echo "d=$(date +\%F)" > /tmp/lab_cron_pct2 2>&1
ini
lab_cron_pct 没有
lab_cron_pct2 有 d=2026-09-21
三台一样:没转义的那一行,连输出文件都没生成 。cron 把 % 当成换行,命令在第一个 % 处被截断,变成一条语法错误的 shell 命令,而你写的 > 文件 2>&1 在截断点后面,也一起没了 ------ 所以报错也不会进你的日志文件。报错去了哪,三台各不相同:
ini
# CentOS 7:发到 root 的邮箱,邮件标题就是截断后的命令
Subject: Cron <root@localhost> echo "d=$(date +
# Rocky 9:进了 journal
Sep 21 16:51:01 rocky9 CROND[4064]: (root) CMDOUT (/bin/sh: -c: line 1: unexpected EOF while looking for matching `)')
# Ubuntu:日志里只看到截断后的命令本身
Sep 21 16:51:01 ubuntu2404 CRON[2863]: (root) CMD (echo "d=$(date +)
最省事的做法:把命令写进脚本文件,cron 只调脚本 ,脚本里的 % 不用转义。
14. 没重定向的输出去哪了?三台三个答案 ✅
bash
* * * * * echo "hello-from-cron-no-redirect"
CentOS 7 (装了 postfix):发邮件到 /var/spool/mail/root:
perl
2 hello-from-cron-no-redirect
2 Subject: Cron <root@localhost> echo "hello-from-cron-no-redirect"
Rocky 9(没装邮件程序):crond 启动时就说了,输出改记到日志:
ini
Sep 21 16:35:13 rocky9 crond[668]: (CRON) INFO (Syslog will be used instead of sendmail.)
Sep 21 16:51:01 rocky9 CROND[4062]: (root) CMDOUT (hello-from-cron-no-redirect)
Ubuntu 24.04(没装邮件程序):直接丢掉:
ini
Sep 21 16:51:01 ubuntu2404 CRON[2855]: (CRON) info (No MTA installed, discarding output)
也就是说,在 Ubuntu 上一个出错的定时任务,如果你没写 >> 日志 2>&1,报错信息就彻底没了。所以一律写:
javascript
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
15. 「日」和「星期」同时写:是「或」不是「且」 ✅
实验当天是 21 号、星期一(UTC)。三条任务:
bash
* * 21 * 4 touch /tmp/lab_dom_only # 日期对(21 号),星期不对(4 = 周四)
* * 13 * 1 touch /tmp/lab_dow_only # 日期不对,星期对(1 = 周一)
* * 13 * 5 touch /tmp/lab_neither # 都不对
lab_dom_only 有
lab_dow_only 有
lab_neither 没有
三台都这样(CentOS 7 那台是 CST 22 号周二,规则按当天换算,结果相同)。两个字段都不是 * 时,满足任意一个就执行。「每月 13 号且是周五才跑」没法只用 crontab 表达,得在命令里再判断一次日期。
16. /etc/cron.d/ 的两个格式坑 ✅
16.1 少写了用户那一列
/etc/crontab 和 /etc/cron.d/* 比用户 crontab 多一列用户名。把用户 crontab 的格式抄进来:
bash
# /etc/cron.d/labuserfmt
* * * * * touch /tmp/lab_crond_userfmt
touch 被当成了用户名:
less
crond[718]: (touch) ERROR (getpwnam() failed) (CentOS 7)
crond[668]: (touch) ERROR (getpwnam() failed - user unknown) (Rocky 9)
cron[514]: Error: bad username; while reading /etc/cron.d/labuserfmt (Ubuntu)
cron[514]: (*system*labuserfmt) ERROR (Syntax error, this crontab file will be ignored)
Ubuntu 会把整个文件都忽略掉,同文件里写对的行也不会执行。
16.2 文件名带点:只有 Ubuntu 会忽略
shell
# /etc/cron.d/labok * * * * * root touch /tmp/lab_crond_ok
# /etc/cron.d/lab.cron * * * * * root touch /tmp/lab_crond_dot
bash
# CentOS 7 / Rocky 9
lab_crond_ok 有
lab_crond_dot 有
# Ubuntu 24.04
lab_crond_ok 有
lab_crond_dot 没有
🔴 「文件名带点会被静默忽略」只在 Ubuntu 上成立 ,CentOS 7 和 Rocky 9 照跑。跨系统统一的做法:文件名只用字母、数字、横线。
17. crontab -r:没有确认,一下全没 ✅
shell
# crontab -r; echo "crontab -r 退出码=$?"; crontab -l; echo "crontab -l 退出码=$?"
crontab -r 退出码=0
crontab -l 退出码=1
no crontab for root
三台都一样:不问、不提示、退出码 0。-r 和 -e 在键盘上挨着。两个办法:
bash
crontab -l > ~/cron.bak # 改之前先备份
crontab -ri # -i 会先问一句
vbnet
crontab: really delete root's crontab? crontab -ri 答 n 的退出码=0 # 答 n,任务还在(9 行)
(Ubuntu 的提示后面多一个 (y/n)。)
18. systemd timer:能看到下次什么时候跑 ✅
同样的定时需求用 timer 写:
ini
# lab-job.timer
[Timer]
OnCalendar=*:*:30
AccuracySec=1s
Persistent=true
RandomizedDelaySec=1
[Install]
WantedBy=timers.target
sql
# systemctl list-timers lab-job.timer (Rocky 9)
NEXT LEFT LAST PASSED UNIT ACTIVATES
Mon 2026-09-21 16:51:30 UTC 14s left Mon 2026-09-21 16:50:30 UTC 45s ago lab-job.timer lab-job.service
「下次什么时候跑」「上次什么时候跑的」一眼可见,输出也自动进 journal(journalctl -u lab-job.service)。注意 timer 触发的服务一样读不到 profile 里的变量:
ini
timer job ran, LABVAR=<空>
验证时间表达式可以用 systemd-analyze calendar,但 CentOS 7 没有这个子命令:
yaml
# CentOS 7
Unknown operation 'calendar'.
# Rocky 9 / Ubuntu
Normalized form: *-*-* 02:00:00
Next elapse: Tue 2026-09-22 02:00:00 UTC
From now: 9h left
19. 手册(和很多教程)里需要改的地方
| 常见说法 | 实测 |
|---|---|
ExecStart 必须写绝对路径 |
只有 CentOS 7 拒绝;Rocky 9 / Ubuntu 24.04 相对写法能起 |
Type 选错 → 进程在跑但显示 failed |
实测是 inactive (dead),后台进程被 systemd 一起清掉了 |
少了 [Install],enable 会失败 |
退出码 0,状态 static,CentOS 7 连提示都没有 |
cron 的 PATH 只有 /usr/bin:/bin |
CentOS 7 / Rocky 9 是;Ubuntu 24.04 读 /etc/environment,是完整的 PATH |
/etc/cron.d/ 文件名带点会被忽略 |
只有 Ubuntu 忽略;CentOS 7 / Rocky 9 照跑 |
用 env -i 模拟 cron 环境 |
PATH 和 cron 实际的不一样,只能部分模拟 |
| cron 没配邮件,输出就丢了 | Rocky 9 会记进 journal(CMDOUT),只有 Ubuntu 是直接丢 |
20. 速查
服务起不来:
bash
systemctl status 服务 --no-pager -l # 看 Active 和 status=
journalctl -u 服务 -n 50 --no-pager
systemctl show 服务 -p NeedDaemonReload # 改了 unit 没 reload?
systemctl reset-failed 服务 # 被 start-limit 限住了
systemctl --failed # 开机后先看这个
| 看到 | 多半是 |
|---|---|
status=203/EXEC |
路径不对 / 没执行权限 / CentOS 7 上写了相对路径 |
Result: start-limit-hit(7 上是 start-limit) |
10 秒内启动超过 5 次 |
inactive (dead),进程也没了 |
程序自己 fork 到后台了,要 Type=forking |
| 手工能跑、服务不行 | 环境变量,在 unit 里写 Environment= |
| 改了没生效 | 没 daemon-reload |
定时任务不执行:
bash
grep CROND /var/log/cron # RHEL:有没有 CMD 这一行
grep CRON /var/log/syslog # Ubuntu
journalctl -u crond / -u cron
crontab -l > ~/cron.bak # 改之前先备份
- 日志里没有
CMD→ 没触发:服务名、/etc/cron.d/格式(用户列、Ubuntu 上的文件名) - 有
CMD但没效果 → 命令本身:PATH、profile 变量、%转义 - 一律加
>> 日志 2>&1,Ubuntu 上不加就什么都看不到
下一篇:磁盘满了、LVM 扩容、fstab 写错开不了机。