
1. 装 Nginx 之前先把 CentOS 环境这几个细节摸清楚说实话我每次接到“帮忙装个 Nginx”这种需求第一反应都不是直接敲安装命令而是先确认这台 CentOS 到底是个什么状态。因为 Nginx 本身安装并不难真正让人头疼的往往是你以为装好了结果页面打不开、反向代理 502、前端刷新就 404——这些坑十个里有八个在环境阶段就埋下了。这篇文章我不会只给一串“yum install nginx”就完事。我会把我实际部署 CentOS 7/8 系列服务器时走过的完整流程、踩过的坑、以及最后沉淀下来的一套标准操作都写出来。无论你是第一次在 Linux 上装 Nginx还是已经装过几次但总在配置阶段卡壳这篇内容应该都能帮你省下不少排查时间。1.1 先搞清楚你手上是哪个 CentOS 版本很多人都吃过这个亏照着网上的教程敲命令敲完发现源不对、包名不对、甚至整个命令的语法都不一样。原因多半是 CentOS 版本不同导致的。cat /etc/redhat-release这一条命令就能看到系统版本比如 CentOS Linux release 7.9.2009 (Core) 还是 CentOS Stream release 8。不同的版本yum 源配置方式、软件包可用性、甚至 systemd 的行为都有细微差别。CentOS 7 目前已经进入 EOL 状态CentOS 8 更是早就停止维护了。如果你现在新装一台 CentOS 8 然后直接yum install nginx大概率会报错找不到镜像源因为默认的 mirrorlist 已经指向了已经关闭的仓库。这时候需要把 yum 源切换到 vault.centos.org 或者国内镜像站的 archive 目录。这是第一道坎很多人在这里就卡住了。1.2 系统初始化检查防火墙、SELinux、时间同步在安装任何东西之前我会花两分钟把下面这几项过一遍# 查看系统版本 cat /etc/os-release # 查看内核版本 uname -r # 查看 SELinux 状态 getenforce # 查看防火墙状态 systemctl status firewalld # 检查当前时区 timedatectl为什么要特意检查 SELinux因为我见过太多人 Nginx 装好了、配置也改好了、端口也放行了结果访问还是 403 或者 502最后折腾半天发现是 SELinux 拦截了。这个话题后面我单独拉一节讲但你现在至少要知道getenforce返回 Enforcing 说明 SELinux 正在强制模式它会拦截 Nginx 的很多文件读取和网络访问行为。刚开始学或者排错阶段可以先临时setenforce 0重启失效确认是不是它的问题但生产环境不建议直接关掉 SELinux。防火墙这块CentOS 7 以上默认用的是 firewalld很多人在本机 curl 通了局域网其他机器访问不了大概率就是防火墙没放行 80/443 端口。另外如果你是云服务器云控制台的安全组规则也得单独放行这个和系统防火墙是两层漏一个都不行。1.3 把编译依赖一次性装齐如果你打算用源码编译安装 Nginx后面会细讲那编译工具链和几个关键依赖库必须在安装前准备好。即使你打算直接用 yum 装我也建议把下面这组依赖装上因为后面你很可能需要额外编译模块或者安装一些与 Nginx 配套的软件yum install -y gcc gcc-c make pcre-devel zlib-devel openssl-devel这四个库各管一摊pcre正则表达式支持Nginx 的 rewrite 模块、location 匹配都依赖它。zlibgzip 压缩模块的依赖。opensslHTTPS 证书支持没有它你没法配置 SSL。gcc/make编译工具链源码安装必备。我见过有人编译 Nginx 时因为缺 zlib-devel编译到一半报错然后又得重新装依赖再 configure 一遍很浪费时间。所以无论你选哪种安装方式先把这组依赖装好不会亏。2. 三种安装方式怎么选yum 源、官方 rpm 包、源码编译Nginx 在 CentOS 上的安装方式基本可以归为三类yum 直接装、用官方 yum 仓库装 rpm 包、源码编译安装。看起来都是“装 Nginx”但这三者的版本、目录结构、后续维护方式差异很大。我按推荐顺序一个个说。2.1 yum install nginx最省事但版本和来源要留意如果你用的是 CentOS 7最省事的方式是配置好 EPEL 源之后直接装yum install -y epel-release yum install -y nginx装完之后 Nginx 的版本通常是 1.20 左右以 CentOS 7 的 EPEL 源为例。对大多数场景来说这个版本够用且稳定目录结构也是标准的配置文件/etc/nginx/站点根目录/usr/share/nginx/html/日志/var/log/nginx/但这里有一个隐患EPEL 源里的 Nginx 版本更新节奏比较慢而且它的默认配置文件是官方 rpm 包的变体include 了 conf.d 下所有 .conf 文件你在网上看到的一些教程里写的路径可能对不上。2.2 官方 nginx.org 的 rpm 包我个人最常用的方式如果你想要版本更新、补丁跟进及时直接用 Nginx 官方提供的 yum 仓库是最稳的。在 /etc/yum.repos.d/nginx.repo 写入以下内容[nginx-stable] namenginx stable repo baseurlhttp://nginx.org/packages/centos/$releasever/$basearch/ gpgcheck1 enabled1 gpgkeyhttps://nginx.org/keys/nginx_signing.key module_hotfixestrue然后执行yum install -y nginx用官方仓库装出来的版本通常比 EPEL 源新而且它带的/etc/nginx/nginx.conf更接近官方默认风格。目录结构仍然是 /etc/nginx、/var/log/nginx 这一套但主配置文件里的 include 规则可能略有不同服务管理通过 systemd 直接搞定。这里要提醒一下$releasever变量在 CentOS 7 下会解析成 7在 CentOS 8 下解析成 8如果你用的是 CentOS Stream 系列baseurl 里的 releasever 解析可能不准这时可以手动写成具体版本号比如baseurlhttp://nginx.org/packages/centos/7/x86_64/2.3 源码编译安装适合需要定制模块的场景源码编译是最“折腾”但也是最灵活的方式。需要场景包括需要编译进不在默认模块列表里的第三方模块。需要调整 Nginx 的二进制文件路径、模块路径。需要自定义 configure 参数。标准流程是# 下载源码包 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 --prefix/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_gzip_static_module \ --with-pcre # 编译安装 make -j$(nproc) make install编译完的 Nginx 在 /usr/local/nginx/ 下配置文件是 /usr/local/nginx/conf/nginx.conf可执行文件是 /usr/local/nginx/sbin/nginx。源码编译的缺点也很明显它不会自动注册成 systemd 服务你需要手动写 service 文件日志也不会自动放进 /var/log/nginx而是要自己在配置里指定路径。如果没有特殊需求我一般不推荐为了“最新版本”去编译安装因为后续更新维护太折腾。2.4 三种方式怎么选一张表说清楚安装方式版本新鲜度维护难度目录结构适用场景EPEL yum 源偏旧最简单标准路径快速部署、功能要求不高的场景官方 yum 仓库较新简单标准路径日常生产环境推荐这种方式源码编译自选版本较高自定义路径需要集成第三方模块、特殊定制我个人的默认选择是官方 yum 仓库。原因很简单更新方便、目录标准、配置文件完整、后续排查问题时有大量社区资料可以对照。只有当我需要集成 Lua 模块、或者编译特殊的第三方插件时才会走源码编译。3. 从零写一份能扛住真实业务的 Nginx 配置文件装好 Nginx 只是第一步真正考验功力的是写配置文件。很多初学者把 nginx.conf 当成一个不需要理解的模板出了问题就茫然。其实 Nginx 配置的核心就是一个嵌套的块结构理解了这个你就掌握了它的一半。3.1 主配置文件结构别被 nginx.conf 吓住不管你是哪种方式安装的打开 nginx.conf 都会看到一大堆内容。但它的骨架其实很清晰# 全局块设置运行用户、worker 进程数、日志路径等 user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid; # events 块配置事件模型、连接数等 events { worker_connections 1024; } # http 块HTTP 服务器的全局配置 http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; # server 块一个 server 对应一个虚拟主机 server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location /50x.html { root /usr/share/nginx/html; } } }注意这里我在 http 块内省略了很多细节比如 gzip、log_format 等。但整体逻辑就是这样一层包一层全局配置在最外events 管连接事件http 管内层所有 serverserver 管具体的域名和端口location 管 URL 匹配规则。理解了嵌套关系之后我建议你写配置时遵循一个习惯不要把站点配置写在主 nginx.conf 里而是创建一个独立的 conf 文件比如 /etc/nginx/conf.d/my-site.conf然后在 http 块里 include。官方 yum 包安装的 Nginx 默认已经做了这个 include所以你的站点配置可以一个文件一个站点互不干扰出问题也方便回滚。3.2 静态站点 server 块root、location、try_files一个最基础的静态站点配置是这样的server { listen 80; server_name example.com www.example.com; root /var/www/example; index index.html; location / { try_files $uri $uri/ /index.html; } }这里有一个知识点很多人不清楚root和alias的区别。root 会拼接完整的路径比如请求 /static/a.pngroot 为 /var/www/example 的话Nginx 会去找 /var/www/example/static/a.png。而 alias 的用法更像重命名把 URL 路径映射到另一段物理路径。然后是最关键的try_files $uri $uri/ /index.html。这行的意思是先尝试按原样查找文件找不到再尝试按目录查找还找不到就把请求重写到 /index.html。这正是现代前端框架Vue、React采用 history 路由模式后刷新页面不 404 的核心配置。没有这行你部署的 Vue3 单页应用在用户点击路由跳转时没问题一刷新就变成 404。但要注意如果你有 /api 这样的接口路径一定不要让 API 的 location 也走到 /index.html 这个兜底逻辑里去否则后端接口全部会被前端首页接管。3.3 反向代理proxy_pass 与客户端真实 IP 的传递Nginx 最常用的一个角色就是反向代理。最标准的写法location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }这里有两个必须搞明白的细节第一个proxy_pass 带不带尾部斜杠行为完全不同。如果你写location /api/ { proxy_pass http://127.0.0.1:8080; }那么请求 /api/userNginx 会原封不动地转发给后端/api/user。但如果你写location /api/ { proxy_pass http://127.0.0.1:8080/; }请求 /api/user 时Nginx 会匹配到的 /api/ 部分被替换成 /也就是后端实际收到的是 /user。这个差异极其容易踩坑尤其是前后端分离项目里后端接口路径如果带统一前缀你在 Nginx 里写错了接口会直接 404 或 400。第二个很多人问过一个非常经典的问题Nginx 转发请求时后端的 Tomcat、Spring Boot 服务能看到客户端的真实 IP 吗答案是默认情况下不能。因为 Nginx 转发的是 TCP 连接层的请求后端的服务看到的是 Nginx 这台服务器的 IP。要想让后端拿到客户端真实 IP就必须通过 X-Real-IP 和 X-Forwarded-For 这两个请求头把信息传过去上面配置里的proxy_set_header就是在干这件事。至于什么五元组信息那是网络层的概念到了应用层七层代理这里你能传递的只有请求头和请求体这两个头就是应用层用来补全 IP 信息的标准做法。3.4 负载均衡upstream 的三种默认策略如果你有多台后端服务器可以用 upstream 做负载均衡upstream backend_pool { server 192.168.1.10:8080 weight3; server 192.168.1.11:8080 weight1; keepalive 32; } server { listen 80; server_name api.example.com; location / { proxy_pass http://backend_pool; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }Nginx 默认的负载均衡策略是轮询每个请求按顺序轮流分发给后端。还可以指定 ip_hash让同一个客户端的请求始终打到同一台后端适合需要保持会话的场景。least_conn 则是根据当前连接数分配。weight 参数能调整权重比如让性能更强的机器扛更多流量。这里要注意一点如果你要在 upstream 里配置 keepalive需要额外注意与后端的长连接参数配合否则可能出现连接池耗尽的问题。默认情况下没有特殊需求的话先把 keepalive 注掉也没问题。3.5 一个面向真实部署的综合示例Vue3 前端 API 后端把前面这些串起来写一个实际项目里很常见的完整配置server { listen 80; server_name www.example.com; # 前端静态文件 root /var/www/example/dist; index index.html; # Vue3 history 路由支持 location / { try_files $uri $uri/ /index.html; } # 前端静态资源的缓存策略 location /assets/ { expires 30d; add_header Cache-Control public, immutable; } # API 反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }需要注意 location 的匹配优先级普通前缀匹配中最长前缀优先。所以/assets/和/api/会分别命中对应配置而/是兜底负责前端 history 路由的页面访问。4. 配置校验、启动、开机自启和放行端口这些“收尾动作”很多人装完 Nginx、写完配置直接systemctl restart nginx结果发现服务起不来报错信息刷了一屏。其实只要学会nginx -t90% 的配置错误都能在你执行 restart 之前暴露出来。4.1 校验配置nginx -t 是保命操作不管修改了什么配置我强烈建议你先执行nginx -t如果配置正确输出是nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful如果有错误它会明确告诉你哪个文件的第几行有问题。比如最常见的nginx: [emerg] unknown directive proxy_pas in /etc/nginx/conf.d/test.conf:7看到这种日志你根本不用瞎猜直接去对应文件改就行。另外如果你用的是源码编译安装的 Nginxnginx 命令如果不在 PATH 里可以写成 /usr/local/nginx/sbin/nginx -t。其他人用 systemctl 管理而你没有把二进制路径软链到 /usr/sbin/nginx 的话systemd 是找不到命令的这也是源码编译安装的一个麻烦点。4.2 启动与平滑重载systemctl 和 kill -HUP 的区别yum 或 rpm 方式安装的 Nginx直接用systemctl start nginx systemctl reload nginx systemctl status nginx这里必须强调一下 reload 和 restart 的区别。restart 是直接杀掉 master 和 worker 进程再重新拉起reload 是向 master 进程发送 HUP 信号master 重新加载配置并启动新 worker再逐步把旧 worker 回收。对正在处理请求的连接来说reload 更平滑不会出现请求中断的情况。如果你用的是源码编译安装没有 systemd 服务文件也可以用/usr/local/nginx/sbin/nginx -s reload相当于手动给 master 发 HUP 信号。这个方式同样有效而且很多老运维习惯只用这种方式不依赖 systemd。4.3 开机自启别忽略这一步yum/rpm 安装方式下systemctl enable nginx源码编译安装的话就需要自己写一个 service 文件比如在 /usr/lib/systemd/system/nginx.service 里建一个[Unit] Descriptionnginx - high performance web server Afternetwork.target remote-fs.target nss-lookup.target [Service] Typeforking 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 PrivateTmptrue [Install] WantedBymulti-user.target然后执行 systemctl daemon-reload 和 systemctl enable nginx。这一步的意义是防止服务器重启后 Nginx 不自动拉起。生产环境如果漏掉这一步应急时非常尴尬。4.4 防火墙放行和云安全组两层都要查按照 CentOS 7 的默认习惯防火墙是开着的。放行 80/443 端口firewall-cmd --permanent --add-servicehttp firewall-cmd --permanent --add-servicehttps firewall-cmd --reload如果你用的是云服务器还有一道安全组规则。阿里云、腾讯云这类环境系统防火墙放行了但安全组没放行 80 端口外部照样访问不了。这类问题在“本机 curl 通、外部访问不了”的排查中最常见。4.5 SELinux 导致 403/502 的完整排查思路SELinux 是 CentOS 上最容易坑新手的隐形拦截器。默认 Enforcing 模式下Nginx进程域 httpd_t只能读取特定的几个目录比如 /usr/share/nginx/html、/var/www/html。如果你把站点根目录放在 /home/myweb 或者 /opt/myweb访问时就会出现 403 Forbidden但 error.log 里却看不到明显的权限错误。排查链路# 查看 SELinux 是否拦截了 Nginx grep nginx /var/log/audit/audit.log | tail -20 # 临时放行 httpd_t 访问网络解决反向代理 502 setsebool -P httpd_can_network_connect 1 # 修正网站目录的 SELinux 文件类型标签 chcon -Rt httpd_sys_content_t /var/www/example从操作顺序上讲先看 audit 日志确认原因再针对性放行不要一上来就 setenforce 0 关掉 SELinux。虽然在很多内部环境里大家图省事会直接关 SELinux但这不是一个好习惯也不符合主流的安全实践。5. 装完 Nginx 后最容易踩的五个坑含完整排查链路5.1 80 端口被占用bind() to 0.0.0.0:80 failed这是最经典的启动报错。完整报错通常是nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)排查思路很简单ss -lntp | grep :80你会发现占用 80 端口的可能是 httpd、Apache、或者其他服务。如果是你自己之前启动的 Nginx那就是没停干净。解决办法# 停止占用端口的服务 systemctl stop httpd # 或者杀掉残留的 nginx master 进程 pkill -9 nginx # 然后再启动 systemctl start nginx5.2 页面 403 Forbidden不要急着改权限先看 error.log403 的根源有三个文件不存在、文件权限不够、SELinux 拦截。排查顺序应该是# 1. 看 Nginx 错误日志 tail -20 /var/log/nginx/error.log # 2. 确认站点目录存在且有权限 ls -l /var/www/example ps aux | grep nginx # 确认 worker 进程以什么用户运行Nginx 默认 worker 进程用户是 nginxrpm 安装或 nobody部分源码编译如果站点目录权限是 755 且文件属主是 rootnginx 用户也可能读取失败。最直接的办法是把站点目录属主改成 nginxchown -R nginx:nginx /var/www/example chmod -R 755 /var/www/example如果日志里还看不到明确错误再按上一节的方法查 SELinux。这里我有一个非常实际的建议不要把网站根目录放在 /root 下也不要放在某个用户家目录里否则权限和 SELinux 标签会一直跟你纠缠不清。5.3 前端项目刷新 404错在 location 兜底规则Vue3 部署到 Nginx 后用户在 /login 或 /dashboard 页面刷新一下直接 404。原因很明确前端路由用的是 history 模式浏览器直接请求 /login 这个路径而服务器上并没有 login.html 这个文件Nginx 自然返回 404。解决办法就是前文写过的location / { try_files $uri $uri/ /index.html; }但这里有一个进阶提醒如果你的前端路由里包含 /api 之类的路径或者你需要在前端页面内访问 /api 接口一定不要用try_files $uri $uri/ /index.html把所有路径都兜底到 index.html否则 API 请求也会被改写。正确做法是为 API 单独设置 location放到更优先的位置。5.4 改了配置但没生效配置文件加载路径要核对这个坑我踩得特别多。明明改了 /etc/nginx/nginx.confreload 也执行了但行为没变化。时间一长才发现当前 Nginx 实际加载的配置文件根本不是你以为的那个。你可以用一条命令查看实际生效的配置和加载路径nginx -T它会输出 Nginx 实际使用的完整配置并且每一行都标注了来源文件。如果发现路径和你想的不一样大概率是启动时指定了 -c 参数或者 systemd 服务文件里 ExecStart 指定了另一个配置文件。另外网上很多教程会让初学者用 service nginx start但如果你安装的是源码编译版本service 命令可能根本没有注册对应的 init 脚本。此时用 systemctl status nginx 会发现服务未找到。所以每次换教程先确认教程对应的安装方式再执行命令。5.5 离线环境安装依赖包整合不全导致 rpm 安装失败有些服务器是内网环境没法直接访问外部 yum 源。这时如果前面没有装 pcre-devel、zlib-devel、openssl-devel源码编译几乎一定会失败。离线环境有个办法找一台同版本 CentOS 且能联网的机器用 yumdownloader 把依赖包下载下来yumdownloader --resolve --destdir/tmp/nginx-rpms nginx然后把 /tmp/nginx-rpms 目录下的 rpm 包拷贝到离线服务器执行rpm -Uvh *.rpm # 或者 yum localinstall -y *.rpm依赖整合时最需要注意的是架构x86_64 vs aarch64和系统版本CentOS 7 vs 8 vs Stream混着用大概率装不上。6. 正常跑起来之后再看几个性能相关的小参数Nginx 装好、服务跑通只是及格线。既然都部署到生产了至少还有几个基础参数值得顺手优化一下。6.1 worker 进程数和连接数别盲目拉大worker_processes auto; events { worker_connections 1024; }worker_processes 设置为 autoNginx 会自动匹配 CPU 核心数。worker_connections 是每个 worker 进程能同时保持的最大连接数默认 1024。很多人一上来就改成 65535觉得越大越好。实际上这个值受系统文件描述符限制也就是 ulimit -n 的值。如果系统级限制不够把 worker_connections 调得再大也不会生效反而会造成资源损耗。正确做法是先确认系统限制ulimit -n如果返回的是 1024那 worker_connections 就别超过 1024否则要单独调 /etc/security/limits.conf。在我日常经验里单机抗几千并发worker_connections 2048 配合 auto 的 worker_processes 已经够用了。6.2 gzip 压缩和客户端请求体大小gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xmlrss image/svgxml; gzip_min_length 1k; gzip_comp_level 5; client_max_body_size 20m;gzip 开启后前端资源传输体积能明显减小。注意 gzip_types 不要漏掉 application/json 和 application/javascript因为现在前后端分离项目里这两种类型占了大头。client_max_body_size 默认是 1m如果你有上传文件的需求记得调大否则用户上传稍大一点的文件就会收到 413 Request Entity Too Large。6.3 日志切割与访问日志配置生产环境日志会越滚越大。一般 yum/rpm 安装会自带 logrotate 配置位于 /etc/logrotate.d/nginx。如果没有也可以手动创建/var/log/nginx/*.log { daily missingok rotate 14 compress delaycompress notifempty create 640 nginx adm sharedscripts postrotate if [ -f /var/run/nginx.pid ]; then kill -USR1 cat /var/run/nginx.pid fi endscript }这里 postrotate 里的 kill -USR1 是让 Nginx 重新打开日志文件为的就是在日志切割后让 Nginx 继续正常写日志不会因为文件被改名而丢失内容。6.4 关于调参的一个个人建议不要在没有压力测试和监控数据的情况下盲目调参。Nginx 本身默认配置是经过大量生产环境验证的对于一个中小型项目默认参数完全够用。真正需要优化的往往是上游后端比如数据库连接池、应用服务的线程数这些才是系统瓶颈所在。Nginx 调参这块我的个人习惯是先保持默认跑一段时间看监控指标如果 worker 连接数长期触顶再逐步调大。调参要有依据不要学网上那些“高端配置”一顿抄容易把自己带坑里。最后分享一点我的实际体会这几年来我在各种 CentOS 环境里装过无数次 Nginx从 CentOS 6 一直用到现在的 Stream最终沉淀下来的标准流程其实非常朴素先确认系统版本和环境再用官方 yum 仓库安装一个站点一个 conf 文件改完配置先nginx -t再 reload最后不要忘记开机自启、防火墙和安全组。如果你第一次装跟着这篇文章走一遍基本不会有太大问题。如果中间遇到和我提到的坑类似的情况按照对应章节的排查链路一步步来比你在网上随机搜答案要高效得多。最后再送一个小习惯改任何配置前先 cp 一份备份比如 nginx.conf.20250101花不了十几秒但回滚的时候能救你一次。