很多年前我第一次在服务器上部署应用时,用的还是Apache。那时候项目不大,也没觉得有什么问题,直到一次线上活动流量稍微上来一点,服务器CPU直接飙红,进程接二连三被拖死。后来架构上引入了Nginx做反向代理和负载均衡,同样的机器配置,抗住的请求量翻了好几倍。从那以后,Nginx就成了我所有项目里几乎是必装的组件。
这篇文章就是围绕“Nginx入门与实战”这条主线展开的:从Nginx是什么、为什么需要它,到Linux环境下的几种安装方式,再到反向代理、负载均衡、HTTPS证书、四层代理这些实战配置,最后聊聊生产环境里怎么调优、怎么安全加固、怎么平滑升级版本。适合刚接触Nginx的开发者,也适合已经会用了但没认真做过“生产级配置”的人。文中所有配置都是我在实际项目中用过的,直接可以照着抄,我也会把每个配置为什么要这么写讲清楚。
1. 先搞清楚Nginx到底解决了什么问题
1.1 它不只是一个静态文件服务器
不少新手看完“Nginx是什么”的科普文章,得出的结论是“一个很流行的Web服务器”。这话没错,但如果你只把它当成处理静态文件的Web服务器,后面那些复杂问题就理解不了。
Nginx在今天的架构里主要扮演几个角色:
- 静态资源服务器:直接返回HTTP请求,处理HTML/CSS/JS/图片。这个能力很强,单机静态文件性能比Python/Node/Java写的Web服务不知道高到哪里去。
- 反向代理:客户端所有请求先到Nginx,Nginx再根据规则转发给后端的应用服务,比如SpringBoot、Django、Node.js。这样客户端根本不感知后端地址。
- 负载均衡:后端有多台实例时,Nginx负责把流量分发给不同实例,让每台机器压力均匀。
- SSL终结:证书挂在Nginx上,客户端和Nginx之间走HTTPS,Nginx到后端可以走HTTP,减少后端处理加解密的开销。
- 缓存服务器:对某些不常变化的接口或静态资源做缓存,减少后端压力。
- 四层代理:通过stream模块转发TCP/UDP流量,比如MySQL、Redis,这在微服务架构里很常见。
静态资源服务是Nginx最基础的用法,但真正让它成为“生产级标配”的,是它作为统一流量入口的能力。一个现代应用架构里,外层永远是Nginx,后面才是各种业务服务。
1.2 生产环境为什么那么多项目选Nginx
把Nginx和几个常见选择放在一起看,更容易理解:
| 组件 | 擅长的场景 | 通常的位置 |
|---|---|---|
| Nginx | 高并发静态资源、反向代理、负载均衡、SSL、网关 | 最前端 |
| Apache | 老牌Web服务器,支持模块丰富 | 也有一定份额,但新一代项目用得越来越少 |
| Tomcat | Java Servlet容器 | Nginx后面的应用层 |
| Node.js | 业务逻辑、WebSocket、API层 | Nginx后面的应用层 |
Nginx最核心的优势就是单进程异步非阻塞模型,配合master-worker多进程架构,可以用很少的资源支撑很高的并发连接。同样是10个并发请求,传统多线程模型可能要开10个线程或10个进程,每个都占用内存;Nginx里一个worker进程用事件循环就把这些请求全接住了,几乎不额外占用资源。
说到底,Nginx在Web层干的事情就像一个门卫加调度员:先统一接收所有来访,再根据来意分配到不同办公室。办公室不管前面来多少人,只管处理自己手里的事就行。
1.3 用一个类比理解Nginx的并发模型
Apache传统的处理模型接近“来一个客人,安排一个服务员全程跟到底”。客人一多,服务员全忙,新来的客人只能排队。Nginx不一样,它像酒店前台:一个接待员可以同时招呼很多客人,你问一句他答一句,中间需要等的事情就挂起,他转头去帮别人处理。这种机制叫事件驱动、异步非阻塞。
Nginx运行起来会有一个master进程和若干worker进程。master负责读配置、管理worker;worker才真正处理请求,每个worker都是单线程事件循环。所以worker进程数通常设置为CPU核心数,避免频繁进程切换。
你可以在Linux上用ps命令看进程状态:
code复制ps -ef | grep nginx
会看到类似这样的输出:一个master进程,几个worker进程,worker数量基本等于CPU核数。这个结构决定了Nginx在高并发下的稳定表现,也决定了后面调优时worker数量要按CPU核数来做。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先把环境搭起来:安装Nginx的三种主流方式
2.1 安装前必须想明白的三件事
Nginx安装方式很多,但安装前先想清楚三件事,能省下后面不少折腾。
第一,用包管理器装、源码编译装,还是Docker跑?第二,用稳定版还是主线版?第三,这个环境是开发、测试还是生产?
包管理器安装最快最省事,适合快速搭环境、学习验证。但缺点是版本往往滞后,而且某些发行版默认不带Nginx,需要额外配源。源码编译安装是生产环境非常常见的做法,你可以自己指定编译参数,选择要编译进哪些模块,目录和版本完全可控。缺点是手动操作多一点,升级时也要重新编译。Docker安装则胜在环境隔离、部署方便,一条命令跑起来,非常适合容器化部署和本地开发。
版本选择上,Nginx官方版本线分Mainline和Stable。Mainline包含新功能,更新快;Stable是稳定线,建议生产环境用Stable版本,不要盲目追新。安装后做一次nginx -v确认版本,这是后面排查问题时首先要对齐的信息。
2.2 方式一:包管理器安装(适合快速上手)
Ubuntu/Debian系:
code复制sudo apt update
sudo apt install nginx -y
sudo systemctl enable --now nginx
CentOS/RHEL 7/8/9:
code复制sudo yum install epel-release -y
sudo yum install nginx -y
sudo systemctl enable --now nginx
CentOS默认仓库里没有nginx,要先装epel-release。很多人安装时卡在这一步:yum install nginx直接提示No package nginx available。这就是没装EPEL源导致的,装上再执行就有了。
装完后验证:
code复制systemctl status nginx
curl -I http://localhost
看到HTTP/1.1 200 OK就说明Nginx已经在正常服务了。这种安装方式的优点是不用关心依赖,系统自动处理;缺点是版本可能不是最新的。
2.3 方式二:源码编译安装(生产系统的重头戏)
源码编译是Nginx进阶路上绕不开的一步,很多生产环境都这么装。因为你可以自定义编译参数,把需要的模块编进去,不需要的去掉,还能指定安装目录,之后升级维护更可控。
先装依赖:
Ubuntu:
code复制sudo apt install -y build-essential libpcre3-dev libz-dev libssl-dev
CentOS:
code复制sudo yum install -y gcc gcc-c++ make pcre-devel zlib-devel openssl-devel
为什么这几样必须?pcre是正则表达式库,Nginx的location、rewrite指令都要用到;zlib提供gzip压缩;openssl提供TLS/SSL支持,没有它https模块编不出来。
然后下载源码:
code复制cd /usr/local/src
wget https://nginx.org/download/nginx-1.26.3.tar.gz
tar zxvf nginx-1.26.3.tar.gz
cd nginx-1.26.3
configure这一步最关键:
code复制./configure --prefix=/usr/local/nginx \
--with-http_ssl_module \
--with-http_stub_status_module \
--with-stream \
--with-http_gzip_static_module \
--with-http_v2_module
--prefix指定安装目录;--with-http_ssl_module开启https;--with-stream开启四层代理;--with-http_v2_module开启HTTP/2。生产环境我用得比较多的还有这几个:--with-http_realip_module,配合CDN或负载均衡前置获取真实IP;--with-http_geoip_module,做地域识别。
然后编译安装:
code复制make -j$(nproc) && make install
装完后启动:
code复制/usr/local/nginx/sbin/nginx
验证:
code复制/usr/local/nginx/sbin/nginx -v
curl localhost
源码编译安装最大的好处是目录清晰,所有文件都在/usr/local/nginx下,后期升级、卸载都方便。而且编译参数是自己定的,模块不会缺。缺点是第一次configure失败时会比较打击人,但报错信息大多数时候都明确指向缺哪个依赖,补上就好。
2.4 方式三:Docker方式(容器化部署很省心)
如果你已经在用容器化流程,Nginx用Docker跑是很省心的选择。
最简启动:
code复制docker run -d --name nginx-demo -p 80:80 -v /opt/nginx/html:/usr/share/nginx/html:ro nginx:stable
正式使用,建议用docker-compose把配置目录和日志目录都挂载出来:
code复制services:
nginx:
image: nginx:stable
container_name: nginx-prod
ports:
- "80:80"
- "443:443"
volumes:
- /opt/nginx/conf.d:/etc/nginx/conf.d:ro
- /opt/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- /opt/nginx/certs:/etc/nginx/certs:ro
- /opt/nginx/logs:/var/log/nginx
restart: unless-stopped
Docker方式的核心思路是“容器无状态”:容器本身不存任何数据,配置、日志、证书全部从宿主机挂载进去。这样容器删除重建都不影响业务。
一个容易出错的地方:在容器里修改配置后,要执行docker exec nginx-prod nginx -t和docker exec nginx-prod nginx -s reload,而不是在宿主机直接改。因为容器里的进程由容器管理,宿主机上执行systemctl restart nginx是不生效的。
2.5 离线安装的思路
如果是完全隔离的内网环境,没有外网访问权限,离线安装同样可以做。
- 在能访问外网的同版本环境上,下载好对应发行版的rpm包或deb包,传到内网后通过包管理器安装。比如CentOS:用yumdownloader把nginx和它的依赖包全部下下来,然后yum localinstall *.rpm,或者用rpm -ivh逐个装。
- 源码编译方式:把源码包和依赖库的源码包一起拷进去,先编译PCRE、zlib、openssl,再编译nginx。注意configure时用--with-pcre=/path/to/pcre-source这种参数,指定依赖源码目录。
- Docker方式:在有网环境把镜像打好,docker save导出tar文件,内网docker load导入,再启动。这个方式在跨环境部署时最省事。
离线环境最容易出的坑:少了依赖库导致configure阶段报错;或者编译出的二进制在另一台机器上因为glibc版本不同而无法运行。所以离线环境下我更推荐用Docker镜像迁移或者自带依赖的静态编译方式。
3. 写对第一份配置:静态站点与反向代理实战
3.1 nginx.conf结构拆解
拿到Nginx之后,第一件事是打开配置文件,搞懂它的组织方式。默认配置文件一般在:
- 包管理器安装:/etc/nginx/nginx.conf,子配置目录是 /etc/nginx/conf.d/ 和 /etc/nginx/sites-enabled/
- 源码编译安装:/usr/local/nginx/conf/nginx.conf
nginx.conf的整体结构是这样:
code复制user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name example.com;
...
}
}
- 最外层是main块,配置进程用户、worker数量、日志路径。
- events块配置与连接处理相关的参数,核心是worker_connections。
- http块包含所有HTTP相关的配置,里面可以定义多个server块,每个server块就是一个站点/虚拟主机。
- server块里再用location块匹配不同的URL路径,做不同的处理。
理解这个嵌套关系很关键。你用Nginx托管多个网站时,不是写一堆混杂的指令,而是给每个网站建一个server块,互不干扰。生产环境里常见做法是每个网站在conf.d下放一个独立配置文件,比如blog.example.com.conf,里面就只写这个站点的server块,管理起来很清晰。
3.2 静态站点配置样例
假如我有一个目录 /var/www/blog,里面是打包好的静态文件:
code复制server {
listen 80;
server_name blog.example.com;
root /var/www/blog;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
- root指令把域名对应的文件根目录指到/var/www/blog。
- index声明默认首页文件。
- try_files $uri $uri/ =404的意思是:先尝试按URI找文件,找不到就按目录找,再找不到就返回404。这是静态站点非常常用的写法。
需要注意权限问题。Nginx的worker进程通常以nginx用户运行,如果网站目录在/var/www下但权限是700且属主是root,Nginx就无权读取,访问会得到403 Forbidden。我曾经不止一次看到有人配好root路径后一直403,最后发现是目录权限的问题。改法可以:
code复制sudo chown -R nginx:nginx /var/www/blog
或者直接把user指令改成root用户,但我不建议这样做,权限给到最小才是安全的。
3.3 反向代理配置样例
反向代理是Nginx最核心的用法。比如我有一个Node.js服务跑在127.0.0.1:3000,我想让用户访问 http://example.com/ 时直接打到这个服务上:
code复制server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
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后面写的是后端地址。proxy_set_header这几行是拿真实客户端信息和域名信息传给后端,否则后端记录到的可能全是Nginx的IP,某些依赖Host判断域名的应用也会出问题。
这里有一个经典的坑:proxy_pass后面带不带URI路径,行为的差异很大。
- proxy_pass http://127.0.0.1:3000; 不带路径:Nginx会把原始的URI完整传给后端。
- proxy_pass http://127.0.0.1:3000/api/; 带路径:Nginx会用location匹配后的剩余部分替换掉路径中的/api/部分再传给后端。
举个具体例子:请求/api/user?id=1,如果location是/api/,proxy_pass不带斜杠路径,后端收到/api/user?id=1;如果proxy_pass写成http://127.0.0.1:3000/,后端收到/user?id=1。很多人第一次配的时候在/api前缀和是否带斜杠之间绕来绕去,搞不清楚后端为什么404了,多半就是这个原因。
3.4 一个请求从进入到返回的完整链路
把配置写对之前,最好先在脑子里过一遍请求流转:
- 用户浏览器请求 http://example.com/api/user,先做DNS解析得到服务器IP。
- 请求到达服务器80端口,Nginx的某个worker进程接收了这个连接。
- Nginx按照server_name和listen端口匹配到对应的server块。
- 在server块里按URI匹配location,命中 /api/ 这个location。
- location里配置了proxy_pass,Nginx就以HTTP协议把请求转发给127.0.0.1:3000。
- 后端处理完返回响应,Nginx把响应原样返回给浏览器。
这个链路理解透了,大多数配置问题都能自己排查。比如404是路径匹配不到,502是后端服务没起来
