在Linux服务器上装Nginx这件事,我这些年干了几百次。如果你问我最怕碰到什么,不是编译报错,而是编译完之后执行 systemctl start nginx,屏幕上弹出一行 Unit not found——那种挫败感,新手能原地崩溃。
这篇内容围绕 Linux 下安装 Nginx、以及用 systemctl 接管 Nginx 的完整过程展开。我会从“要不要源码编译”讲起,再到依赖安装、configure 参数选择、单元文件逐行解析,最后把我在实际环境中踩过的坑整理成一份排错手册。不管你是刚入门的小白,还是已经写过很多配置的老运维,这篇都能给你一些值得抄作业的东西。
1. 动手之前:先想清楚装哪种 Nginx
1.1 包管理器安装:快是快,坑也不少
很多教程一上来就是 yum install nginx 或者 apt install nginx,操作确实快,装完就能用,而且包管理器会自动把 systemd 单元文件、日志轮转、默认目录结构都给你安排好。这一点必须承认,对开发环境或者临时测试来说,包管理器安装是效率最高的方案。
但坑也很明显。第一是版本滞后,CentOS 自带的 Nginx 版本往往停留在 1.18 甚至更老,你想要的 HTTP/2、Stream 模块、新指令支持可能都不完整。第二是模块固化,包管理器编译时的模块列表是发行版维护者定的,你想加一个 echo 模块、brotli 压缩模块,或者想开启 stream_ssl_module,基本无从下手。第三是路径分散,配置文件在 /etc/nginx,日志在 /var/log/nginx,二进制在 /usr/sbin/nginx,看起来规范,但如果你后面想统一纳管、迁移环境,这种分散反而增加成本。
所以我不太建议生产环境无脑用包管理器安装。除非你只是为了在本机快速跑一个静态站点,或者对版本没有诉求、团队也统一用某个发行版自带的 Nginx,那另当别论。
1.2 源码编译安装:一次折腾,长期受益
源码编译安装,说白了就是自己掌控一切。版本可以选最新的稳定版,比如 Nginx 1.24.x 或者主线版 1.25.x;编译参数可以精确到每个模块;安装路径可以统一放到 /usr/local/nginx 下面,整个目录自成体系,升级、备份、迁移都方便。尤其是像 reverse proxy、四层 TCP 转发、状态监控这类需求,编译时就带上对应模块,后面根本不慌。
缺点也明确:依赖多、耗时长、还要手写 systemd 单元文件。你可能会问,既然这么折腾,为什么我还要推荐源码安装?我的回答是:一次折腾,长期受益。编译安装完成之后,日常改动基本都在 conf 目录和 service 文件,你很少需要重新编译。而包管理器安装后期想升级 Nginx 版本,或者加一个第三方模块,反而可能陷入换源、卸载、重装的循环。
这两条路线的差异,我整理了一个表,方便你直接对照做决策:
| 对比项 | 包管理器安装 | 源码编译安装 |
|---|---|---|
| 安装速度 | 快,分钟级 | 慢,依赖齐全也要十几分钟 |
| 版本可选性 | 受发行版仓库限制 | 任意版本 |
| 模块可定制性 | 低,发行版预置 | 高,完全自主 |
| 安装路径 | 分散(/etc、/var、/usr/sbin) | 集中在自定 prefix |
| systemd 支持 | 自带单元文件 | 需要手写 |
| 生产环境推荐度 | 一般 | 推荐 |
| 适合场景 | 开发、快速验证 | 生产、长期维护 |
我的结论很简单:如果你问的是服务器上长期跑业务,直接走源码编译;如果只是临时开个服务验证想法,那用包管理器。
2. 源码安装 Nginx 全流程(实测步骤与参数解读)
2.1 环境准备:装齐四类依赖
源码编译 Nginx 之前,需要确认系统里有这几样东西:C 编译器、make 工具,以及 PCRE、zlib、OpenSSL 三个开发库。很多新手挂在第一步,不是不会敲命令,而是不知道为什么要装这些,我就顺便把每个库的作用讲清楚。
- gcc + make:源码是 C 语言写的,必须有编译器才能把源码变成可执行文件,make 则负责按规则自动编译。
- pcre / pcre2:Nginx 的 location 匹配、rewrite 重写规则都依赖正则表达式,而这个能力就是 PCRE 库提供的。注意有些新版 Nginx 用的是 pcre2,安装的时候留意一下系统仓库里提供的包名。
- zlib:用于实现 gzip 压缩。Nginx 在发送静态资源前对内容做压缩,靠的就是它,不装的话 --with-http_gzip_module 会编不过。
- openssl:HTTPS 的基础。证书下发、TLS 握手、SSL 终止都依赖 OpenSSL 库。如果你想用 HTTP/2、HTTPS 反向代理,这个库必须装。
在 CentOS/Rocky/AlmaLinux 系列上,执行:
yum install -y gcc make pcre-devel zlib-devel openssl-devel在 Ubuntu/Debian 上,命令改成:
apt-get update apt-get install -y build-essential libpcre3-dev zlib1g-dev libssl-dev注意 CentOS 系列装的是带 -devel 后缀的开发包,Ubuntu 对应的是 libxxx-dev。很多 configure 报错,就是因为只装了运行库,没装开发库,头文件找不到。
2.2 下载、解压与 configure 参数详解
依赖装齐之后,去 Nginx 官网下载源码包。我记得第一次装的时候图省事,随便找了个搜索引擎里的旧版本链接,结果编译到一半报错,后来规矩了,只从 nginx.org/download/ 下面拉稳定版。
wget https://nginx.org/download/nginx-1.24.0.tar.gz tar -zxvf nginx-1.24.0.tar.gz cd nginx-1.24.0进入源码目录后,最关键的一步是执行 configure。这一步可以理解为“量房设计”:告诉 Nginx 你准备把房子盖在哪、要哪些功能模块。它检查系统环境、生成 Makefile,后面 make 命令就按照这个 Makefile 来施工。我的常用参数是这样:
./configure \ --prefix=/usr/local/nginx \ --with-http_ssl_module \ --with-http_stub_status_module \ --with-http_gzip_static_module \ --with-http_realip_module \ --with-stream \ --with-stream_ssl_module每个参数都有它存在的理由:
| 参数 | 作用 | 说明 |
|---|---|---|
| --prefix=/usr/local/nginx | 安装根目录 | 指定以后所有文件都装到这里 |
| --with-http_ssl_module | 开启 HTTPS 支持 | 不装这个,ssl 配置全废 |
| --with-http_stub_status_module | 提供状态页 | 开启 /nginx_status 监控页 |
| --with-http_gzip_static_module | 预压缩静态文件 | 搭配 gzip 指令使用 |
| --with-http_realip_module | 获取真实客户端 IP | 反向代理场景必备 |
| --with-stream | 四层代理模块 | TCP/UDP 转发、四层负载均衡 |
| --with-stream_ssl_module | 四层代理的 SSL 支持 | 配合 stream 用于 TLS 转发 |
这里我特别提醒一句:--prefix 一旦定下来,后面所有路径都以它为基准。改 prefix 不是改个参数重新 make install 就完事那么简单,日志路径、PID 路径、配置文件里的相对路径都可能跟着乱。所以开工前想清楚,装完再搬家很痛苦。
2.3 编译安装与启动验证
configure 通过以后,进入编译安装环节。这里有个小技巧,用 nproc 查看 CPU 核数,然后并行编译,能明显缩短时间。
make -j$(nproc) make install如果编译过程中报错,比如某个头文件找不到,先不要慌。检查一下是不是对应依赖没装全,修正后执行 make clean,再重新 configure。我见过有人编译失败后直接换个版本重新下载,结果下一个版本依赖更缺,反而把自己绕进去了。
安装完成后,先验证一下二进制和配置:
/usr/local/nginx/sbin/nginx -V-V 会输出 Nginx 版本号以及编译时的 configure 参数,用来确认模块是否带上。接着检查配置语法:
/usr/local/nginx/sbin/nginx -t看到 syntax is ok 和 test is successful 这两行,说明配置没问题。然后启动:
/usr/local/nginx/sbin/nginx curl http://localhostcurl 能返回 Welcome to nginx! 的 HTML,安装这步就算彻底完成了。如果 curl 被拒,第一步去 /usr/local/nginx/logs/error.log 看日志,比瞎猜高效得多。
3. 用 systemctl 管理 Nginx:手写系统单元文件
3.1 为什么源码安装后没有自带的 systemd 支持
源码安装完成后,你会发现一个很尴尬的情况:systemctl start nginx 直接报 Unit not found。原因很简单,systemd 本身不认识任何服务,它只认 /usr/lib/systemd/system 和 /etc/systemd/system 下的 .service 单元文件。包管理器安装的 Nginx 自带了这个文件,所以开箱即用;源码安装的 Nginx 只是把二进制和配置丢到了 /usr/local/nginx 目录下,没有往 systemd 的目录里注册任何东西。
很多教程到这里就停了,让你直接 /usr/local/nginx/sbin/nginx 启动。进程确实能跑,但你会发现几个后续问题:服务器重启后 Nginx 不会自动跟着起来;进程万一崩了没人自动拉起;想用 systemctl status 查看状态也查不了。生产环境里这几点每一条都够你喝一壶的。
所以我的建议是,源码安装完 Nginx,第一件事就是给它补一张“身份证”——自己写一个 nginx.service 单元文件。这也是本文最核心的部分。
3.2 nginx.service 单元文件逐行解析
在 /etc/systemd/system/ 目录下新建文件,名字就叫 nginx.service:
vim /etc/systemd/system/nginx.service内容如下,我贴的是我在生产环境实际使用的版本:
[Unit] Description=nginx - high performance web server Documentation=http://nginx.org/en/docs/ After=network-online.target Wants=network-online.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 quit PrivateTmp=true LimitNOFILE=102400 [Install] WantedBy=multi-user.target这个文件不长,但每一行都值得抠一下。先说 [Unit] 段:
- Description:服务描述,执行 systemctl status nginx 时会在头部显示,方便辨识。
- After=network-online.target:指定 Nginx 必须在网络完全就绪之后再启动。这里用 network-online.target 而不是 network.target,是因为 network.target 只表示网络服务被加载,不代表网卡已经拿到 IP。如果 Nginx 配置里有 proxy_pass 指向某个域名,或者静态文件放在 NFS 挂载目录上,网络没就绪就启动,解析和挂载都会失败。
再看 [Service] 段,这一段的坑最多:
- Type=forking:告诉 systemd,这个服务启动时会 fork 出子进程。Nginx 的进程模型是 master-worker,master 进程启动后会 fork 出 worker 进程,然后自己在后台继续运行。systemd 通过 Type=forking 来理解这种模式,并借助 PIDFile 追踪真正的主进程。
- PIDFile=/usr/local/nginx/logs/nginx.pid:Nginx 启动后会把 master 进程的 PID 写入这个文件。systemd 依赖 PID 判断服务是活着还是挂了。如果这个路径和 nginx.conf 里 pid 指令指定的路径不一致,就会出现 3.3 里说的“启动失败但进程在跑”的诡异现象。
- ExecStartPre:启动前执行的命令,这里用 nginx -t 做配置预检。这是整个单元文件里最值钱的一行——它保证了配置有语法错误时,systemd 会拒绝启动服务,而不是等 Nginx 启动失败后再去翻日志。
- ExecStart:真正的启动命令。注意用绝对路径加 -c 显式指定配置文件,避免系统 PATH 或者工作目录不一致导致找不到文件。
- ExecReload:执行 systemctl reload nginx 时调用的命令。nginx -s reload 会向 master 进程发信号,master 会加载新配置并平滑重启 worker,整个过程不断服务。这是 Nginx 最优雅的配置生效方式。
- ExecStop:停止命令。这里特意用 -s quit 而不是 -s stop。两者的区别是:quit 是优雅退出,worker 会处理完当前正在进行的请求再退出;stop 是立即终止,正在传输的请求直接断掉。生产环境我永远用 quit。
- PrivateTmp=true:给服务分配独立的临时目录,算是一个安全加固项,隔离 Nginx 和系统其他进程的 /tmp 访问。
- LimitNOFILE=102400:提高进程能打开的文件描述符上限。systemd 默认的进程文件描述符限制对高并发场景不够用,加这一行能避免你在压测时忽然发现连接数上不去。
最后的 [Install] 段:
- WantedBy=multi-user.target:表示在系统进入多用户模式时启动这个服务。multi-user.target 是 Linux 常规运行级别的对应目标,写这一行之后,systemctl enable nginx 才能把服务注册进开机自启列表。
3.3 从启用到开机自启:常用管理命令
服务文件写完,第一步一定是刷新 systemd,让它识别新注册的单元文件:
systemctl daemon-reload不执行这一步,systemd 会一直当你这个文件不存在。然后依次执行:
systemctl enable nginx systemctl start nginx systemctl status nginxenable 只是注册开机自启,不立即启动服务;start 才是立刻拉起进程。如果你希望“启动 + 自启”一步到位,可以写成:
systemctl enable --now nginxservice 文件类型直接配了 reload 命令,所以日常改配置后的重载是:
systemctl reload nginxreload 和 restart 的区别要搞清楚:reload 不断连接,平滑重载配置;restart 会先停再启,会有短暂断流。修改了 listen 端口这类必须重启才能生效的配置,才需要 restart。
验证开机自启是否注册成功:
systemctl is-enabled nginx返回 enabled 就正常。此时 ls /etc/systemd/system/multi-user.target.wants/ 能看到一个 nginx.service 软链接,指向 /etc/systemd/system/nginx.service。想看服务实时日志,用:
journalctl -u nginx -f这条命令会把 Nginx 的标准输出和错误输出以及 systemd 对服务的启停记录都拉出来,很多时候排查“不知道服务为什么挂了”比看 Nginx 自己的日志更直观。
4. 实战排坑:安装和接入 systemd 时的七类问题
4.1 端口被占、依赖缺失这类“拦路虎”
案例一:configure 报错,提示 checking for C compiler ... not found。这就是没装 gcc。我当时在一台最小化安装的 CentOS 7 上犯过这个错,最小化镜像默认不带开发工具包。解决方法是先执行 yum groupinstall "Development Tools",把编译器、make 等一次性补齐,再回来 configure。
案例二:configure 报错,提示 the HTTP rewrite module requires the PCRE library。这就是没装 pcre-devel。同样是依赖缺失,按第 2.1 节把 pcre-devel 装上即可。这里有个教训:看报错要看最后几行,人家已经把缺失的库名字告诉你了,不要从头开始猜。
案例三:nginx -t 语法全对,但启动时报 bind() to 0.0.0.0:80 failed (98: Address already in use)。80 端口被占,要么是系统里已经跑了一个 Nginx,要么是 httpd、Tomcat 或者其他 Web 服务占了端口。排查命令:
ss -lntp | grep :80 lsof -i:80找到占用的进程后,要么停掉它,要么改 Nginx 的 listen 端口。如果 80 被业务占用无法让出来,Nginx 可以考虑监听 8080 或者其他高位端口,然后由前置负载均衡转发过来,这也是生产环境常见的拓扑。
4.2 systemd 启动失败但 Nginx 在运行的“假故障”
这个坑我踩过不止一次,症状非常迷惑:systemctl start nginx 提示失败,但 ps -ef | grep nginx 能看到 master 和 worker 进程都在跑,curl 也能访问。
原因十有八九是 PIDFile 路径不一致。Nginx 默认把 PID 写到编译安装目录的 logs/nginx.pid 下,但如果你在 nginx.conf 里手动加了 pid /run/nginx.pid; 这行指令,PID 写到别处去了。systemd 的 Type=forking 模式下,启动完成后会去 PIDFile 指定的路径读 PID,发现文件不存在,就判定启动失败。
解决思路很简单:让 nginx.conf 里 pid 指令路径和 service 文件里 PIDFile 路径保持一致。我的习惯是 nginx.conf 里不写 pid 指令,让它用默认路径,service 文件里也就固定写 /usr/local/nginx/logs/nginx.pid。这样最干净。排查时先看:
systemctl status nginx cat /usr/local/nginx/logs/nginx.pidstatus 输出里如果提示 Can't open PID file,那基本就是路径漂移问题。
4.3 改了配置 reload 没生效:问题可能不在 Nginx
有时候改完 nginx.conf,执行 systemctl reload nginx,看似一切正常,但业务访问到的还是旧配置。这里要分几种情况排查。
第一种,ExecReload 写错了,比如把 reload 写成了 -s stop,执行 reload 等于直接把服务停了,然后 systemd 可能还会把你服务标记为失败。这种情况在自写 service 文件时经常见,建议对照 3.2 节逐字检查。
第二种,配置语法通过了,但逻辑有问题。举个例子,你新加了一个 server 块想做跳转,但 server_name 和旧配置重复,Nginx 按先匹配到的 server 处理,你的新配置干脆没被命中。这种不会报错,只能靠你把每个 server 块的监听地址、域名、return 状态逐个核对。
第三种,配置本身正确,但浏览器缓存了旧页面。排除办法用 curl 加一个随机参数访问,或者用 nginx -t 后观察日志里是否有对应请求记录。别一上来就怀疑 Nginx 没生效,先确认你访问的是不是真的这台服务器。
4.4 防火墙、SELinux、文件权限三板斧
排到最后还访问不了,就要检查系统层面的拦截了。CentOS 系先看 firewalld:
firewall-cmd --list-ports firewall-cmd --permanent --add-port=80/tcp firewall-cmd --reloadUbuntu 系则是 ufw:
ufw allow 80/tcp很多新手以为防火墙放行就完事了,还有一个容易忽略的是 SELinux。RHEL 系发行版默认 Enforcing 模式下,Nginx 如果监听非标准端口(比如 8080),即使防火墙放行了,SELinux 也可能因为 http_port_t 端口类型里没有 8080 而拒绝绑定。解决办法:
semanage port -a -t http_port_t -p tcp 8080如果 Nginx 要读取某个目录下的静态文件,目录的上下文不对也会 403,这时候用:
chcon -R -t httpd_sys_content_t /data/static最后再给一条速查表,建议收藏:
| 现象 | 常见原因 | 排查/解决 |
|---|---|---|
| systemctl: Unit not found | 没有单元文件 | 创建 nginx.service 并 daemon-reload |
| configure: C compiler not found | 未装 gcc | yum install gcc make |
| configure: PCRE library not found | 未装开发库 | yum install pcre-devel |
| bind 80 Address already in use | 端口被占 | ss -lntp 找占用者,改端口或停进程 |
| start 失败但进程在跑 | PIDFile 路径不一致 | 统一 nginx.conf pid 与 service PIDFile |
| reload 后配置不生效 | server_name 冲突/浏览器缓存 | 核对 server 块、用 curl 验证 |
| 外部访问不通 | 防火墙/SELinux | 放行端口、调整端口标签 |
| 高并发连接数上不去 | 文件描述符限制 | service 里加 LimitNOFILE |
5. 我的一些习惯和最终建议
写到最后,分享几个我实际用下来的习惯,不是教科书里的东西,但真的能少踩坑。
第一,nginx.conf 和 nginx.service 文件一定要纳入 git 管理。不只是配置文件本身,连同安装脚本、依赖清单都放进仓库。三个月后你重装环境,照着 git 历史一键复现,比翻聊天记录回忆强一万倍。
第二,改任何 Nginx 配置之前,先备份,再执行 nginx -t,最后 reload。顺序别乱,能省掉 90% 的故障时间。我一同事直接把正在运行的配置改坏了,reload 之后服务直接拒绝加载新配置,但因为重启前没做语法检查,那天线上中断了十分钟。
第三,源码编译时尽量一次配齐常用模块。虽然 Nginx 官方支持动态模块加载,但很多第三方模块还是编译期定死的。宁可编译时多带一两个用不上的模块,也不要等到需要时再重新编译一遍整个 Nginx。
我自己现在每建一台服务器,第一件事就是先把 Nginx 源码编译好,写好 service 文件,验证一遍 enable --now,然后才去部署业务。这套流程走顺了,一台空机器到 Nginx 完全受 systemd 管理,二十分钟内搞定。你也可以按这个顺序来,相信你跑通之后,会对 systemd 和 Nginx 之间的配合有一个非常直观的理解。