ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

用 systemctl 管理 Nginx:从安装到排错的全流程指南

用 systemctl 管理 Nginx:从安装到排错的全流程指南

一、为什么我坚持用 systemctl 管 Nginx,而不是直接 nginx -s reload

先说个背景。这些年我经手过不少 Linux 服务器,Nginx 装了没有一百次也有八十次。早期图省事,全是./configure && make && make install编译安装,启动靠/usr/local/nginx/sbin/nginx,重载靠nginx -s reload,开机自启靠写/etc/rc.local。说实话,那会儿自己折腾的服务器倒也没出过大乱子,直到后来帮别人维护一台编译安装的 Nginx,遇到开机不自启、重启后服务“神秘失踪”、进程管理全靠ps -ef | grep nginx的尴尬局面,我才彻底意识到:不用 systemd 管 Nginx,等于让服务处在“裸奔”状态。

很多人一提到 systemd 就觉得是“花架子”,觉得service nginx start和/usr/local/nginx/sbin/nginx效果差不多。实际上差距非常大。systemd 能帮你解决几件特别要命的事:开机自启、崩溃自动重启、日志集中管理、依赖关系编排,还有统一的服务状态查询。你手动敲命令启动的 Nginx,一旦机器宕机重启,它不会自己起来;进程被误杀,它也不会自动拉起。而 systemctl 管起来的服务,只要在 unit 文件里配了Restart=always,进程没了 systemd 会自动帮你重启,这就是生产环境和玩具环境最本质的区别。

这篇文章我会把基于软件源安装 Nginx、然后在 CentOS/RHEL 系和 Ubuntu/Debian 系下用 systemd 管理 Nginx 的完整流程讲一遍,涵盖了安装、配置、启动、开机自启、重载、排错、日志查看等一整套日常操作。所有内容都是我在真实服务器上跑过的,有些坑是通用的,有些命令是和发行版底层的 systemd 实现强相关的,我会明确告诉你区别在哪。

二、安装 Nginx 的两种主流方式,以及它们对 systemd 的影响

2.1 软件源安装:省心但版本略旧

在 CentOS 7 上,最简单的安装方式是:

sudo yum install -y nginx

在 Ubuntu 20.04 / 22.04 上,对应的是:

sudo apt update sudo apt install -y nginx

软件源安装最大的好处是:安装包里的 Nginx 已经内置了 systemd service 文件。装完之后你不需要自己写 unit 文件,systemctl start nginx直接就能用。这个过程像是装了一个自带“管家”的软件,服务文件、默认配置路径、PID 文件路径全被安排得明明白白。

但是这里有个容易被忽视的版本问题。比如 CentOS 7 默认源里的 Nginx 版本是 1.20.x 左右,Ubuntu 20.04 的默认源里是 1.18.x。对于大多数业务场景这个版本够用,但如果你需要 HTTP/3、更完善的 TLS 1.3 支持或者某些新模块特性,软件源版本可能满足不了你。这时候就需要第三方源或者编译安装了。

2.2 编译安装:灵活但 systemd 文件要自己写

编译安装的好处是能自定义模块、自定义安装路径、选择最新版本。比如我见过很多人把 Nginx 装到/usr/local/nginx,配置放/usr/local/nginx/conf/nginx.conf。

但问题也来了:编译安装默认不提供 systemd unit 文件,你要是想用 systemctl 管理,必须自己写一个 service 文件放到/etc/systemd/system/nginx.service,然后systemctl daemon-reload才能用。

自己写也并不麻烦,后面我会给出一个可以直接抄的模板。但先说结论:如果你不是对 Nginx 版本或模块有硬性要求,我建议先走软件源安装;如果真的需要编译安装,也请务必按这篇文章里的方法配好 systemd,否则以后维护会非常麻烦。

2.3 安装路径差异对照表,避免你找错文件

很多新手在“明明按照教程配了,却总报文件不存在”之类的坑里反复折腾,其实根因就是安装方式不同导致路径不同。我每次排查问题前,第一件事就是确认安装类型,再去找对应的路径。这里给一张常用对照表:

项目软件源安装(CentOS)软件源安装(Ubuntu)编译安装(默认参数)
可执行文件/usr/sbin/nginx/usr/sbin/nginx/usr/local/nginx/sbin/nginx
配置文件/etc/nginx/nginx.conf/etc/nginx/nginx.conf/usr/local/nginx/conf/nginx.conf
站点配置目录/etc/nginx/conf.d/etc/nginx/sites-enabled/usr/local/nginx/conf/conf.d
PID 文件/run/nginx.pid/run/nginx.pid/usr/local/nginx/logs/nginx.pid
日志目录/var/log/nginx/var/log/nginx/usr/local/nginx/logs

实际上在写 systemd unit 文件时,pid、ExecStart、ExecReload这几行都必须和你的实际路径匹配,否则轻则重载失败,重则启动后 systemd 认为服务没有正常启动而反复尝试拉起。我在 2.4 节会具体说。

三、软件源安装 Nginx 后,用 systemctl 管理的关键操作

3.1 安装后直接启动,并验证状态

以 Ubuntu 为例,安装完成后通常 Nginx 服务可能已经自动启动了。先检查一下状态:

sudo systemctl status nginx

如果显示Active: active (running),说明一切正常。如果显示inactive (dead),你手动启动一下:

sudo systemctl start nginx

然后确认端口已经监听:

sudo ss -tlnp | grep nginx

正常情况下可以看到 :80 和 :443 在监听。ss比旧的netstat更推荐,输出清晰,而且新版系统基本都自带。

3.2 设置开机自启,这个步骤千万别漏

很多服务器重启后 Nginx 没起来,十有八九就是没设置 enabled:

sudo systemctl enable nginx

enable 的本质是把服务符号链接到/etc/systemd/system/multi-user.target.wants/目录,这样系统进入多用户模式后就会自动启动。这里有个小细节:enable 只是创建启动链接,并不会立刻启动服务,所以如果你想马上启动,一般建议enable --now合并操作:

sudo systemctl enable --now nginx

--now会替你完成 enable 和 start 两件事,是我日常用得最多的指令。

3.3 修改配置后正确重载,而不是 restart

Nginx 配置改完之后,什么时候用 reload,什么时候用 restart,是有讲究的。reload 是平滑重载,不会中断正在处理的请求;restart 是先停旧进程再启新进程,必然会产生短时间断连。对于生产环境,能用 reload 就别轻易 restart。

sudo nginx -t

先测试配置是否正确,输出显示syntax is ok和test is successful后再执行:

sudo systemctl reload nginx

其实 reload 本质上就是向 Nginx 主进程发送 HUP 信号,让它重新加载配置。用 systemctl 管理的好处是,你不需要记信号量,它已经帮你包好了。

3.4 查看 Nginx 进程状态,判断是否异常

日常巡检时我经常用:

sudo systemctl status nginx -l

-l参数可以展示完整日志,免得日志行太长被系统截断。输出里除了状态,还会显示主进程 PID、任务数、内存占用和最近几行日志。如果你发现状态是active (running)但访问页面报 502 或 404,那多半不是 Nginx 进程的问题,而是上游服务或配置路径的问题,这需要结合 error.log 来排查。

3.5 停止、禁用的正确姿势

临时停止服务可以用:

sudo systemctl stop nginx

如果以后都不需要开机启动了:

sudo systemctl disable nginx

同样地,disable 不会立刻停止当前运行的服务,需要 stop 和 disable 分开执行。如果你想彻底停用且不保留自启,可以连写:

sudo systemctl disable --now nginx

disable --now就是 stop 加 disable 的合成操作,这也是我推荐的方式,因为一句话就能把两件事做干净。

四、编译安装 Nginx 后,手写 systemd unit 文件完整指南

4.1 为什么编译安装必须写 unit 文件

编译安装的 Nginx 不自己注册为系统服务,你手动执行/usr/local/nginx/sbin/nginx启动后,进程虽然在跑,但 systemd 完全不知道它的存在。带来的后果是:

  • 开机不会自启;
  • 进程异常退出后没人拉起;
  • 你没法用 systemctl 查看统一状态;
  • 超时管理、资源限制、安全隔离等 systemd 能力全部享受不到。

手动管理的服务器,重启后总有人在群里问为什么不访问了,这也是我强调一定要写 unit 文件的原因。

4.2 一份可以直接复制的 nginx.service 模板

假设我的编译安装路径是/usr/local/nginx,配置文件路径/usr/local/nginx/conf/nginx.conf,PID 文件路径/usr/local/nginx/logs/nginx.pid,那么 unit 文件内容如下:

[Unit] Description=A high performance web server and a reverse proxy server Documentation=man:nginx(8) After=network.target [Service] Type=forking PIDFile=/usr/local/nginx/logs/nginx.pid ExecStartPre=/usr/local/nginx/sbin/nginx -t -c /usr/local/nginx/conf/nginx.conf ExecStart=/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf ExecReload=/usr/local/nginx/sbin/nginx -s reload ExecStop=/usr/local/nginx/sbin/nginx -s stop PrivateTmp=true [Install] WantedBy=multi-user.target

把这个文件保存为/etc/systemd/system/nginx.service后,执行:

sudo systemctl daemon-reload sudo systemctl enable --now nginx

这里解释几个关键词的由来:

  • Type=forking:Nginx 启动时会 fork 出一个 daemon 进程。主进程启动后会把 PID 写入 pid 文件,systemd 靠读这个文件来确认服务是否真正起来。
  • ExecStartPre:在正式启动之前先跑一次配置测试,如果配置有问题,服务会启动失败并报错,而不是带病启动,这个设计非常实用。
  • ExecReload:使用nginx -s reload完成平滑重载,不需要停服务和启动服务两步操作。
  • ExecStop:使用nginx -s stop优雅停止,让 Nginx 自己完成退出流程,而不是被 systemd 强制 kill。
  • PrivateTmp=true:给服务独立的临时目录,提升安全性,这个参数对多实例部署尤其重要。

4.3 为什么 ExecStartPre 里必须加 -t

很多人在自己写的 unit 文件里漏掉了 ExecStartPre 这一行,结果配置写错了,systemctl start 也是“成功”的,因为 Nginx 主进程起来后发现配置有误,某些 worker 会挂掉,服务状态显示 active 但实际访问却有问题。加了-t后,配置错误会在启动前就被拦截,systemd 会直接报启动失败,并且错误信息里会写明哪一行配置有问题。这个习惯在多人协作的服务器上价值极大,能避免你改了配置后,别人启动时才发现一堆隐藏问题。

4.4 常见报错:PID file 不存在或路径不匹配

如果 unit 文件里的PIDFile路径和nginx.conf中设置的pid路径不一致,会出现一种奇怪的现象:systemd 启动后立即认为服务启动失败,显示类似 “PID file /usr/local/nginx/logs/nginx.pid not readable” 的错误。原因在于 Nginx 如果把 PID 写到了别的路径,systemd 读不到就只能判定进程没起来。解决办法有两个:一是修改 unit 文件里的PIDFile去和 nginx.conf 保持一致;二是在 nginx.conf 里显式指定pid /usr/local/nginx/logs/nginx.pid;来对齐 unit 文件。我倾向于第二种,因为配置文件的 pid 路径是单一定义点,unit 文件只是读取它的位置。

4.5 修改配置文件后,要让 systemd 感知新 unit

每次修改/etc/systemd/system/nginx.service后都要重新执行daemon-reload,否则 systemd 会继续用内存里的旧配置。我在帮别人排查问题时遇到过不少次这种情况:unit 文件已经改好了,但服务行为一点没变,因为忘了 reload。记住一个口诀:改 unit 文件之前先systemctl daemon-reload,改完后再 start/restart。

五、systemctl 管理 Nginx 的日常高频命令,直接收藏

5.1 常用命令速查表

我整理了一张高频命令表,平时粘贴到笔记里,随查随用:

操作命令说明
启动sudo systemctl start nginx立即启动服务
停止sudo systemctl stop nginx立即停止服务
重启sudo systemctl restart nginx先停止再启动,有瞬间断连
重载sudo systemctl reload nginx平滑加载配置,不中断请求
查看状态sudo systemctl status nginx显示进程、日志、资源占用
开机自启sudo systemctl enable nginx创建开机启动链接
取消自启sudo systemctl disable nginx移除开机启动链接
合并启动并自启sudo systemctl enable --now nginx推荐用法
合并停止并取消自启sudo systemctl disable --now nginx推荐用法
查看服务是否自启sudo systemctl is-enabled nginx返回 enabled 或 disabled
查看服务是否运行sudo systemctl is-active nginx返回 active 或 inactive
查看服务详细配置systemctl cat nginx列出 unit 文件内容

5.2 查看 Nginx 日志的正确姿势

用 systemd 管 Nginx 后,日志主要有两个来源:

  • Nginx 自身的访问日志和错误日志,默认在/var/log/nginx/access.log和/var/log/nginx/error.log;
  • systemd 的 journal 日志,可以用journalctl -u nginx查看。

我最喜欢的组合是:

sudo journalctl -u nginx -f

-f是 follow 模式,相当于实时滚动日志,排查问题时一眼就能看到最新输出。如果你只想看最近 100 行:

sudo journalctl -u nginx -n 100

如果想看某个时间段的日志:

sudo journalctl -u nginx --since "2025-01-04 10:00:00" --until "2025-01-04 11:30:00"

journald 和 syslog 不一样,它把二进制日志存到/var/log/journal,查询速度快,过滤方便。不过需要注意,日志量会持续增长,如果磁盘空间紧张,记得配置 journald 的SystemMaxUse参数,或者定期journalctl --vacuum-size=500M清理旧日志。

六、我最常遇到的 6 个 Nginx systemd 坑及解决办法

6.1 改了配置后 reload 不起作用

有时候你改了 Nginx 配置,执行sudo systemctl reload nginx,发现页面没有变化。先别急着重启,第一步跑一下nginx -t确认语法没问题,第二步确认你改的是不是实际生效的配置文件。软件源安装和编译安装的配置路径不一样,我在 2.3 节提过。还有种情况是站点配置用了软链,比如 Ubuntu 的sites-enabled目录里的文件是指向sites-available的链接,你改了源文件但没注意权限问题,Nginx worker 进程可能根本没有读权限。

6.2 启动时提示 98: Address already in use

这个提示说明 80 或 443 端口被其他进程占用了。常见的原因是有其他 Nginx 实例在跑,或者 Apache、Caddy 之类的 Web 服务占用了端口。排查命令:

sudo ss -tlnp | grep -E ':80|:443'

找到 PID 后,用ps -fp PID看进程,确认是不是旧版 Nginx 没有停止干净。如果是旧实例,先把旧进程杀掉再启动 systemd 服务:

sudo systemctl stop nginx sudo pkill -9 nginx sudo systemctl start nginx

我不建议轻易pkill -9,因为强杀 Nginx 会导致正在处理的请求异常中断。更稳妥的是平滑停止:

sudo /usr/local/nginx/sbin/nginx -s stop

再启动 systemd 服务。

6.3 编译安装后 systemctl status 显示 failed

如果你编译安装后按我 4.2 节的方法写好了 unit 文件,但启动时 status 显示 failed,常见原因有三个:

  • ExecStart指向的二进制路径不对,文件不存在;
  • PIDFile路径和 nginx.conf 不一致;
  • 启动时 Nginx 检测到配置错误,比如路径不存在、目录权限不足。

排查方法很直接:用journalctl -u nginx -n 50查看日志,系统会明确告诉你哪一步出了问题。我看到很多人在这一步卡壳,是因为日志里有Permission denied,但 nginx 的 master 进程是以 root 身份运行的,worker 进程则是nginx用户,配置目录如果权限太严,worker 读不到文件就会报错。解决办法是给目录加适当的读权限,或者把user nginx;改成实际运行用户。

6.4 端口通了但访问页面报 403 Forbidden

403 的常见原因不是 systemd 管理的问题,而是 index 文件不存在或者目录权限不对。但有一点和 systemd 相关:如果服务是由 systemd 启动的,它默认继承的 umask 往往和手动启动不同。我遇到过一种情况:手动启动时正常,systemd 启动后 403,后来发现是 web 根目录的.html文件权限是 600,只有 root 能读。手动启动时 worker 进程是 root 身份,自然能读;systemd 启动时某些配置下 worker 用户成了 nginx,读不了就 403 了。所以在权限问题上,别只盯着配置文件,多看看 owner 和 group。

6.5 服务显示 active 但访问白屏或 502

这种问题通常不是 Nginx 本身挂了,而是上游的 PHP-FPM 或后端应用服务没起来。判断方法很简单:curl -I http://127.0.0.1,如果 Nginx 正常,至少会返回 200 或 404 之类的状态码;如果是 502,说明 Nginx 无法连接 FastCGI 进程。这个时候你先去检查 PHP-FPM 或者后端服务的 systemd 状态:

sudo systemctl status php8.1-fpm

很多服务器上 PHP-FPM 没有设置开机自启,重启后 Nginx 起来了但 PHP-FPM 没起,就会造成 502。这也是为什么我建议在用 systemd 管理 Nginx 时,后端相关服务也统一纳入 systemd,这样整个链路的自启和依赖关系都清清楚楚。

6.6 日志权限不对导致 Nginx 无法写日志

如果你把日志路径改到自定义目录,又没有设置正确的权限,Nginx worker 进程可能写不进日志。一个常见的诡异现象是:Nginx 能正常响应请求,但 access.log 是空的。这往往就是文件权限问题。用 systemd 管理模式中,进程可能受到更多沙箱权限限制,比如ReadOnlyPaths、ProtectSystem等,如果你在 unit 文件里开了这些参数,但日志目录不在允许列表内,服务就会写不进去。解决方法就是要么把日志目录加入可写路径,要么直接关闭 ProtectSystem 限制,具体看你的安全要求。

七、从手动启动切换到 systemctl 管理的全套迁移流程

7.1 先确认现有环境和进程状态

如果你目前是手动启动 Nginx 的老环境,想迁移到 systemd 管理,别一上来就杀进程。先做这些:

ps -ef | grep nginx cat /usr/local/nginx/logs/nginx.pid nginx -V

nginx -V能看到编译参数,搞清楚现有的安装路径和模块情况,这些信息直接决定 unit 文件该怎么写。

7.2 写好 unit 文件,再逐步切换

按照第 4 节的模板写好/etc/systemd/system/nginx.service,然后:

sudo systemctl daemon-reload sudo systemctl enable nginx

此时先不要急着 start,因为可能有旧的手动进程还在跑。你可以执行:

sudo systemctl start nginx

如果端口被占用了,先停掉旧进程,再启动 systemd 管理的新实例:

sudo /usr/local/nginx/sbin/nginx -s stop sudo systemctl start nginx

确认启动成功后:

sudo systemctl status nginx

7.3 迁移后的验证清单

迁移完成后我建议你按这个清单逐项检查:

  • systemctl is-active nginx返回 active;
  • systemctl is-enabled nginx返回 enabled;
  • systemctl status nginx没有异常日志;
  • 访问站点测试正常;
  • journalctl -u nginx -n 20没有报错。

如果以上全部通过,你的 Nginx 就正式由 systemd 接管了。以后无论是重启服务器、进程崩溃恢复,还是统一查看日志,体验都会流畅很多。

八、实操心得与个人建议

我在实际维护中最大的体会是,systemd 管理 Nginx 不是“赶时髦”,而是把服务生命周期管理标准化。早期手动启动 Nginx 的方式,进程的启停完全靠人记,今天你记得nginx -s reload,明天换个人可能就kill -9硬杀,这种随意性在服务器一多以后一定是灾难。用 systemd 以后,所有操作入口统一收敛到systemctl,命令简单,状态透明,日志集中,后期排查问题的路径也清晰得多。

另一个建议是:如果你管理的服务器不止一台,最好把 Nginx 的 unit 文件纳入配置管理工具统一分发,比如用批量执行工具把同一个nginx.service下发到所有机器,避免每台机器手动写、路径不一致、配置漂移。这个问题我在接手多台服务器时深有体会,每台的 Nginx 安装方式、路径、日志位置千奇百怪,排查问题时得先花时间摸清环境。后来统一了 unit 文件和目录规范,服务器运维的复杂度直接降了一个量级。

最后再分享一个小技巧:写 unit 文件时,ExecStartPre里的nginx -t一定不要省,哪怕你觉得配置绝不可能出错。因为 Nginx 配置里允许 include 多个子配置文件,别人可能随手改动某个子配置但没测试,你启动服务时就能被-t拦下来,而不是等到线上访问异常才发现问题。这个习惯帮我避免过好几次事故,希望对你也有用。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进