SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-23

如何在 VPS 上配置 systemd 服务运行程序

学习为 VPS 程序编写可运行的 systemd unit:开机自动启动、崩溃后重启、通过 journalctl 查看日志,并用 timer 定时运行与限制服务权限。

systemd 服务是什么,以及为什么需要它

systemd 服务是一个简短的文本文件,用于告诉服务器如何运行程序:在启动时启动程序,程序崩溃时重新启动,并将其输出发送到系统日志。这就是它的全部作用。在 SSH 会话中手动启动的程序会在您退出登录或服务器重启时立即终止。由 systemd 服务管理的程序会持续运行,因为它由服务器本身管理,而不是由您的 shell 管理。

systemd 是 Ubuntu、Debian、Fedora 和大多数现代 Linux 服务器使用的 init 系统。它是第一个启动的进程,也是负责监管其他所有进程的进程。过去并不总是这样;了解 unit 文件的作用后,建议阅读一次systemd 如何取代早期的 init 脚本。编写服务文件后,您就将程序交给这个监管器管理。本指南将介绍一个可运行的最小 unit、每个 unit 都包含的 3 个部分、如何启用服务并读取其日志、如何使用 timer 按计划运行服务,以及如何限制服务权限,使其以尽可能少的权限运行。

可运行的最小服务

服务单元文件位于 /etc/systemd/system/,扩展名为 .service,只需包含几行配置。为位于 /usr/local/bin/myapp 的程序创建一个服务单元:

sudo nano /etc/systemd/system/myapp.service
[Unit]
Description=My application

[Service]
ExecStart=/usr/local/bin/myapp

[Install]
WantedBy=multi-user.target

这是一个完整且可运行的单元。ExecStart 是要执行的命令。WantedBy=multi-user.target 表示服务器进入正常多用户运行状态后启动该服务,因此服务会在启动时自动运行。其他内容都属于细化配置。

三个部分及各自的用途

每个 unit 文件都按方括号中的部分进行划分。服务单元使用其中的三个部分。

[Unit]描述服务及其依赖关系。最常用的是下面两行:

[Unit]
Description=My application
After=network-online.target
Wants=network-online.target

Description是您在 systemctl status 中看到的服务名称。After=network-online.target 告诉 systemd,网络启动后才能启动您的程序。对于绑定端口或发起出站连接的程序,这一点很重要。

[Service]定义程序的运行方式。大多数设置都写在这里:

[Service]
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.toml
WorkingDirectory=/opt/myapp
User=myapp
Restart=on-failure
RestartSec=5
Environment=LOG_LEVEL=info

User=myapp让程序以非特权账户而不是 root 运行,这是安全性方面最重要的一行。这里没有 Type= 行,因此 systemd 会使用 simple,并假定 ExecStart 进程会一直在前台运行;如果程序会 fork 到后台运行,则需要根据其启动方式指定 正确的 Type=,否则该单元会显示为 active,但实际守护进程已经退出。Restart=on-failureRestartSec=5 会在下面单独介绍,因为它们正是大多数人编写服务单元的原因。

[Install]定义启用服务时的行为:

[Install]
WantedBy=multi-user.target

运行 systemctl enable 时,WantedBy=multi-user.target 会将服务关联到启动流程中。没有 [Install] 部分时,可以手动启动服务,但系统重启后不会自动启动它。

启用服务并监控日志

编写或编辑 unit 文件后,重新加载 systemd,使其读取更改,然后一步启用并启动服务:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service

daemon-reload 是最容易被忽略的一步:systemd 会缓存 unit 文件,因此重新加载前,编辑不会生效。enable --now 会同时将服务设为开机启动,并立即启动服务。检查服务状态:

sudo systemctl status myapp.service
* myapp.service - My application
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled)
     Active: active (running) since Wed 2026-07-15 22:40:11 UTC; 3s ago
   Main PID: 4123 (myapp)

Active: active (running)enabled 表示服务运行正常。要查看程序输出,请仅查询此 unit 的日志:

sudo journalctl -u myapp.service -f

-f 会在新行到达时持续输出,类似于 tail -f。程序写入标准输出或标准错误的任何内容都会记录在这里,无需自行配置日志记录。

故障时自动重启,这正是您使用它的原因

服务的主要价值在于,程序退出后 systemd 会自动重启它。只需添加两行配置:

[Service]
Restart=on-failure
RestartSec=5

Restart=on-failure会在程序以非零退出码退出,或因 SIGKILL、SIGSEGV 等崩溃信号终止时重启程序。程序正常退出,或因 SIGTERM、SIGINT、SIGHUP、SIGPIPE 停止时,不会触发重启。RestartSec=5会在每次尝试之间等待 5 秒,避免立即崩溃的程序进入紧密重启循环。您可以终止进程,然后观察 systemd 将其重新启动,以确认配置生效。请使用 SIGKILL:默认的 SIGTERM 会被视为正常停止,因此 on-failure不会重启服务:

sudo systemctl kill -s SIGKILL myapp.service
sudo systemctl status myapp.service

5 秒内,状态会显示新的 Main PID,并再次显示 active (running)。这就是该功能的全部作用,也正是服务优于让程序在 tmuxscreen中持续运行的原因。

以非特权用户运行并进行加固

如果程序遭到利用,以 root 身份运行的服务可以对服务器执行任意操作。应让服务使用专用用户运行,并通过 systemd 指令限制其权限。首先创建一个禁止登录且没有主目录的系统账户:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp

然后设置 User=myapp,并将加固配置添加到 [Service]

[Service]
User=myapp
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true

每一行配置都会移除程序不需要的能力。NoNewPrivileges=true 可阻止进程获取新权限,即使通过 setuid 二进制文件也无法获取。PrivateTmp=true 会为进程提供一个独立的 /tmp,其他进程无法查看。ProtectSystem=strict 会将整个文件系统设为只读,但通过 ReadWritePaths= 指定的少数路径除外。ProtectHome=true 会完全隐藏 /home。这与通过防火墙保护服务采用的是同一项最小权限原则:只提供服务所需的权限。如果您已阅读关于 在 VPS 上关闭 IPv6 防火墙漏洞 的指南,这就是同一原则在主机上的实现。对于面向互联网的服务,应将此加固措施与 在 SSH 前配置 Fail2ban 以及默认拒绝的防火墙结合使用。

与其手动输入所有配置并记错指令,不如生成完整的加固单元文件,然后将其复制出来:

Toolsystemd service and timer generator

定时器:现代版 cron

systemd 定时器按计划运行服务,是 cron 任务的现代替代方案。一个定时器由两个文件组成:负责执行任务的 .service,以及定义运行时间的 .timer。例如,您希望每天凌晨 3 点执行备份。该服务执行一次任务后退出:

# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh

Type=oneshot 告诉 systemd,该程序会运行、完成任务并退出,而不是常驻运行。定时器负责调度它:

# /etc/systemd/system/backup.timer
[Unit]
Description=Run the nightly backup

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target

OnCalendar=*-*-* 03:00:00 表示每天凌晨 3 点。使用 systemd-analyze calendar "*-*-* 03:00:00" 测试任何日历表达式。该命令会确认表达式可以解析,并打印下一次触发时间。若服务器在凌晨 3 点关机,Persistent=true 会在服务器恢复后立即运行错过的任务,而 cron 无法执行此操作。请注意,定时器通过 timers.target 启用,而不是通过 multi-user.target 启用。应启用定时器,而不是服务:

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers

list-timers 显示所有定时器及其下次和上次运行时间,您可以快速查看任务下一次何时运行。启用定时器模式后,上面的生成器会为您创建配对的 .service.timer。与 cron 行相比,定时器可将真实日志写入 journal,支持与服务相同的加固指令,并提供 Persistent=true 提供的错过任务补运行功能。对于简单任务,cron 仍然适用;任务变得重要后,定时器是更合适的工具。

FAQ

systemd 服务和 cron 任务有什么区别?

服务用于持续运行程序:它在启动时运行,失败时自动重启,并将日志写入 journal。cron 任务按计划运行短命令,然后退出。如果既需要定时执行,又需要 journal 日志、加固和补偿执行遗漏的任务,请使用 systemd timer。它将 .timer 调度与 oneshot 服务配对,在大多数服务器任务中可替代 cron。

systemd 服务文件应放在哪里?

将自定义 unit 放在 /etc/systemd/system/ 中,并使用以 .service 结尾的名称。该目录用于存放管理员添加的 unit,其优先级高于软件包在 /lib/systemd/system/ 中提供的 unit。在该目录创建或编辑文件后,运行 sudo systemctl daemon-reload,使 systemd 重新加载更改。

如何让服务在崩溃后自动重启?

[Service] 部分添加 Restart=on-failureRestartSec=5,然后运行 sudo systemctl daemon-reload 并重启服务。程序以非零退出码退出或因崩溃信号终止时,systemd 会重新启动它,并在每次尝试之间等待 5 秒。使用 sudo systemctl kill -s SIGKILL myapp.service 测试。SIGTERM 是默认信号,属于正常停止,不会触发 on-failure。监控 systemctl status,确认其在几秒内显示新的 PID。

如何让 systemd 服务以非 root 用户运行?

使用 sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp 创建系统账户,然后在 [Service] 部分添加 User=myapp。添加 NoNewPrivileges=truePrivateTmp=trueProtectSystem=strict,使进程仅拥有运行所需的最低权限。以非特权用户运行,是提高服务安全性的最重要单项措施。

服务为什么启动失败?

运行 systemctl status myapp.service 查看摘要,运行 journalctl -u myapp.service 查看完整输出。最常见的原因包括:ExecStart 中的路径错误、缺少 WorkingDirectoryUser= 无法读取文件导致权限错误,或编辑后忘记执行 sudo systemctl daemon-reload。journal 会显示程序自身的错误消息,通常会直接指出问题。