举例:mybot.service
/etc/systemd/system/mybot.service
下面把一个标准的 /etc/systemd/system/mybot.service 文件每一行(每个关键键值对)都逐一详细解释清楚,方便你完全理解并根据自己的程序灵活修改。
[Unit]
Description=我的 Telegram/Discord 机器人
After=network.target
# Wants=postgresql.service
# Requires=mariadb.service
# Before=nginx.service
[Service]
Type=simple
User=sammy
Group=sammy
WorkingDirectory=/home/sammy/telegram-bot
Environment="PATH=/home/sammy/telegram-bot/venv/bin"
Environment=TOKEN=abcd1234yourbottoken
ExecStart=/home/sammy/telegram-bot/venv/bin/python bot.py
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=5
KillMode=process
TimeoutStopSec=30
StandardOutput=journal
StandardError=journal
# LimitNOFILE=65535
# MemoryMax=2G
[Install]
WantedBy=multi-user.target
【[Unit] 段】——描述服务本身以及依赖关系
| 键 | 含义 | 常见取值 / 举例 |
|---|---|---|
| Description 描述 | 服务简要描述,systemctl status 时会显示 | 随便写,越清楚越好 |
| After 后 | 本服务必须在哪些单元启动之后才能启动(软性依赖) | After=network.target mysql.service redis.service |
| Before 以前 | 本服务必须在哪些单元启动之前启动 | Before=nginx.service |
| Wants 想 | 弱依赖:启动本服务时顺便尝试启动这些单元,失败也不影响本服务 | Wants=network-online.target 想要=network-online.target |
| Requires 需要 | 强依赖:这些单元没启动成功,本服务也会启动失败 | Requires=docker.service |
| Documentation 文档 | 文档链接 | Documentation=https://github.com/xxx/mybot 文档=https://github.com/xxx/mybot |
【[Service] 段】——真正控制进程怎么跑
| 键 | 含义 | 推荐/常见取值 |
|---|---|---|
| Type 类型 | 进程启动类型(极重要!选错了会启动失败或状态异常) | simple(最常用)、forking、oneshot、notify、idle |
| • simple(默认):ExecStart 的进程就是主进程 | 99% 的 Python/Node/Go 程序都用这个 | |
| • forking:程序自己 fork 后父进程退出(如传统 nginx、sshd) | 用这个时必须配合 PIDFile= | |
| • oneshot:执行完就退出(如批量脚本) | 配合 RemainAfterExit=yes | |
| User / Group 用户 / 组 | 以哪个用户/组运行进程(强烈建议不要用 root) | User=sammy Group=www-data User=sammy Group=www-data |
| WorkingDirectory 工作目录 | 进程的工作目录(相当于 cd 进这个目录再启动) | /home/sammy/mybot |
| Environment 环境 | 设置环境变量(可写多行) | Environment="PATH=/opt/venv/bin" Environment=“PATH=/opt/venv/bin” |
| ExecStart 执行启动 | 真正要执行的启动命令(只能有一行主命令) | /usr/bin/python3 app.py /usr/bin/python3 的 app.py |
| ExecStartPre | 启动前先执行的命令(可多行,用 + 开头表示即使失败也继续) | ExecStartPre=/usr/bin/test -f /tmp/ready |
| ExecReload | 系统 CTsystemctl reload 时执行的命令 | /bin/kill -HUP $MAINPID (最常见) /bin/kill -HUP $MAINPID (最常见) |
| ExecStop 执行 | 系统 CTsystemctl stop 时执行的命令 | |
| Restart 重新启动 | 自动重启策略(神级选项) | no、on-failure、on-abnormal、on-abort、always(最常用) 不,on-failure,on-abnormal,on-abort,总是(最常用) |
| RestartSec 重启安全 | 每次重启间隔多少秒 | 3、5、10 |
| KillMode 杀戮模式 | 如何杀死进程 | process(只杀主进程,推荐)、control-group(杀整个进程树) |
| TimeoutStopSec 暂停停止安全 | 停止服务时最多等多少秒,超时就 kill -9 | 30(默认 90s) |
| StandardOutput / StandardError 标准输出 / 标准误差 | 标准输出和错误输出交给哪里 | journal(推荐)、syslog、file:/var/log/mybot.log journal(推荐)、syslog、file:/var/log/mybot.log |
【[Install] 段】——开机自启相关
| 键 | 含义 | 常见取值 |
|---|---|---|
| WantedBy 通缉 | 启用服务时(systemctl enable)会把符号链接放到哪个 target 下面 | multi-user.target(无图形界面服务器) graphical.target(带桌面) |
| Alias 别名 | 给服务起个别名 | Alias=mybot.service |
实战小贴士(根据不同程序调整的关键点)
| 程序类型 | Type= 类型= | Restart= 重启= | 其他注意事项 |
|---|---|---|---|
| Python 脚本 | simple 简单 | always 总是 | WorkingDirectory + 虚拟环境 PATH |
| Node.js (pm2 不用) Node.js (pm2 不用) | simple 简单 | always 总是 | |
| Go 编译后的二进制 | simple 简单 | always 总是 | |
| 传统 daemon(自己 fork) | forking 分 叉 | always 总是 | 必须加 PIDFile=/run/xxx.pid |
| Java Jar | simple 简单 | always 总是 | 加 -Xmx -Xms,建议用 exec java -jar |
| 只需要跑一次的脚本 | oneshot 短篇 | no | RemainAfterExit=yes |
| Docker 容器 | simple 简单 | on-failure 失效时 | ExecStart=/usr/bin/docker start mycontainer |
这样你就完全掌握了 systemd service 文件每一行的意义,可以随意写出任何程序的生产级服务配置了。如果你把你要跑的具体程序(比如 frp、Ollama、qd、Aria2、WebUI、frp 内网穿透、Minecraft 服务器等)告诉我,我直接给你一份 100% 可复制粘贴的完美 .service 文件。
User / Group为什么强烈建议不要用 root?
强烈建议不要用 root 运行自己的程序(无论是 systemd 服务、tmux、nohup、docker),原因全部都是安全,一旦出事就是“全站失守”级别的事故。下面用最直白的方式给你讲清楚为什么必须用普通用户。
| 风险场景 | 用 root 运行会怎样? | 用普通用户(比如 sammy)运行会怎样? | 实际血案举例(真实发生过) |
|---|---|---|---|
| 程序有远程代码执行漏洞(RCE) | 黑客直接拿到 root shell,整台服务器彻底沦陷 | 最多拿到 sammy 用户权限,黑客还得再提权才能进一步搞破坏 | 2024 年 xz-utils 后门、2021 年 Spring4Shell、无数 Python 机器人 RCE |
| 程序被爆破弱密码登录(SSH、Web、数据库) | 黑客直接 root 权限登录 | 只能以普通用户登录,还需要本地提权漏洞才能进一步控制 | 无数被挖矿的服务器都是因为 SSH 弱密码 + root 运行的服务 |
| 程序写文件时不小心写错路径 | 会直接覆盖 /etc、/root、/bin 下的关键文件,导致系统损坏或留下后门 | 最多只能破坏自己的家目录,其他系统文件根本碰不到 | 有人写日志写成 > /etc/passwd,直接把自己锁死 |
| 被拖库 / 脱裤(程序里硬编码了 token) | 黑客拿到 root,配合 token 可以直接控制你的云服务器、GitHub、Telegram Bot 等 | 普通用户就算 token 泄露,黑客也只能控制 bot,无法动服务器本身 | 无数 Telegram Bot token 被泄露后服务器被删库的就是 root 运行的 |
| 程序崩溃或被 kill -9 | 有可能留下 root 权限的残留进程或套接字,增加被利用风险 | 影响范围仅限当前用户 | |
| 服务器被云厂商安全扫描(阿里云、腾讯云) | 直接红警报“发现 root 权限服务暴露”,可能被强制下线或封号 | 基本没事,云厂商只关心 root 权限的服务 | 很多人的 frp、Ollama、WebUI 因为用 root 跑被阿里云直接停机 |
真实世界里踩过的大坑(2023–2025 年常见)
- 用 root 跑 Ollama → 被 RCE → 服务器被种矿 → 电费几万块
- 用 root 跑青龙面板/NoneBot → token 被爬 → 服务器被删库 + 挖矿
- 用 root 跑 frp 内网穿透 → 被暴力破解 → 公司内网全暴露
- 用 root 跑 Stable Diffusion WebUI → 被扫描到 7860 端口弱口令 → 显卡被远程挖矿
正确的做法(几秒钟就能搞定)
# 1. 先创建一个专用普通用户(强烈推荐每个服务一个用户)
sudo adduser --disabled-password --gecos "" mybot
# 或者最低权限专用用户
sudo adduser --system --no-create-home mybot
# 2. 把代码、虚拟环境 chown 给这个用户
sudo chown -R mybot:mybot /opt/mybot
# 3. systemd 里写上
User=mybot
Group=mybot # 或者 Group=nogroup
极简安全原则(记住这三条就永远不会被入侵)
- 任何自己编译/下载的程序,绝不用 root 跑
- 任何对外暴露端口的服务(80、443、22、frp、WebUI),必须用普通用户
- 实在不知道用哪个用户,就新建一个专门给这个程序用的用户
一句话总结:
用 root 跑服务 = 把服务器前门大开着写“欢迎来黑我”
用普通用户跑服务 = 就算程序 0day 被打,也只是丢一个号,服务器还能保住你现在跑的任何东西(机器人、WebUI、frp、游戏服务器),如果还在用 root,赶紧改掉,真的是一键避免 99% 的血泪史。
Comments