一、systemd 服务配置文件(.service)完全指南
1. 配置文件存放目录(按优先级从高到低)
| 优先级 | 路径 | 说明 |
|---|---|---|
| 高 | /etc/systemd/system/ |
管理员自定义或覆盖。所有手动编写的服务应放此处。 |
| 中 | /run/systemd/system/ |
运行时动态生成(重启后丢失)。通常由其他工具或脚本创建。 |
| 低 | /usr/lib/systemd/system/ |
软件包安装时自带的默认配置(如 docker、nginx)。不要直接修改 ,应在 /etc 下覆盖。 |
💡 覆盖规则 :
/etc/systemd/system/下的同名.service会完全替代低优先级目录中的同名文件,而非合并。
2. 配置文件结构(三个主要区块)
一个完整的 .service 文件通常包含以下三个区块:
[Unit] -- 通用信息与依赖
| 指令 | 说明 |
|---|---|
Description |
服务的简短描述。 |
After |
定义启动顺序(本服务在这些服务之后启动)。不强制依赖。 |
Before |
本服务在这些服务之前启动。 |
Requires |
强依赖 ------ 依赖服务启动失败,本服务也不会启动。 |
Wants |
弱依赖 ------ 依赖服务失败,本服务仍尝试启动。 |
Conflicts |
冲突 ------ 与本服务不能同时运行的服务。 |
[Service] -- 核心运行配置
| 指令 | 说明 |
|---|---|
Type |
启动类型: • simple(默认)-- 立即认为启动完成 • forking -- 父进程退出,子进程成为守护进程 • oneshot -- 执行一次即退出(常配 RemainAfterExit=yes) • notify -- 就绪后会通过 sd_notify() 通知 systemd • dbus -- 获得特定 D-Bus 名称后才算就绪 |
ExecStart |
启动命令(绝对路径)。可以写多个,按顺序执行。 |
ExecStartPre/Post |
启动前/后执行的命令。 |
ExecStop |
停止命令(不写则 systemd 会发送 SIGTERM)。 |
ExecReload |
重载配置的命令。 |
Restart |
何时重启:no, on-success, on-failure, always, on-abnormal |
RestartSec |
自动重启前等待的秒数。 |
User / Group |
运行服务的用户和组。 |
WorkingDirectory |
工作目录。 |
Environment |
设置环境变量,如 Environment="PATH=/usr/bin"。 |
EnvironmentFile |
从文件加载环境变量(适合敏感信息)。 |
StandardOutput |
标准输出去向:journal, file:/path, null 等。 |
StandardError |
标准错误去向,同上。 |
[Install] -- 安装与开机自启
| 指令 | 说明 |
|---|---|
WantedBy |
服务被哪个 target 需要。常用 multi-user.target(多用户命令行模式)。 |
RequiredBy |
强依赖的 target(较少用)。 |
Alias |
服务的别名。 |
Also |
当 enable 本服务时,同时 enable 的其他服务。 |
3. 核心操作流程与常用命令
3.1 编写/修改服务文件后
bash
# 1. 重新加载所有配置文件(必须执行)
sudo systemctl daemon-reload
# 2. 启动服务
sudo systemctl start myapp.service
# 3. 设置开机自启
sudo systemctl enable myapp.service
# 4. 查看状态(含最近日志)
sudo systemctl status myapp.service
# 5. 停止服务
sudo systemctl stop myapp.service
# 6. 重启服务(修改 ExecStart 等后需重启生效)
sudo systemctl restart myapp.service
# 7. 禁用开机自启
sudo systemctl disable myapp.service
3.2 查看服务日志(journalctl)
bash
# 实时跟踪日志
sudo journalctl -u myapp.service -f
# 显示最近 50 行
sudo journalctl -u myapp.service -n 50
# 只看今天的日志
sudo journalctl -u myapp.service --since today
# 从最新日志开始显示(跳到末尾)
sudo journalctl -u myapp.service -e
# 按时间范围
sudo journalctl -u myapp.service --since "2025-01-01 10:00:00" --until "2025-01-01 12:00:00"
4. 典型示例
示例 1:简单 Python 服务(常驻)
ini
[Unit]
Description=My Python Web App
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 /opt/myapp/main.py
Restart=on-failure
RestartSec=5
Environment="LOG_LEVEL=info"
EnvironmentFile=/etc/myapp/env.conf
[Install]
WantedBy=multi-user.target
示例 2:一次性任务(oneshot)
ini
[Unit]
Description=Backup Script
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/backup.sh
User=backup
[Install]
WantedBy=multi-user.target
5. 易错点与进阶提醒
| 易错点 | 说明 |
|---|---|
| 绝对路径要求 | ExecStart 等指令中的所有命令 必须使用绝对路径(如 /usr/bin/python3),不能用 python3 或 ~/script.sh。 |
| 管道与重定向失效 | 不能在 ExecStart 中直接写 ` |
| 环境变量隔离 | systemd 服务不会继承用户 shell 的环境变量(如 .bashrc)。必须通过 Environment= 或 EnvironmentFile= 显式提供。 |
| 引号与空格处理 | 若路径包含空格,需用反斜杠转义:/path/with\ space/app。 不要试图用双引号包裹整个命令(会失败)。 |
| Type 选择不当导致启动状态错误 | 守护进程化程序(如 fork())必须用 Type=forking,否则 systemd 会认为启动失败。 使用 Type=notify 的程序需要调用 sd_notify(),否则 systemd 会一直等待。 |
| 修改服务文件后未重启服务 | daemon-reload 只让 systemd 重新读取配置,不会自动重启 已运行的服务。必须手动 restart 才能使新 ExecStart 等生效。 |
| 日志查看权限 | 普通用户查看服务日志可能需要 sudo,除非将用户加入 systemd-journal 组。 |
| Restart=always 的危险性 | 如果服务因配置错误反复崩溃,Restart=always 会导致疯狂重启。可设置 StartLimitBurst=5 和 StartLimitIntervalSec=10s 限制重启次数。 |
6. 调试小技巧
- 模拟启动检查 :
systemd-analyze verify /etc/systemd/system/myapp.service可提前发现语法错误。 - 查看服务环境变量 :
systemctl show myapp.service --property=Environment或运行systemctl exec myapp env(需服务支持)。 - 临时覆盖配置 :使用
systemctl edit myapp.service会创建/etc/systemd/system/myapp.service.d/override.conf,无需手动创建目录。
这份指南既保留了原格式的清晰性,又补充了常见遗漏点,适合系统管理员和开发者快速上手。
二、相关案例
1. tomcat 压缩包安装配置
现在我把tomcat相关的软件包解压到了
/usr/local/tomcat目录中他的启动脚本路径在/usr/local/tomcat/bin/startup.sh
- 编写systemd文件,从而实现systemctl命令控制,比如
- systemctl stop tomcat
- systemctl start tomcat
- systemctl restart tomcat
- systemctl enable tomcat
- systemctl status tomcat
从而可以用上述命令对这个安装包产生的服务进行控制。
提问:
- 还需要指定PIDFile以便systemd跟踪主进程。
PIDFile文件的作用是什么?systemd跟踪主进程,这里的主进程指的的是tomcat的服务主进程吗?- 跟踪主进程的原理是什么?
- 下面这些环境变量的变量名是固定的吗?必须这样写吗?
Environment="JAVA_HOME=/usr/lib/jvm/default-java"
Environment="CATALINA_HOME=/usr/local/tomcat"
Environment="CATALINA_BASE=/usr/local/tomcat" - 这一行为什么要说"让 Tomcat 将 PID 写入指定文件"
Environment="CATALINA_PID=/usr/local/tomcat/tomcat.pid"
answer
- 还需要指定PIDFile以便systemd跟踪主进程。
PIDFile的作用是存储服务主进程的pid,为systemd管理服务的启动和停止提供条件。systemd跟踪主进程,这里的主进程指的就是tomcat的服务主进程。systemd通过PIDFile中存储的服务主进程pid从而对服务进行管理。
- 下面这些环境变量的变量名是固定的吗?必须这样写吗?
- 对的环境变量的变量名字是固定的,必须要这样写。
- 这些环境变量的名字根据不同的服务有不同的名字,具体参考软件包自带的说明文档。
其中:
JAVA_HOME变量是存储JDK环境的文件路径。
CATALINA_HOME存储的是tomcat的工作目录。
CATALINA_BASE一般情况下与CATALINA_HOME的内容相同。
CATALINA_PID环境变量存储的是包含服务主进程pid文件路径。
配置文件解析
配置模板
config
[Unit]
Description=Apache Tomcat Web Application Container
After=network.target
# 可选:如果 Tomcat 依赖某些文件系统挂载,可以加上 After=remote-fs.target nss-lookup.target
[Service]
Type=forking
# Tomcat 的启动脚本和停止脚本
ExecStart=/usr/local/tomcat/bin/startup.sh
ExecStop=/usr/local/tomcat/bin/shutdown.sh
# 重启策略:非正常退出时尝试重启
Restart=on-failure
RestartSec=10
# 指定 PID 文件路径(需要 Tomcat 能够写入该文件)
PIDFile=/usr/local/tomcat/tomcat.pid
# 运行服务的用户和组(建议创建专用的 tomcat 用户,避免使用 root)
User=tomcat
Group=tomcat
# 工作目录
WorkingDirectory=/usr/local/tomcat
# 设置环境变量(根据你的实际 Java 环境调整)
Environment="JAVA_HOME=/usr/lib/jvm/default-java"
Environment="CATALINA_HOME=/usr/local/tomcat"
Environment="CATALINA_BASE=/usr/local/tomcat"
# 让 Tomcat 将 PID 写入指定文件
Environment="CATALINA_PID=/usr/local/tomcat/tomcat.pid"
# 可选:限制资源使用
# LimitNOFILE=65536
# 日志输出(如果不希望日志混入 systemd journal,可以注释掉)
# StandardOutput=journal
# StandardError=journal
[Install]
WantedBy=multi-user.target
1. 重启策略
shell
# 重启策略:非正常退出时尝试重启
Restart=on-failure
RestartSec=10
含义:
Restart=on-failure:表示非正常退出时才会出发重启。RestartSec=10:表示重启前等待10秒。
2. 指定PIDFile的位置
shell
# 指定 PID 文件路径(需要 Tomcat 能够写入该文件)
PIDFile=/usr/local/tomcat/tomcat.pid
含义:
- systemd管理服务根据这个文件获取主服务的
pid。
3. 工作目录
shell
# 工作目录
WorkingDirectory=/usr/local/tomcat
含义:
- 指定服务的工作目录。
- 如果不指定,systemd默认以
/目录作为工作目录。 - 服务的配置中可能有相对路径位置,这个相对路径的起点由工作目录决定。
4. 环境变量
shell
# 设置环境变量(根据你的实际 Java 环境调整)
Environment="JAVA_HOME=/usr/lib/jvm/default-java"
Environment="CATALINA_HOME=/usr/local/tomcat"
Environment="CATALINA_BASE=/usr/local/tomcat"
# 让 Tomcat 将 PID 写入指定文件
Environment="CATALINA_PID=/usr/local/tomcat/tomcat.pid"
含义:
systemd启动的服务运行在一个干净、隔离的环境中 ,不会继承用户shell或登录会话中设置的环境变量,但是它可以读取PATH指定的位置的全局命令。- 服务运行的必要环境变量必须在这里进行声明。
Environment="JAVA_HOME=/usr/lib/jvm/default-java": 指定java的jdk目录。Environment="CATALINA_HOME=/usr/local/tomcat": 指定tomcat的家目录。Environment="CATALINA_BASE=/usr/local/tomcat": 指定tomcat的家目录,在单实例的情况下,这两个环境变量存储的值相同。Environment="CATALINA_PID=/usr/local/tomcat/tomcat.pid": 指定tomcat服务存储主服务进程的pid的文件的环境变量。如果不指定,systemd不能够通过/usr/local/tomcat/tomcat.pid获取主服务的pid。
实操验证
编辑tomcat.service文件及相关配置
- 在
/etc/systemd/system/tomcat.service文件中编写下面的内容。
shell
[Unit]
Description=Tomcat start script
After=network.target
[Service]
Type=forking
ExecStart=/usr/local/tomcat-11.0.18/bin/startup.sh
ExecStop=/usr/local/tomcat-11.0.18/bin/shutdown.sh
Restart=on-failure
RestartSec=5
PIDFile=/usr/local/tomcat-11.0.18/tomcat.pid
User=tomcat
Group=tomcat
WorkingDirectory=/usr/local/tomcat-11.0.18/
Environment="JAVA_HOME=/usr/local/jdk-21.0.10"
Environment="CATALINA_HOME=/usr/local/tomcat-11.0.18/"
Environment="CATALINA_PID=/usr/local/tomcat-11.0.18/tomcat.pid"
[Install]
WantedBy=multi-user.target
- 创建
tomcat用户和用户组
如果需要复习用户及用户组管理相关知识请跳转
shell
# 分别修改/etc/passwd,/etc/shadow,/etc/group,/etc/gshadow文件
echo "tomcat:888:888:tomcat service user:/usr/local/tomcat-11.0.18/:/sbin/nologin" >> /etc/passwd
echo "tomcat:!!:20478::::::" >> /etc/shadow
echo "tomcat:x:888:" >> /etc/group
echo "tomcat:!::" >> /etc/gshadow
- 修改
/usr/local/tomcat-11.0.18所属组和所有者。
shell
chown -R tomcat:tomcat /usr/local/tomcat-11.0.18
- 创建文件
/usr/local/tomcat-11.0.18/tomcat.pid
shell
touch /usr/local/tomcat-11.0.18/tomcat.pid
chown tomcat:tomcat /usr/local/tomcat-11.0.18/tomcat.pid
- 重新加载全部的
xxx.service文件
shell
systemctl daemon-reload
验证
shell
# 检测 systemctl start tomcat.service 是否能够正常启动tomcat服务。
[root@tomcat1 tomcat-11.0.18]# systemctl status tomcat.service
○ tomcat.service - Tomcat start script
Loaded: loaded (/etc/systemd/system/tomcat.service; disabled; preset: disabled)
Active: inactive (dead)
4月 10 11:57:43 tomcat1 systemd[1]: Stopped Tomcat start script.
4月 10 11:57:43 tomcat1 systemd[1]: tomcat.service: Consumed 4.701s CPU time.
4月 10 11:58:07 tomcat1 systemd[1]: Starting Tomcat start script...
4月 10 11:58:07 tomcat1 startup.sh[2193]: Tomcat started.
4月 10 11:58:07 tomcat1 systemd[1]: Started Tomcat start script.
4月 10 12:12:45 tomcat1 systemd[1]: Stopping Tomcat start script...
4月 10 12:12:46 tomcat1 shutdown.sh[2337]: Tomcat stopped.
4月 10 12:12:46 tomcat1 systemd[1]: tomcat.service: Deactivated successfully.
4月 10 12:12:46 tomcat1 systemd[1]: Stopped Tomcat start script.
4月 10 12:12:46 tomcat1 systemd[1]: tomcat.service: Consumed 7.051s CPU time.
[root@tomcat1 tomcat-11.0.18]# systemctl start tomcat.service
[root@tomcat1 tomcat-11.0.18]# systemctl status tomcat.service
● tomcat.service - Tomcat start script
Loaded: loaded (/etc/systemd/system/tomcat.service; disabled; preset: disabled)
Active: active (running) since Fri 2026-04-10 12:13:03 CST; 6s ago
Process: 2401 ExecStart=/usr/local/tomcat-11.0.18/bin/startup.sh (code=exited, status=0/SUCCESS)
Main PID: 2408 (java)
Tasks: 35 (limit: 12251)
Memory: 84.7M
CPU: 3.663s
CGroup: /system.slice/tomcat.service
└─2408 /usr/local/jdk-21.0.10/bin/java -Djava.util.logging.config.file=/usr/local/tomcat-11.0.18//>
4月 10 12:13:03 tomcat1 systemd[1]: Starting Tomcat start script...
4月 10 12:13:03 tomcat1 startup.sh[2401]: Tomcat started.
4月 10 12:13:03 tomcat1 systemd[1]: Started Tomcat start script.
# 检测服务是否可以使用。
[root@tomcat1 tomcat-11.0.18]# curl 192.168.122.17:8080
tomcat1 192.168.122.17
# 检测 systemctl stop tomcat.service 是否可以正常停止服务。
[root@tomcat1 tomcat-11.0.18]# systemctl stop tomcat.service
[root@tomcat1 tomcat-11.0.18]# systemctl status tomcat.service
○ tomcat.service - Tomcat start script
Loaded: loaded (/etc/systemd/system/tomcat.service; disabled; preset: disabled)
Active: inactive (dead)
4月 10 12:12:46 tomcat1 systemd[1]: Stopped Tomcat start script.
4月 10 12:12:46 tomcat1 systemd[1]: tomcat.service: Consumed 7.051s CPU time.
4月 10 12:13:03 tomcat1 systemd[1]: Starting Tomcat start script...
4月 10 12:13:03 tomcat1 startup.sh[2401]: Tomcat started.
4月 10 12:13:03 tomcat1 systemd[1]: Started Tomcat start script.
4月 10 12:13:27 tomcat1 systemd[1]: Stopping Tomcat start script...
4月 10 12:13:27 tomcat1 shutdown.sh[2478]: Tomcat stopped.
4月 10 12:13:27 tomcat1 systemd[1]: tomcat.service: Deactivated successfully.
4月 10 12:13:27 tomcat1 systemd[1]: Stopped Tomcat start script.
4月 10 12:13:27 tomcat1 systemd[1]: tomcat.service: Consumed 4.643s CPU time.
全部正常,实验完毕。