Linux 实战(三):服务起不来、定时任务不执行 —— CentOS 7 / Rocky 9 / Ubuntu 24.04 实测差异

这一篇讲什么

「服务起不来」和「定时任务不执行」是 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           # 改之前先备份
  1. 日志里没有 CMD → 没触发:服务名、/etc/cron.d/ 格式(用户列、Ubuntu 上的文件名)
  2. 有 CMD 但没效果 → 命令本身:PATH、profile 变量、% 转义
  3. 一律加 >> 日志 2>&1,Ubuntu 上不加就什么都看不到

下一篇:磁盘满了、LVM 扩容、fstab 写错开不了机。

相关推荐
Hello_Pyhx1 小时前
Ventoy:Linux制盘与排障
linux·运维·服务器
Jason_zhao_MR2 小时前
电力数据采集核心板选型指南
linux·嵌入式硬件·fpga开发·嵌入式
microxiaoxiao2 小时前
MobaXterm vs Aeroshell
linux·运维·ssh
东城居士2 小时前
理解设备树下的platform驱动编写
linux·嵌入式系统
小义_2 小时前
Linux 补充
linux·服务器
晴天的雨.9922 小时前
【Linux】Shell 命令以及运行原理+权限管理
linux·运维·服务器
djjjx.2 小时前
Linux工具篇 (六):GDB 调试从底层逻辑到高阶实战
linux·运维·服务器·gdb
coward913 小时前
RK3576 双千兆 GMAC 驱动加载异常问题排查与总结
linux
幼儿园蛋小黄3 小时前
从设备树到 ARM 驱动:嵌入式 Linux 驱动开发学习总结
linux·arm开发·驱动开发