Nginx 是什么意思:能做什么、和 Apache 怎么选
Nginx(读作 engine x)是一款开源的 Web 服务器,同时承担反向代理、负载均衡、HTTPS 终结和缓存。它通常放在网站最前面:静态文件自己返回,动态请求转给后端程序。事件驱动的架构让它用很少的进程维持大量连接,高并发下比传统的 Apache 省内存。
Nginx 是什么意思?Nginx 读作“engine x”,是一款开源的 Web 服务器,同时也是反向代理、负载均衡器和 HTTP 缓存。它由 Igor Sysoev 开发,2004 年公开发布,最初就是为了解决单台服务器同时处理上万个连接的问题。今天它最常见的位置是网站的最前面:图片、CSS、JS 这些静态文件由它直接返回,PHP、Java、Node.js、Python 写的动态请求由它转给后端程序,HTTPS 证书也在它这里处理。与 Apache 相比,它用事件驱动模型处理连接,少数几个进程就能维持成千上万个连接,所以高并发场景下更省内存。
Nginx 能做什么:五种常见用法
| 用法 | 做什么 | 关键配置指令 |
|---|---|---|
| 静态网站与文件服务 | 直接返回 HTML、图片、CSS、JS 和下载文件 | root、index、try_files、expires |
| 反向代理 | 接收客户端请求,转给后端程序,客户端看不到后端地址 | proxy_pass、proxy_set_header |
| 负载均衡 | 把请求分给多台后端,连续失败的后端暂时摘除 | upstream、least_conn、max_fails |
| HTTPS 终结 | 在 Nginx 上完成 TLS 握手,后端只跑普通 HTTP | ssl_certificate、ssl_protocols |
| 缓存 | 把后端的响应存到本地,重复请求不再打到后端 | proxy_cache_path、proxy_cache |
此外还能做 TCP/UDP 四层转发(stream 模块,例如转发 MySQL 的 3306 端口)、gzip 压缩和按 IP 限流。
下面一份配置把五种用法放在一起:一个商城站点,静态文件由 Nginx 返回,接口转给 3 台应用服务器,商品列表接口缓存 10 分钟。放在 /etc/nginx/conf.d/shop.conf,这个目录里的文件会被包含进主配置的 http 块:
# 缓存目录:1 MB 的 keys_zone 大约能存 8000 个缓存键
proxy_cache_path /var/cache/nginx/shop levels=1:2 keys_zone=shop_cache:50m
max_size=2g inactive=60m use_temp_path=off;
# 负载均衡:请求优先给当前连接数最少的后端,30 秒内失败 3 次就暂停使用 30 秒
upstream shop_app {
least_conn;
server 10.0.0.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.12:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.13:8080 backup; # 前两台都不可用时才启用
}
# HTTP 统一跳转到 HTTPS
server {
listen 80;
server_name shop.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on; # 1.25.1 起的写法,旧版本改为 listen 443 ssl http2;
server_name shop.example.com;
ssl_certificate /etc/nginx/ssl/shop.example.com.fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/shop.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
# 静态文件:由 Nginx 直接读磁盘返回,浏览器缓存 7 天
location /static/ {
root /var/www/shop; # 实际读取 /var/www/shop/static/...
expires 7d;
}
# 商品列表接口:缓存 10 分钟,后端出错时先返回旧缓存
location /api/products/ {
proxy_cache shop_cache;
proxy_cache_valid 200 10m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://shop_app;
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 / {
proxy_pass http://shop_app;
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_set_header 不能省。不传 Host,后端拿到的是 upstream 名称而不是域名;不传 X-Forwarded-For 和 X-Real-IP,后端日志里所有访客的 IP 都是 Nginx 自己;不传 X-Forwarded-Proto,后端以为用户走的是 HTTP,可能反复跳转到 HTTPS。
- 开源版只有被动健康检查。
max_fails和fail_timeout靠真实请求失败来判断后端是否可用;定时主动探测后端属于商业版 NGINX Plus 的功能,开源版需要第三方模块或在后端前面另加检测。 - 验证缓存是否生效。连续请求两次
curl -sI https://shop.example.com/api/products/ | grep -i x-cache-status,第一次是 MISS,第二次是 HIT(走 HTTP/2 时响应头名称是小写,所以 grep 要加 -i)。如果一直是 MISS,先看后端响应是否带了 Set-Cookie 或禁止缓存的 Cache-Control,默认情况下 Nginx 不缓存这类响应。 - PHP 程序走 FastCGI。Nginx 不执行 PHP,要把
.php请求交给 PHP-FPM:在location ~ \.php$里写include fastcgi.conf;和fastcgi_pass unix:/run/php/php8.3-fpm.sock;。注意fastcgi.conf比fastcgi_params多一行 SCRIPT_FILENAME,只 include 后者又没补这一行,PHP-FPM 会找不到脚本;Debian、Ubuntu 上也可以直接用include snippets/fastcgi-php.conf;。socket 路径以服务器上实际安装的 PHP 版本为准。
为什么 Nginx 在高并发下更省资源
Nginx 启动后是一个 master 进程加若干 worker 进程。master 只负责读配置、管理 worker;真正处理请求的是 worker,数量通常设为 worker_processes auto;,即与 CPU 核数相同。每个 worker 是单线程的事件循环:它把几千个连接都交给内核的 epoll(BSD 上是 kqueue)监视,哪个连接有数据可读、可写,内核就通知它去处理哪个,处理完立刻转向下一个,从不停下来等某一个慢客户端。
传统的 Apache prefork 模式则是一个连接占一个进程。用户的浏览器开着 keep-alive 什么也不发,这个进程也只能干等。两种模型面对 1 万个并发连接时的差别:
| 对比 | Nginx | Apache prefork |
|---|---|---|
| 处理 1 万个连接需要 | 4 核服务器上 4 个 worker,每个维持约 2500 个连接(worker_connections 要相应调高) | 理论上 1 万个进程;默认 MaxRequestWorkers 只有 256,超出的连接排队等待 |
| 空闲 keep-alive 连接的开销 | 很小:Nginx 官网的说法是 1 万个空闲 keep-alive 连接约占 2.5 MB 内存 | 每个连接占一个完整进程,加载了 mod_php 时每个进程常有几十 MB |
| 慢客户端的影响 | 只占一个连接的少量内存 | 长时间占住一个进程 |
Nginx 能维持的连接上限大约是 worker_processes × worker_connections。发行版自带的配置里 worker_connections 常见 768 或 1024,做反向代理时每个请求要占用客户端和后端两个连接,实际能服务的并发约为上限的一半;要调高时,同时用 worker_rlimit_nofile 提高每个进程能打开的文件数。
事件驱动也有代价:如果某个操作阻塞了 worker(例如在很慢的磁盘上读大文件,或者第三方模块里有同步调用),这个 worker 上的所有连接都会跟着卡住。Nginx 为此提供了线程池(aio threads),把读磁盘这类阻塞操作交给后台线程。另外,Apache 2.4 的 event 模式也把空闲的 keep-alive 连接交给专门的监听线程管理,差距已经比 prefork 时代小,但每个正在处理的请求仍然要占用一个线程。
在服务器上可以直接看到这两种结构:
ps -o pid,ppid,user,rss,cmd -C nginx # 1 个 master(root)加若干 worker,RSS 单位是 KB
grep -E 'worker_processes|worker_connections' /etc/nginx/nginx.conf
nproc # CPU 核数,worker_processes auto 时与它相同
apachectl -V | grep -i mpm # Apache 当前用的 MPM:prefork、worker 或 event
Nginx 和 Apache 的区别
| 对比项 | Nginx | Apache httpd |
|---|---|---|
| 连接处理模型 | 事件驱动,少数 worker 进程各自处理大量连接 | 由 MPM 决定:prefork 一连接一进程,worker 和 event 为多进程多线程 |
| 静态文件、高并发 | 内存占用低,适合做前端入口 | 并发上去后进程或线程数随之增长 |
| 运行 PHP | 交给 PHP-FPM | 可以用 mod_php 内嵌执行,也可以接 PHP-FPM |
| 目录级配置 | 不支持 .htaccess,规则写在主配置里,改完要 reload | 支持 .htaccess,网站目录里放文件即可生效 |
| 伪静态规则 | rewrite、try_files |
mod_rewrite,很多 PHP 程序自带 .htaccess 规则 |
| 模块 | 多数编译进程序;动态模块要和 Nginx 版本匹配 | 模块动态加载,Debian、Ubuntu 上用 a2enmod 一条命令启用 |
| 反向代理与负载均衡 | 核心功能,配置简洁 | 用 mod_proxy、mod_proxy_balancer 实现 |
| 许可证 | BSD 类许可证 | Apache License 2.0 |
| 典型位置 | 网站入口、反向代理、静态资源、负载均衡 | 共享虚拟主机、依赖 .htaccess 的老程序 |
两者的分界其实就在两点:要不要 .htaccess、并发有多高。共享虚拟主机让每个用户在自己目录里改规则,离不开 .htaccess,所以多用 Apache 或兼容它的 LiteSpeed;自己有整台服务器、能改主配置的,用 Nginx 的限制就不存在了。
还有一种常见的混合方案:Nginx 在前面处理 HTTPS、静态文件和连接,Apache 在后面跑依赖 .htaccess 的老程序。这时 Apache 看到的来源 IP 全是 Nginx 的地址,要启用 mod_remoteip 并信任 Nginx 传来的 X-Forwarded-For,日志和程序里才能拿到访客的真实 IP。
租服务器建站,什么情况选 Nginx
| 场景 | 选择 | 理由 |
|---|---|---|
| 1–2 GB 内存的云服务器或 VPS 跑 WordPress 等 PHP 网站 | Nginx 加 PHP-FPM | 静态文件和空闲连接不占 PHP 进程,内存更够用 |
| Node.js、Java、Python、Go 写的应用 | Nginx 做反向代理 | 应用只监听 127.0.0.1,由 Nginx 对外处理 80、443 和证书 |
| 多台后端服务器 | Nginx 的 upstream | 负载均衡和故障摘除开箱即用 |
| 图片站、下载站、纯静态网站 | Nginx | 静态文件吞吐高,配置简单 |
| 程序大量依赖 .htaccess,且不想改写规则 | Apache,或前 Nginx 后 Apache | 免去把规则逐条翻译成 Nginx 写法 |
| 用宝塔等面板建站 | 两者都可以 | 面板里切换,按上面的规则选 |
小内存服务器上,Nginx 的优势可以算出来。假设一台 2 GB 内存的服务器,系统和 MySQL 占去约 1 GB,剩下约 1000 MB 给 PHP,用下面的命令实测每个 PHP 进程平均占 40 MB:
ps -o rss= -C php-fpm8.3 | awk '{s+=$1; n++} END {printf "%d 个进程,平均 %.1f MB\n", n, s/n/1024}'
那么 PHP 最多开 1000 ÷ 40 = 25 个进程,PHP-FPM 的 pm.max_children 就设为 25。区别在于这 25 个进程被谁占用:用 Apache prefork 加 mod_php 时,每个连接都占一个带 PHP 的进程,包括下载一张图片、甚至什么都不发的 keep-alive 连接,一个页面带 30 个静态资源,几个访客同时打开就可能把 25 个进程占满,后来的人只能排队;用 Nginx 时,静态文件和空闲连接由 Nginx 处理,25 个 PHP-FPM 进程只服务真正需要执行 PHP 的请求。
装好 Nginx 后,记得放行 80 和 443 端口,Ubuntu 上用 ufw:
sudo ufw allow 22/tcp # 先放行 SSH,否则启用防火墙后可能再也连不上
sudo ufw allow 80,443/tcp
sudo ufw enable
sudo ufw status
第一行最容易忘:启用 ufw 前没有放行 22 端口,当前会话可能还连着,退出后就再也登录不上 SSH。真遇到这种情况,用服务商控制面板里的 VNC 控制台登录修改,例如基于 Toplink DCIM 的 客户自助端 就能直接在浏览器里打开服务器控制台,不依赖 SSH。云服务器还要在安全组里放行同样的端口。
常用命令与配置文件位置
| 项目 | Debian、Ubuntu 软件源 | RHEL、Rocky、AlmaLinux 及 nginx.org 官方包 |
|---|---|---|
| 安装 | apt install nginx |
dnf install nginx |
| 主配置 | /etc/nginx/nginx.conf | /etc/nginx/nginx.conf |
| 站点配置 | /etc/nginx/sites-available/,软链接到 sites-enabled/ 生效;也读取 conf.d/ | /etc/nginx/conf.d/*.conf |
| 默认网站目录 | /var/www/html | /usr/share/nginx/html |
| 日志 | /var/log/nginx/access.log、error.log | 同左 |
| worker 运行用户 | www-data | nginx |
nginx -v # 版本号:偶数小版本(如 1.26)是 stable,奇数是 mainline
nginx -V 2>&1 | tr ' ' '\n' | grep -E '^--with' # 编译了哪些模块
sudo nginx -t # 检查配置语法,改完配置先执行它
sudo nginx -T | less # 打印合并后的完整配置,排查“改了不生效”
sudo systemctl reload nginx # 平滑加载新配置
sudo tail -f /var/log/nginx/error.log
改配置的固定顺序是:编辑 → nginx -t → systemctl reload nginx → 看 error.log。改配置不需要 restart:reload 时 master 进程用新配置启动新的 worker,旧 worker 处理完手头的请求再退出,访客不会断开;新配置有错时 reload 不会生效,线上仍按旧配置运行。跳过检查直接 restart,配置一错 Nginx 就起不来了。
常见问题
Nginx 和 Tomcat 有什么区别?
Tomcat 是运行 Java Web 应用的容器,负责执行 Servlet 和 JSP;Nginx 不执行 Java 代码。常见搭配是 Nginx 在前面处理 HTTPS、静态文件和负载均衡,把动态请求用 proxy_pass 转给后面的一台或多台 Tomcat。
Nginx 免费吗?可以商用吗?
开源版 Nginx 以 BSD 类许可证发布,免费使用,包括商业用途。F5 另外提供商业版 NGINX Plus,增加主动健康检查、动态配置接口和官方支持。国内常见的 OpenResty、Tengine 是基于 Nginx 的衍生版本。
Nginx 报 502 和 504 是什么原因?
502 Bad Gateway 表示 Nginx 连不上后端,或后端返回了无效响应,常见于后端程序没启动、proxy_pass 或 fastcgi_pass 的地址写错、PHP-FPM 进程崩溃;504 Gateway Timeout 表示后端在超时时间内没有响应,proxy_read_timeout 默认 60 秒。先看 error.log 里带 upstream 字样的报错,再去查后端。