💡
本文面向已经会使用 Linux 命令行、但还没有系统理解 systemd 的读者。目标是读完后能独立创建一个长期运行服务、一个一次性任务服务,以及一个替代 cron 的 systemd timer。
🔎
这篇文章也服务于后续 Linux 安全排查:攻击者可能把后门、挖矿程序、反连脚本伪装成 systemd service 或 timer 持久化运行。因此除了“怎么创建服务”,还要掌握“怎么枚举所有服务、怎么查看某个服务到底执行什么、怎么找定时触发的可疑任务”。

1. systemd 是什么

在多数现代 Linux 发行版中,systemd 是系统和服务管理器。它在系统启动时作为 PID 1 运行,负责把用户空间服务拉起来,并持续跟踪、管理这些服务。相比传统 init 脚本,systemd 的核心抽象是 unit:服务、定时器、socket、挂载点、target 等都被描述成 unit 文件。
官方手册把 unit 文件定义为一种纯文本、INI 风格的配置文件,用来描述 service、socket、device、mount、timer 等对象;通用配置写在 [Unit][Install],服务自己的配置写在 [Service],定时器自己的配置写在 [Timer]。参考:systemd.unit(5)systemd.service(5)systemd.timer(5)
可以先看 PID 1:
查看 systemd 版本:

2. unit 和 service 的关系

service 是 unit 的一种。一个 nginx.servicessh.servicedocker.service 本质上都是 unit 文件,只是后缀是 .service,因此它们拥有 [Service] 段。
常见 unit 类型包括:
  • .service:服务进程,最常见。
  • .timer:定时触发另一个 unit,通常触发同名 .service
  • .socket:socket 激活,收到连接后再拉起服务。
  • .target:一组 unit 的同步点,类似“运行级别”的现代替代。
  • .mount / .automount:挂载点。
  • .path:监听文件路径变化后触发 unit。
列出当前已加载的 unit:
列出已安装的 service unit 文件:
安全排查时,不要只看 running 服务,也要看所有 service 的启用状态:
列出所有已加载服务,包括 inactive、failed:
只看失败服务:
排查时这些异常值得重点看:名称像系统服务但不属于发行版包、enabled 但业务上没人知道、描述含糊如 Update Helper、服务文件位于 /etc/systemd/system/ 且创建时间很新、ExecStart= 指向 /tmp/dev/shm、隐藏目录、用户家目录或随机命名二进制。

3. 服务文件放在哪里

systemd 会从多个目录加载 unit。不同发行版略有差异,但最重要的路径可以这样理解:
目录
用途
优先级与建议
/etc/systemd/system/
管理员自己创建的服务、覆盖发行版默认服务的配置
优先级高。自定义服务通常放这里
/run/systemd/system/
运行时临时 unit
重启后消失
/usr/lib/systemd/system//lib/systemd/system/
软件包安装的 vendor unit
不要直接改,升级可能覆盖
~/.config/systemd/user/
当前用户级服务
配合 systemctl --user 使用
查看某个服务实际来自哪里:
查看 systemd 实际识别到的片段路径:
如果要改软件包自带服务,不推荐直接编辑 /usr/lib/systemd/system/xxx.service。推荐用 drop-in:
示例输出通常是编辑器界面;保存后可验证:

4. 一个 service 文件长什么样

最小服务文件一般由三段组成:
三段含义:
  • [Unit]:写元信息和依赖关系。After= 只表达启动顺序,Wants= / Requires= 才表达拉起依赖。
  • [Service]:写服务如何运行。ExecStart= 是主命令,User= 指定运行用户,Restart= 指定异常退出后的重启策略。
  • [Install]:写 enable 时如何挂到启动目标上。WantedBy=multi-user.target 的意思是启用后,系统进入多用户模式时拉起它。
Type= 很关键:
  • simple:默认类型,systemd fork 出进程后就认为启动成功。
  • exec:等到 execve() 成功后才认为启动成功,通常比 simple 更能暴露“命令不存在/用户不存在”等启动错误。
  • forking:适合老式会自我 daemonize 的程序,需要配合 PIDFile= 更可靠。
  • oneshot:一次性任务,执行完就结束,常和 timer 搭配。
  • notify:服务主动通过 sd_notify 告诉 systemd 自己 ready。

5. 创建一个长期运行服务

假设我们要把 /opt/qetx-demo/app.py 作为 HTTP 服务运行。先准备目录和脚本:
创建专用用户:
写 unit 文件:
重新加载 systemd 配置:
daemon-reload 成功时通常没有输出,但它非常重要:systemd 不会因为你新建或修改 unit 文件就自动重读配置。官方 FAQ 也提到自动监听 unit 文件变化存在竞态问题,所以需要显式 reload。
检查 unit 语法:
启动服务:
查看状态:
设为开机自启:
也可以启动并启用一步完成:
验证是否 enabled:
验证是否 active:
查看日志:
停止、重启、禁用:

6. systemctl 常用命令速查

命令
作用
典型输出
systemctl start qetx-demo
立即启动服务
成功通常无输出,退出码 0
systemctl stop qetx-demo
停止服务
成功通常无输出
systemctl restart qetx-demo
重启服务
成功通常无输出
systemctl reload nginx
让支持 reload 的服务重载配置
成功通常无输出;不支持会报错
systemctl status qetx-demo
查看运行状态和最近日志
Active: active (running)
systemctl enable qetx-demo
设置开机自启
创建 wants 目录下的符号链接
systemctl disable qetx-demo
取消开机自启
删除 enable 创建的符号链接
systemctl daemon-reload
重读 unit 文件
成功通常无输出
systemctl cat qetx-demo
查看最终 unit 文件内容
显示 unit 文件和 drop-in
systemctl edit qetx-demo
创建 override drop-in
打开编辑器
systemctl list-units --type=service --all
查询当前所有已加载服务
显示 active、inactive、failed 服务
systemctl list-unit-files --type=service
查询所有已安装 service 文件
显示 enabled、disabled、static 等状态
systemctl --failed --type=service
查询失败服务
显示 failed 服务列表
systemctl show qetx-demo -p FragmentPath -p ExecStart -p User
查看服务文件路径、启动命令、运行用户
FragmentPath=/etc/systemd/system/qetx-demo.service
systemctl list-timers --all
查询所有 systemd 定时器
显示 NEXT、LAST、UNIT、ACTIVATES
enablestart 要分清:start 是现在启动,enable 是设置未来启动。systemctl 手册也明确说明二者是正交关系:一个服务可以已启动但未启用,也可以已启用但当前没启动。参考:systemctl(1)

7. 定时服务:timer + service

systemd 的定时任务由 .timer unit 实现。它不像 cron 那样直接写一行命令,而是通常由两个文件组成:
  • xxx.service:真正要执行的任务。
  • xxx.timer:什么时候触发这个任务。
默认规则是:foo.timer 会触发同名的 foo.service。也可以在 [Timer] 中用 Unit=bar.service 指定要触发别的 unit。官方 timer 手册说明:每个 timer 文件都需要有一个匹配的 unit;默认激活同名 service。参考:systemd.timer(5)

8. 创建一个定时备份任务

先写脚本:
写一次性 service:
注意这里没有 [Install]。因为这个 service 不需要自己开机启动,它由 timer 触发。
写 timer:
重载并启用 timer:
查看 timer:
手动触发一次 service 测试:
查看 timer 细节:

9. timer 的原理

timer 的工作方式可以拆成五步:
  1. systemd 读取 .timer unit,并根据 [Timer] 段计算下一次触发时间。
  1. timer 到点后,systemd 激活对应的 unit,通常是同名 .service
  1. 如果被触发的 service 已经处于 active 状态,timer 不会再启动一个新实例,而是保持原样。这一点在官方 timer 手册中有明确说明。
  1. service 运行、退出、写日志,状态由 systemd 记录到 journal。
  1. timer 计算下一次触发时间,继续等待。
核心字段:
  • OnCalendar=:日历时间,类似 cron,适合“每天 03:30”“每周一”等。
  • OnBootSec=:开机后多久触发。
  • OnUnitActiveSec=:被触发 unit 上次激活后多久再次触发。
  • OnUnitInactiveSec=:被触发 unit 上次变为 inactive 后多久再次触发。
  • Persistent=true:机器关机或 timer 未运行时错过了触发时间,下次 timer 启动后补跑一次。
  • AccuracySec=:允许 systemd 在一个时间窗口内合并唤醒,默认可能不是秒级精准;需要更准可调小。
  • RandomizedDelaySec=:给触发时间加随机延迟,适合避免大量机器同时打到同一个服务。
  • Unit=:指定 timer 触发哪个 unit。
示例:每 10 分钟跑一次:
启用后查看:
示例:每周一凌晨 4 点,并随机延迟 30 分钟:
验证日历表达式:

10. timer 和 cron 的区别

维度
cron
systemd timer
任务定义
一行时间 + 命令
.timer 负责时间,.service 负责执行
日志
依赖 cron/mail/syslog 配置
天然进入 journal,可按 unit 查询
依赖关系
较弱
可用 After=Wants=Requires= 表达
错过任务
默认不补跑
Persistent=true 可补跑
资源与安全
需要额外包装
可直接用 User=MemoryMax=PrivateTmp=
如果只是个人 crontab 里一条简单命令,cron 仍然很方便;如果任务是系统服务的一部分,需要日志、依赖、失败状态、权限隔离和可观测性,systemd timer 通常更适合。

11. 面向安全排查的服务和定时器查询

在入侵排查里,systemd 需要重点关注两个面:服务持久化定时任务持久化。攻击者可能创建一个看似正常的 service,让木马开机自启;也可能创建 timer,每隔几分钟拉起脚本、下载 payload 或恢复被删除的进程。

11.1 查询当前所有服务

查询所有已加载服务:
查询所有 service 文件以及是否自启:
只看 enabled 服务,适合快速找开机自启项:
查看失败服务,失败的后门服务、残留服务也可能暴露痕迹:

11.2 关闭服务开机自启动

确认某个服务当前是否开机自启动:
只取消开机自启动,但不停止当前正在运行的进程:
再次确认:
如果安全排查中已经确认服务可疑,通常要同时停止当前进程并取消开机自启动:
验证服务已经停止且不再自启:
如果目标是 timer 定时任务,应该关闭 .timer,而不是只关闭它触发的 .service。因为 timer 下次到点后仍可能再次拉起 service:
如果已经确认是恶意持久化,停止和禁用只是第一步。还需要保留证据后处理 unit 文件、可执行文件、日志、网络连接、父子进程和相关账号。不要在没有取证需求判断前直接删除所有痕迹,否则可能影响后续溯源。

11.3 查看某一个具体服务

先看状态和最近日志:
看 unit 文件真实内容,尤其关注 ExecStart=User=WorkingDirectory=Restart=
show 抽取关键字段,适合脚本化排查:
根据 MainPID 继续看进程、网络连接和文件路径:
查看该服务日志:

11.4 查询所有定时器

systemctl list-timers --all 是排查 systemd 定时任务的核心命令。默认不加 --all 时只显示即将触发或活跃 timer;加上 --all 可以把 inactive、已经错过、未启用但已加载的 timer 也列出来。
只查某一个 timer:
查看 timer 文件内容:
抽取 timer 关键字段:

11.5 直接从目录层面查可疑 unit

按时间排序查看管理员目录中新建或修改的 unit:
搜索高危路径或可疑命令:
查看 enable 创建的符号链接,理解服务是如何挂到启动目标上的:

11.6 排查时的判断清单

  • 服务名是否伪装成系统组件,例如 system-update.servicedbus-helper.servicekworker.service
  • Description= 是否模糊但无法对应业务。
  • FragmentPath= 是否在 /etc/systemd/system/,且修改时间接近入侵时间。
  • ExecStart= 是否指向 /tmp/var/tmp/dev/shm、隐藏目录、用户家目录、.cache
  • 是否使用 bash -ccurl | bashwget -O-ncsocatpython -c 等高风险启动方式。
  • 是否 Restart=always,被杀后会自动拉起。
  • 是否存在同名 .timer 反复触发可疑 .service
  • 运行用户是否异常,尤其是普通业务不需要 root 却用 root 运行。
  • 日志是否被清空或只有极少输出,unit 文件是否刻意把输出重定向到 /dev/null

12. 排错思路

查看 unit 是否加载成功:
看详细日志:
改完 unit 后忘了 reload 的典型提示:
重置 failed 状态:
检查依赖链:

13. 服务安全加固的常用字段

很多服务不应该直接用 root 跑。systemd 可以在 unit 里直接做基础隔离:
验证最终属性:
这些字段属于执行环境配置,更多可以查 systemd.exec(5)

14. 一个推荐实践清单

  • 自定义系统服务放 /etc/systemd/system/
  • 软件包自带服务不要直接改,用 systemctl edit xxx.service 创建 drop-in。
  • 修改或新增 unit 后运行 sudo systemctl daemon-reload
  • 新服务先 systemd-analyze verify,再 systemctl start
  • 长期运行服务优先考虑 Type=exec 和合适的 Restart=
  • 一次性任务用 Type=oneshot,再交给 .timer 定时触发。
  • timer 启用的是 .timer,不是 .service
  • 需要错过后补跑就加 Persistent=true
  • 排错优先看 systemctl statusjournalctl -u
  • 生产服务尽量指定专用用户,并开启必要的 sandbox 选项。

15. 参考资料

0xGame2024Linux 系统权限配置详解:从 chmod 到 ACL、sudo 与 capabilities
Loading...