最近有个朋友问我,他办公室那台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.targetOnCalendar=*-*-* 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-timerslist-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基础概念都串起来了。把这个小功能吃透,你以后再做任何定时任务,都能少踩一半的坑。