ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Ubuntu每天23点自动关机:cron与systemd定时任务配置详解

Ubuntu每天23点自动关机:cron与systemd定时任务配置详解

最近有个朋友问我,他办公室那台Ubuntu电脑能不能做到每天23:00自动关机,省得人下班走了机器还亮一晚上。这种需求其实很常见:开发机、打印服务器、宿舍里的个人电脑,甚至机房里的某台测试设备,都可能需要“到点就关”的能力。很多人第一反应是“这还不简单,设个定时任务就行”,但真上手会发现,里面藏着不少细节:shutdown命令的参数、cron的权限问题、时区差异、systemd timer的配置方式,每一步都可能踩坑。

这篇文章就专门讲清楚“Ubuntu系统每天定时23:00关机”这件事。我不只给你一个能直接用的配置,还会把方案选型、执行原理、排查方法都讲透。无论你是刚接触Linux的新手,还是已经用过cron的老手,都能在这里找到适合自己的做法,并且理解为什么这样写、背后是什么逻辑。

1. 需求分析与方案选型

1.1 这个需求到底在什么场景下出现

定时关机(Scheduled Shutdown)表面上是个非常简单的功能,但背后的场景差异很大。

第一种场景是办公电脑节能。很多公司里,员工下班后电脑不关,显示器关了但主机还在跑。定时23:00关机可以让主机自动断电,既省电费,也减少设备损耗。这一类需求通常希望“不打扰、静默执行”,最好在下班后还能给加班的同事留出缓冲时间,所以23:00这个点选得挺考究。

第二种场景是服务器或实验室设备的强制维护。比如某台设备需要在夜间停止对外服务,或者测试环境需要在固定时间断电重启。这类场景对准确度要求高,而且往往不能依赖人工手动操作,必须由系统自己完成。

第三种场景是个人电脑的“防熬夜”功能,很多人为了强迫自己按时睡觉,会设置一个到点强制关机的规则。

不同场景对方案的要求不一样。个人电脑可能更关心“关机前要不要提醒”,服务器更关心“任务是否一定能执行”。所以我在下面给出了两套主流做法:cron方案和systemd timer方案,分别满足不同偏好。

1.2 三种主流做法对比

在Linux系统里,定时执行任务的选择其实不少,但主流就是这三样:cron、systemd timer、at。

at命令是用来“安排一次性的定时任务”的,比如“5分钟后重启”,但它不适合做周期性的每日关机。如果你明确是“每天23:00”,那at就需要每天手动加一次任务,根本不实用。

cron是Unix/Linux系统里最经典的任务调度器,几乎所有发行版都支持,配置简单,一行crontab就能搞定。它适合做“分钟级粒度的重复任务”,对绝大多数定时关机需求来说是第一选择。

systemd timer是systemd体系里的任务调度组件,Ubuntu 16.04之后系统默认就带。它比cron更“现代化”:时间格式更像自然语言,日志记录更规范,还支持错过时间后的策略选择。缺点是学习曲线稍微陡一点,配置需要写两个文件。

我把这三个方案放在一张表里对比:

方案学习成本可靠性日志可查性适用场景
cron + shutdown低高中(日志在syslog)大多数Linux服务器和桌面
systemd timer中高高(journalctl统一查)追求可维护性的现代系统
at + shutdown极低中低一次性任务,不适合周期执行

如果你只是想要“快速搞定,以后忘掉它”,选cron就够了。如果你想把定时任务做得更规范,方便以后排查和扩展,选systemd timer。下文我会把这两套方案都写清楚。

2. 方案一:cron + shutdown 快速实现每日23点关机

2.1 先搞清楚shutdown命令的几个参数

在写定时任务之前,必须先弄明白shutdown命令本身。很多人在第一步就写错了命令,导致定时任务执行时报错或者根本没有效果。

最常用的关机命令是shutdown -P now,意思是“立即关闭系统并切断电源”。参数-P(poweroff)表示关电源,如果不带这个参数,默认可能会进入其他运行级别,行为会很奇怪。在Ubuntu上,安全的写法是:

sudo shutdown -P now

还有一种写法是shutdown -h now,-h表示halt,在部分系统上也会关机,但效果可能不如-P彻底,所以我个人更推荐-P。

另外一定要特别注意:shutdown命令还可以接受一个“时间参数”,比如sudo shutdown -P 23:00,意思是安排系统在23:00关机。这个写法在命令行直接操作时很方便,系统会提前5分钟给所有登录用户发送“系统即将关机”的警告消息。

但在cron任务里,这个写法就要小心了。如果你在cron里写的是0 23 * * * /sbin/shutdown -P now,那意思是“23:00整准时执行关机命令”,这是正确的。如果你写的是0 23 * * * /sbin/shutdown -P 23:00,那cron在23:00执行时,又会让shutdown再调度一次“23:00关机”。由于执行瞬间已经是23:00了,这个调度通常也会立即生效,但存在几秒到几十秒的竞态风险,不推荐这么写。

还有一点值得记一下:取消一个已经调度的关机任务,用shutdown -c。比如你临时不想关机了,执行sudo shutdown -c就能取消。

2.2 用crontab创建每日23点的定时任务

cron的配置入口有两个:当前用户的crontab和root用户的crontab。因为关机操作需要root权限,所以我推荐直接用root的crontab,这也是全系统生效的做法。

第一步,编辑root的cron任务:

sudo crontab -e

第一次执行时,系统会问你想用哪个编辑器,通常选nano(新手友好)或者vim(老手习惯)都可以。

第二步,在文件末尾加上一行:

0 23 * * * /sbin/shutdown -P now

这行cron表达式的含义是:每天的第23小时、第0分钟执行后面的命令。五个字段从左到右分别是“分、时、日、月、周”,顺序特别容易写反,很多人把“0 23”写成“23 0”,结果变成了23分钟时执行一次,坑得很。

第三步,保存退出。如果是nano,按Ctrl+O保存,再按Ctrl+X退出;如果是vim,输入:wq回车。

第四步,确认任务写进去了:

sudo crontab -l

看到刚才那行,就说明任务已经注册成功。

这里有一个细节值得展开:为什么我写的是/sbin/shutdown的绝对路径,而不是直接写shutdown?因为cron执行任务时使用的环境变量非常精简,PATH通常只有/usr/bin:/bin这种基础路径,而shutdown命令在Ubuntu上位于/sbin/shutdown,不一定在默认PATH里。如果不写绝对路径,定时任务可能报“command not found”却不知道去哪查。你可以用which shutdown命令查看自己系统上shutdown的绝对路径,在Ubuntu上通常是/usr/sbin/shutdown或/sbin/shutdown,把查到路径填进去即可。

2.3 确认cron服务在运行

任务写完之后,还需要确认cron服务本身是启动状态。Ubuntu用systemd管理服务,所以检查命令是:

sudo systemctl status cron

如果看到状态是active (running),说明cron在正常工作。如果发现服务没有启动,那就手动启动并设为开机自启:

sudo systemctl enable --now cron

这个--now参数的意思是“立刻启动,同时设置开机自启”,比分开两条命令写更省事。

另外,你还可以查看cron的执行日志来验证任务是否真的跑了。在Ubuntu上,cron日志默认写到/var/log/syslog,可以这样查询:

grep CRON /var/log/syslog

不过更推荐用journald来查:

journalctl -u cron

如果你看到类似CMD (/sbin/shutdown -P now)的记录,说明定时任务确实被执行了。这个排查习惯很重要,遇到“任务没生效”时,日志是第一个调查对象。

3. 方案二:systemd timer 实现每日23点关机

3.1 为什么还要用systemd timer

cron方案够简单,但有几个地方让人觉得不够爽:cron表达式那五段数字,过两天再看就忘了什么意思;日志分散在syslog里,不太醒目;cron对“任务是否必须要跑”没有太多策略控制。

systemd timer就不一样。它用两个文件组成一个任务单元:一个.service文件定义“要执行什么”,一个.timer文件定义“什么时候执行”。时间格式用的是类似OnCalendar=*-*-* 23:00:00的写法,一读就懂:任意年任意月任意日,23点整。

systemd timer还有一个cron没有的能力:Persistent选项,可以决定“如果任务时间点错过了一次,开机后要不要补执行”。这个对定时关机来说非常关键——稍后我会专门讲怎么用安全。

3.2 创建shutdown.service单元文件

第一步,创建service文件:

sudo vim /etc/systemd/system/poweroff-at-night.service

内容如下:

[Unit] Description=Power off at night scheduled task [Service] Type=oneshot ExecStart=/sbin/shutdown -P now

注意Type=oneshot这个配置。它告诉systemd:这个服务不是持续运行的后台程序,而是一次性执行完毕的任务,比较符合“执行关机”这种动作的特性。

ExecStart后面依然要写shutdown的绝对路径。你自己系统上是什么路径,就用which shutdown查出来的结果。

3.3 创建shutdown.timer单元文件

第二步,创建timer文件:

sudo vim /etc/systemd/system/poweroff-at-night.timer

内容如下:

[Unit] Description=Timer to power off every day at 23:00 [Timer] OnCalendar=*-*-* 23:00:00 Persistent=false [Install] WantedBy=timers.target

OnCalendar=*-*-* 23:00:00是systemd的日历时间表达式,含义是每一天的23:00:00。这个格式非常直观,比cron的五字段更接近人类语言。

3.4 启动并验证定时器

第三步,重新加载systemd配置并启动定时器:

sudo systemctl daemon-reload sudo systemctl enable --now poweroff-at-night.timer

第一条命令让systemd重新读取新增的单元文件,第二条命令把timer设为开机自启并立即激活。

第四步,验证定时器状态:

sudo systemctl status poweroff-at-night.timer sudo systemctl list-timers

list-timers输出里能看到每个定时器的下次触发时间(NEXT)和最近触发时间(LAST)。如果你看到poweroff-at-night.timer的NEXT时间是第二天的23:00,说明配置成功。

想看这个任务到底有没有执行,可以用:

journalctl -u poweroff-at-night.service

日志里会记录service的启动和停止情况,这比cron的日志清爽多了。

取消这个定时任务也很简单,一条命令:

sudo systemctl disable --now poweroff-at-night.timer

如果你想彻底删掉,再把两个单元文件删掉即可。

这里我要重点强调一下Persistent=false的选择。Persistent=true的意思是“如果原定的触发时间点被错过(比如当时机器没开机),那么下次开机时立即补执行”。对“备份”“数据同步”这类任务来说这个功能很棒,但对“关机”来说非常危险。假设你出差一周,电脑是关着的,周一早上9点开机准备干活,结果systemd发现上周日23:00的关机任务没执行,按Persistent=true的逻辑马上执行关机命令——刚开机就瞬间关机,资料都没来得及看,这绝对是你不想遇到的。所以我明确建议用Persistent=false,保证“错过就错过,绝不补刀”。

4. 进阶技巧:让定时关机更顺手

4.1 关机前如何提醒当前用户

直接关机其实有点“粗暴”。如果23:00时你还在电脑前忙活,系统突然掉电,谁都会崩溃。即便系统会在关机前通知,默认的shutdown警告也不够醒目。所以最好加一个“关机前5分钟弹窗提醒”的机制。

cron方案和systemd方案都能实现,思路是:把原来的23:00关机改成“先弹提醒,再执行关机”。这里不要用两个独立的定时任务去拼,太脆了,最简单的做法是利用shutdown命令自带的时间参数。

如果你追求简单,可以直接把cron任务改成如下两条:

55 22 * * * DISPLAY=:0 notify-send "系统将在5分钟后关机,请保存工作" ; /sbin/shutdown -P 23:00

前提是你用的是桌面版Ubuntu,并且需要知道当前用户的DISPLAY编号。这个方案把“提醒”和“调度关机”合并成一条任务,22:55弹一次通知,然后由shutdown在23:00执行真正的关机,省心。

如果你用的是GNOME桌面,还可以用zenity --warning --text="系统即将关机"来代替notify-send,效果差不多,看个人喜好。

4.2 时区与时间同步问题

这是定时任务最常见的隐性杀手:系统的时区不对。

Ubuntu安装时如果选的不是国内时区,或者服务器默认是UTC时区,你的“23:00关机”可能变成“UTC 23:00关机”,换算成北京时间就是第二天早上7点关机。这完全是两码事。

检查方法很简单:

timedatectl status

输出里Time zone一栏如果显示Asia/Shanghai,那没问题。如果显示的是UTC或者别的时区,执行:

sudo timedatectl set-timezone Asia/Shanghai

改完再timedatectl status确认一次。

顺带说一句,cron和systemd timer用的都是系统当前的本地时区,所以只要系统时区对了,两者都不需要单独配置。另外,如果你的系统时间有偏差,还需要确认时间同步服务在运行:

sudo timedatectl set-ntp true

这个命令会启用systemd-timesyncd服务,让系统自动通过NTP服务器校准时间,减少“因为系统时钟走得慢导致定时任务不准”的问题。

4.3 和“定时开机”联动使用

定时关机的进阶玩法是配合“定时开机”,实现夜间自动关机、早晨自动开机的完整闭环。定时开机不是操作系统层面的功能,而是BIOS/UEFI里的功能,通常叫RTC Wake或Power On by RTC。你可以在BIOS设置里指定每个工作日早晨8点通电,然后在操作系统里设置每天23:00关机,这样这台机器就实现了“晚上睡觉、早上自动起来工作”的无人值守周期。

这个玩法在机房、实验室或者需要长期运行的设备上特别实用。有一点要注意:定时开机依赖主板时钟,如果你的主板电池没电了,RTC时间会丢失,定时开机就失效了。所以建议同时开启NTP时间同步,让系统在开机后自动纠正时间偏差。

4.4 快速测试定时任务的方法

不要去傻等23:00验证任务,手动改时间测试更快。用cron举例,你可以先把crontab改成:

2 * * * * /sbin/shutdown -P now

意思是“每小时的第2分钟关机”。保存后,等2分钟,机器顺利关机,说明这条链路没问题。测完之后,再改回0 23 * * *。

用systemd timer测试更简单,直接把OnCalendar改成*-*-* 00:02:00,或者更暴力的方式:触发的service文件保持不变,只把OnCalendar改成几小时后,然后看systemctl list-timers有没有正确地计算下次时间。注意测试时别把系统玩崩了就行。

5. 常见问题排查与实操避坑

5.1 任务到了23:00但没有反应

这是最常遇到的问题。按照我自己的排查顺序,一般是这样:

第一步,确认cron服务在运行。用sudo systemctl status cron查看,没启动就启动。

第二步,确认crontab条目真实存在。用sudo crontab -l,不要只查看你登录用户自己的crontab,因为关机任务在root的crontab里。

第三步,看cron日志,确认任务有没有被调度执行。用grep CRON /var/log/syslog查。如果你在日志里看到了CMD记录,说明cron执行了命令,那问题大概率出在shutdown命令本身——比如路径写错、权限不够,或者系统当时挂起了。

第四步,检查机器在23:00时是不是处于休眠或者关机状态。如果电脑当时已经休眠了,所有定时任务都不会触发,因为CPU和内存都没在工作。这是很多人忽略的:定时任务依赖系统运行,不是靠主板独立芯片调度。

我发现文档里“系统已经关机/休眠”这个原因格外容易被忽略。cron和systemd timer都不能“唤醒”已经关机的电脑。定时关机任务只保证“机器开着的时候,到点自动关”,并不能保证“机器关了之后再把它叫醒”,这是主板层的工作。

5.2 触发了关机命令但系统没有真正断电

这种情况比较少见,但一旦遇到就很让人头疼。常见原因有两个:

一个是shutdown参数不对。如果你写的是shutdown -h now,在某些系统上会执行halt而不是poweroff,最终表现就是系统卡在“已关机但电源还通电”的状态。解决办法是把参数统一改成-P。

另一个是系统里还有其他会话或者服务阻止了关机。在桌面版Ubuntu上,如果有未保存的文档,GNOME会弹窗问“是否保存?”,这一弹就卡住了关机流程。这种情况下,建议在关机命令前加上跳过交互的参数。不过说实话,桌面环境下的定时关机本来就不适合强制断电,它会留下未保存文件。所以如果你想在桌面机上做这个功能,我建议先把提醒机制做好,让人有保存时间。

5.3 定时任务有时候执行有时候不执行

如果你发现“今天关了、明天没关”,那大概率是系统更新或者定时任务冲突导致的。比如Ubuntu的自动更新程序在23:00前后锁住了关机流程;或者系统正在执行其他apt任务,shutdown命令被排队阻塞。

排查方法还是看日志。用journalctl查看关机时的系统日志,看有没有异常阻塞。另外,检查一下是不是存在多个定时任务互相冲突,比如你既在root的crontab里写了关机,又在系统timer里注册了一个关机,两个都执行,行为就会混乱。如果你两套方案都配置了,建议只保留一套,不然到时候自己都分不清是谁关的。

5.4 用户权限问题:提示Need to be root怎么办

这是新手最常撞上的报错。在某天23:00的日志里你会看到类似:

shutdown: Need to be root

原因很简单:cron任务以普通用户的身份执行,而shutdown命令需要root权限。解决办法是先切到root用户编辑定时任务:

sudo crontab -e

关键点:不要在自己用户的crontab里写关机命令,必须用sudo crontab -e写到root的crontab里。

5.5 定时任务被系统休眠“吞掉”的细节

很多人遇到“到了23:00系统直接休眠了,没有关机”的怪现象。这其实可能是power management和定时关机的交互问题。有些桌面环境默认在闲置一段时间后进入休眠,休眠状态和关机状态不一样:休眠后系统状态还在内存里,但定时任务不会执行。如果你机器每天23点之前就自动休眠了,那定时关机自然“没有发生”。

处理方式:要么禁用自动休眠,要么把定时关机时间提前到休眠之前。如果你真的既想节能,又想保留定时任务,可以考虑“休眠+定时唤醒”的组合,但那需要更复杂的配置,已经不属于本文讨论范围。

5.6 我自己的实操习惯

最后分享几个实际操作中的经验。

第一,我会先把定时测试时间设在2分钟后,跑通了再改回23:00。别看这个步骤简单,它能帮你把“配置问题”和“环境问题”快速分离开。

第二,我建议在cron方案跑通的情况下,尽快迁移到systemd timer。原因很直接:systemd的日志查询太方便了,什么时候执行、有没有失败、为什么失败,一条journalctl全能看到。对于需要长期维护的机器来说,这个优势值得投入一点学习成本。

第三,我会定期检查系统日志,确认定时关机没有因为某个版本更新而失效。Ubuntu系统升级时,偶尔会动到systemd相关配置,导致自定义timer单元被禁用。每次大版本更新后,跑一下systemctl list-timers看看状态,能省去后面不少排查时间。

从“一开始随便跑一下crontab”到“后来用systemd timer管理所有周期任务”,我自己也是这么一路过来的。定时关机看起来是个微不足道的小功能,但它涉及的权限、时区、日志、电源管理这些知识点,几乎把所有Linux基础概念都串起来了。把这个小功能吃透,你以后再做任何定时任务,都能少踩一半的坑。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进