做运维这些年我最大的感受是很多问题不是难而是没人把基础命令讲透。就拿定时任务来说临时想跑个脚本可以用 at每天、每周固定执行要用 crontab。很多人会把 crontab 打成或念成“crontd”群里聊命令的时候经常闹误会其实它们说的是同一个东西。这篇文章就把这两个命令放在一起聊透各自适合什么场景、怎么写才不会踩坑、出了问题怎么定位。不管你是刚摸服务器的新人还是被线上任务坑过的老兵都能从里面捞到点能直接用的东西。1. 先搞清楚工具边界at 与 crontab 到底有什么区别1.1 at 像闹钟crontab 像日历很多初学者分不清这两个命令用错的根源在于把“一次性”和“周期性”混为一谈。at 是典型的“一次性闹钟”。我说“下午三点跑一次数据修复脚本”到点执行完这个任务就消失了不会明天同一时间再跑。它适合那种临时、单次、有明确时间点的事情。比如今天下午 13:30 重启一次应用服务夜里 23:00 做一次一次性的全量导出5 分钟后清理一次临时目录crontab 则是“日历闹钟”。我说“每天凌晨 2 点备份数据库”“每周一上午 9 点生成周报”它会按周期反复执行。它的核心价值是让机器替你记住这些重复动作不需要人每天手动敲命令。明白了这个边界你就知道该用哪个了。临时、一次性、指定时间点用 at固定频率、长期重复、希望风雨无阻自动执行用 crontab。1.2 后台服务与执行链路这两个命令都只是“前台客户端”真正干活的是后台守护进程。at 对应的服务是atd。你执行at 16:00并输入命令后任务会被写进/var/spool/at/目录下的一个文本文件atd到点之后读取并执行这个文件。crontab 对应的服务是crond。你执行crontab -e编辑任务后内容会保存到/var/spool/cron/目录下文件名是你的用户名。crond每分钟会扫描一次这些文件凡是时间匹配的任务就用/bin/sh执行对应的命令。这里要注意系统自带了一个/etc/crontab文件它和用户级crontab -e的任务格式稍有区别后面我会专门讲。常说的“crontd”并不是一个真实存在的命令正确拼写是 crontab对应的守护进程叫 crond。2. 动手前的准备安装、服务管理与权限控制2.1 安装并启动服务多数 Linux 发行版默认已经装了 cron但 at 不一定。建议先检查再安装。在 Debian/Ubuntu 这套环境里apt update apt install -y at cron systemctl enable --now atd systemctl enable --now cron在 Red Hat 系环境里dnf install -y at cronie systemctl enable --now atd systemctl enable --now crond安装之后第一时间确认服务状态systemctl status atd systemctl status crond # Debian系是 cron我见过不少“at 命令明明提交成功却不执行”的案例最后发现只是 atd 没启动。所以这一步别嫌啰嗦先确认两个服务是绿色的再往下走。2.2 谁能用allow/deny 权限文件生产环境不能允许每个用户都随便提交定时任务。系统提供了四个权限文件/etc/at.allow和/etc/at.deny/etc/cron.allow和/etc/cron.deny规则很简单allow 文件放在面前只有里面列出的用户能用deny 文件则是黑名单里面列出的用户不能用。如果两个都存在allow 优先。不同发行版在“两个文件都不存在”时的默认行为不完全一致有的允许所有用户有的只允许 root。所以别凭印象判断直接看文件最靠谱ls -l /etc/at.allow /etc/at.deny /etc/cron.allow /etc/cron.deny 2/dev/null cat /etc/at.allow 2/dev/null cat /etc/cron.allow 2/dev/null如果你的服务器存在多用户共用的情况建议明确创建/etc/cron.allow和/etc/at.allow只放需要使用的账号。白名单比黑名单更安全、更好维护。2.3 编辑器相关的几个烦人细节crontab -e默认使用环境变量EDITOR指定的编辑器。如果你不习惯 vi可以先设置export EDITORnano运行时出现no crontab for root - using an empty one不是错误只是告诉你当前用户还没有任务文件会新建一个空文件正常保存退出就行。有些人第一次看到这句话被吓一跳以为系统出问题了。另外直接使用crontab -e编辑时如果语法写错保存后 cron 不会立刻告诉你错在哪里只会拒绝加载并报错。所以大改动之前建议先把当前任务备份下来crontab -l /root/backup/$(date %Y%m%d)_crontab.txt3. at 命令一次性延时任务的完整实战3.1 最基本用法at 的交互方式是很多人第一次用觉得突兀的地方。执行at加时间后会进入at提示符接下来输入的命令就是到点要执行的内容输完按CtrlD结束。at 16:30 at /usr/local/bin/repair.sh at EOT job 5 at Thu Dec 26 16:30:00 2026看到job 5说明提交成功这个 5 就是任务编号。如果想避免交互式输入可以用管道一行搞定echo /usr/local/bin/repair.sh | at 16:30或者用-f从文件读取命令at 16:30 -f /tmp/at_cmd.txt我最常用的是now 1 minute因为调试 at 任务用它可以很快验证效果。一个典型的测试流程是echo touch /tmp/at-test-ok | at now 1 minute sleep 70 ls -l /tmp/at-test-ok看到文件生成说明 atd 工作正常。3.2 at 时间表达式的写法at 的时间语法很灵活掌握下面这些基本就够用写法含义16:30今天 16:30若已过则明天4:30pm下午 4:30noon中午 12:00midnight午夜 0:00now 5 minutes5 分钟后now 2 hours2 小时后now 3 days3 天后2026-12-31 23:59指定日期时间tomorrow 08:00明天早上 8 点有一个容易踩坑的点如果你指定的“小时分钟”已经在今天过去at 不会报错而是自动当成明天。比如现在是下午 5 点你写at 09:00它实际执行的是明天早上 9 点。这不是 bug是 at 的正常设计。所以提交任务后最好看一眼返回的日期时间确认是不是自己心里想的那个时间。3.3 查看、确认和删除任务和 at 配套的有三个命令务必要记得atq或at -l查看当前排队中的任务列表at -c 编号查看编号对应任务的完整内容atrm 编号或at -d 编号删除指定任务atq输出大致是4 Wed Dec 31 10:00:00 2026 a root 5 Thu Dec 26 16:30:00 2026 a root第一列是任务编号最后有用户名。想要删除任务atrm 4再用atq确认没了。这里建议养成习惯提交 at 任务后立刻atq确认一次避免时间写错又不自知。3.4 at 任务的环境与输出处理at -c 编号看到的文件里会包含一堆环境变量这是 atd 在执行任务前为你还原的当时环境。但有一个坑非登录 shell 的 PATH 未必完整脚本里如果用到了某些二进制最好写绝对路径。更关键的是输出。at 默认如果有标准输出或错误输出会以邮件方式发给当前用户。服务器上根本没人看邮件这些邮件堆积在系统里不仅占空间还会造成“任务到底跑了没有”的困惑。所以提交时建议强制重定向echo /usr/local/bin/repair.sh /var/log/at-task.log 21 | at 16:30如果我真的不想收到任何邮件可以用-M参数at -M 16:30 -f /tmp/at_cmd.txt-m则是“即使没有输出也发一封邮件”用来确认执行状态。日常不建议用别给自己制造邮件垃圾。4. crontab 命令周期任务的正确打开方式4.1 crontab 的基本操作crontab 的操作比 at 要“重”一些因为它管理的是一长串任务。最基本的是四条crontab -e编辑当前用户任务crontab -l列出当前用户任务crontab -r删除当前用户全部任务crontab -u 用户名配合参数操作指定用户需要 root 权限crontab -r非常危险它会一下清空所有任务且不跟你确认。我见过有人本想删其中一行结果手滑敲了-r整个 crontab 没了。建议每次改动前先备份crontab -l /root/crontab-backup-$(date %Y%m%d).txt恢复备份也很简单crontab /root/crontab-backup-20261226.txt4.2 5 段时间字段拆解crontab 每一行任务的核心是前面 5 个时间字段后面跟命令。五个字段分别是字段取值范围说明第1段 分0-59一小时中的第几分钟第2段 时0-23一天中的第几个小时第3段 日1-31一个月中的第几天第4段 月1-12第几个月第5段 周0-70 和 7 都代表周日1 到 6 对应周一到周六核心规则五个字段是“与”的关系某一天只有所有字段都匹配时才会执行。星号*表示“每个取值都行”。比如* * * * *就是每分钟执行一次。但这里有个容易绕晕的点日和周是“或”的关系这一点不同手册说法不同实际 Linux cron 的行为是如果日和周都不是*则其中任一匹配即可触发。所以我建议不要在一条规则里同时限制“每月 1 号”和“周一”这种组合否则结果可能和你预期相反拆成两行更稳。4.3 特殊符号与简化关键字除了写数字还有几个常用符号逗号,枚举多个值比如0 8,12 * * *表示每天 8 点和 12 点连字符-表示范围比如* 9-18 * * *表示 9 点到 18 点内每分钟斜杠/表示步长比如*/10 * * * *表示每 10 分钟所以# 每 2 小时执行一次 0 */2 * * * /usr/local/bin/check.sh # 工作日早上 9:30 执行 30 9 * * 1-5 /usr/local/bin/report.sh # 每月的 1 号和 15 号凌晨执行 0 2 1,15 * * /usr/local/bin/clean.shcrontab 还提供几个简写关键字关键字等价于reboot守护进程启动时执行一次daily / midnight0 0 * * *weekly0 0 * * 0monthly0 0 1 * *hourly0 * * * *特别提醒reboot不是“用户登录时执行”而是 crond 服务启动时执行。因此服务器重启、crond 重启都会触发脚本里要做好重复执行不会出事的准备。4.4 常见任务周期对照表直接给一份常用写法抄作业就行需求crontab 写法每分钟执行* * * * *每 5 分钟执行*/5 * * * *每小时执行0 * * * *每天凌晨 2:30 执行30 2 * * *每周一凌晨 3:00 执行0 3 * * 1每月 1 号凌晨 4:00 执行0 4 1 * *工作日 9:30 执行30 9 * * 1-5每年 1 月 1 日零点执行0 0 1 1 *写完后用crontab -l确认识别成功。有时肉眼看着没错但 crond 就是因为某个空格或不可见字符加载失败所以一定要养成“编辑完立刻crontab -l”的习惯。5. 综合场景选择与实操案例5.1 几个典型场景的选择思路我总结了一个简单判断方法刚刚说过“一次性用 at、周期性用 crontab”但实际项目里还会遇到“系统重启后要执行”“每周频率但机器可能关机”的情况。系统启动后自动拉起服务用reboot笔记本、工作站等经常关机的机器优先用 anacron 而不是 crontab它会补跑错过的任务临时跑个一次性的脚本用 at固定周期的监控、备份、清理用 crontab希望精确到秒执行crontab 最小粒度是分钟做不到得在脚本里自己循环5.2 案例每天备份 临时运维重启假设服务器上有一个 Web 站点需求是每天凌晨 2 点半备份站点目录同时今天下午 4 点要重启一次应用服务验证新配置。先写备份脚本/opt/scripts/backup.sh#!/bin/bash BACKUP_DIR/data/backup mkdir -p ${BACKUP_DIR} tar czf ${BACKUP_DIR}/web_$(date %Y%m%d).tar.gz -C /var/www/html .给脚本执行权限并手动验证一次chmod x /opt/scripts/backup.sh /opt/scripts/backup.sh确认备份文件生成后再把它写进 crontabcrontab -e写入30 2 * * * /opt/scripts/backup.sh /var/log/backup.log 21注意脚本里date %Y%m%d不用转义因为 crontab 里的%才是特殊符号脚本内部的%不受影响。临时重启应用服务则用 atecho /usr/bin/systemctl restart myapp | at 16:00提交后立刻用atq检查一下6 Thu Dec 26 16:00:00 2026 a root到了 16:00任务执行完atq里这条任务会自动消失。5.3 防止任务堆积并发控制生产环境一个大坑是“任务还没跑完下一次又启动了”。比如备份脚本要跑 1 小时但 cron 设置成了每 30 分钟跑一次两个备份进程就会同时压向同一份数据轻则资源占用重则数据自相残杀。标准解法是用flock0 2 * * * /usr/bin/flock -n /tmp/backup.lock /opt/scripts/backup.sh /var/log/backup.log 21-n表示拿不到锁就直接退出不阻塞等待。这样如果上一次备份还在跑下一次到点时就会自动跳过避免并发。这是一种非常实用的“允许延迟、不要重复”的运维策略。6. 常见问题排查与避坑实录6.1 at 提交成功却不执行先检查systemctl status atd如果服务正常再确认时间格式。at 对不合法的字符串会报garbled time能提交成功通常说明时间合法。然后检查任务文件ls /var/spool/at/如果文件存在但没执行多半是执行时找不到命令或环境不对。把任务里的命令改为绝对路径并把输出重定向到日志再试一次echo /usr/local/bin/script.sh /var/log/at-run.log 21 | at now 1 minute还有权限问题如果/etc/at.deny里包含了当前用户名提交时直接会被拒绝。用 root 身份提交时基本不受影响。6.2 crontab 脚本不执行但手动能跑这是出现频率最高的问题。脚本手动执行一切正常一进 crontab 就“没反应”。原因一般是环境变量差异或者 crontab 内容本身没被加载。排查顺序是systemctl status crond crontab -l grep CRON /var/log/syslog journalctl -u cron --since today journalctl -u crond --since today日志里如果显示No such file or directory先检查脚本第一行的解释器路径是否存在。比如脚本首行写了#!/usr/bin/python但系统实际只有/usr/bin/python3手动用 bash 执行时因为有了 shebang实际上如果首行错误bash 执行脚本会报错。这时改成#!/usr/bin/python3就好。更常见的还是 PATH。crontab 的最小 PATH 往往只有/usr/bin:/bin你手动登录 shell 时/usr/local/bin可能被加入了 PATH所以调用restic、rclone这类装在/usr/local/bin的工具时crontab 里找不到命令。解决办法有二脚本里用绝对路径或在 crontab 文件顶部显式指定PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin6.3 百分号和特殊字符问题crontab 文件里的%会被当成换行符处理这是一个非常隐蔽的坑。比如我想在 crontab 里写0 0 * * * /usr/bin/date %Y-%m-%d /tmp/date.log这里面的%Y、%m、%d如果不转义crontab 实际执行时会发生换行和错乱。正确写法是0 0 * * * /usr/bin/date \%Y-\%m-\%d /tmp/date.log这种问题在日志里非常难看懂因为 cron 会显示Illegal instruction之类或者根本加载错误。最稳妥的办法是把复杂逻辑写进脚本crontab 只负责调用脚本不要在 crontab 行内写太多 shell 语法。6.4 时区问题crontab 默认按照系统本地时间执行。如果服务器时区不对你会觉得“明明设了 2 点结果 4 点才跑”。检查时区timedatectl date如果发现时区不对改掉timedatectl set-timezone Asia/Shanghai修改时区后最好重启 cron 服务让任务时间重新计算systemctl restart crond systemctl restart atd在容器环境里要特别注意有些容器镜像的TZ环境变量不会传给 crond导致容器里 cron 还是 UTC。这时要在 Dockerfile 或启动命令里显式写上ENV TZAsia/Shanghai并且确认/etc/localtime已正确设置。另外如果业务对时间要求极端严格有些团队干脆把系统时区统一设为 UTC所有 crontab 时间按“UTC 对应的本地时间”换算后写入这样避开夏令时和时区漂移的干扰也是一种思路。6.5 输出日志和邮件问题的排查cron 默认会把标准输出和错误邮件发给MAILTO指定的地址。如果不设置则发给任务属主。很多 Linux 服务器默认没装邮件服务邮件会堆积在本地看起来就是“任务好像没跑”。建议在所有 cron 任务后面都加上输出重定向0 2 * * * /opt/scripts/backup.sh /var/log/backup.log 21如果确实不想收到邮件在文件顶部设置MAILTO这样 crond 就不会给每个任务发邮件日志也清爽很多。7. 最后分享几点个人使用心得用了这些年我慢慢养成了一些习惯可能对刚接触这些命令的人有帮助。第一新机器装完系统第一件事就是把 atd 和 crond 设为开机自启并纳入监控。定时任务这种东西平时看着不起眼一旦服务挂了可能几周后你才会发现“原来这个脚本一直没跑”。第二每个定时任务都要有自己的日志文件哪怕只是简单追加到一个文件也比系统邮件里靠猜强得多。第三凡是脚本中涉及的命令一律写绝对路径凡是脚本要处理的文件也一律用绝对路径。这是我从一次次坑里总结出来的铁律。第四修改 crontab 之前先备份改完立刻crontab -l检查这是一个三秒就能完成的习惯却能救你于水火。第五调试 at 时多用now 1 minute调试 cron 时把时间临时改成下一分钟确认逻辑没问题后再改回真实周期。这两个小技巧能大幅缩短验证时间。最后建议大家把常用的 crontab 任务清单纳入版本管理和代码一起提交。这样服务器出问题时你可以快速对比“之前好好的后来谁改了什么”。基础命令看似简单背后全是运维的活儿细节做到位才不容易被线上问题打脸。