Appearance
Nginx 入门与部署
手头有一个写好的网页 index.html,想让别人通过浏览器访问。最直接的办法是起一个 HTTP 服务,把文件目录暴露出去。
一、先跑一个最简单的 Web 服务
用 Python 自带的工具,不用装任何东西:
bash
cd /data/www/example
echo 'hello nginx' > index.html
python -m http.server 80浏览器或 curl 访问这台机器的 80 端口:
bash
curl http://127.0.0.1/返回 hello nginx,服务跑起来了。但很快会发现几个问题。
把 python -m http.server 换成自己的 Java 或 Go 应用直接监听 80 端口,能跑,但 80 端口要 root 权限,应用以 root 跑有安全风险;一台机器上想跑多个站点,一个 80 端口不够分,没法按域名区分;静态文件(HTML、图片)让应用进程处理,既慢又浪费应用性能;应用重启发布时,正在处理的连接全部断开。
Python 那个临时服务也有同样的毛病,只是平时不会拿它上生产。Nginx 解决的就是这些入口层问题——它站在应用前面,接住所有 80/443 请求,按域名和路径分流:静态文件自己直接返回,动态请求转发给后端应用。
二、Nginx 是怎么工作的
装上 Nginx 跑起来,先看一眼它的进程:
bash
ps -ef | grep '[n]ginx'text
root 1234 1 0 10:00 ? 00:00:00 nginx: master process /usr/sbin/nginx
nginx 1235 1234 0 10:00 ? 00:00:00 nginx: worker process
nginx 1236 1234 0 10:00 ? 00:00:00 nginx: worker process
nginx 1237 1234 0 10:00 ? 00:00:00 nginx: worker process一个 master 进程,几个 worker 进程。master 不直接处理请求,只管 worker 的启停和配置 reload。worker 才是真正接请求干活的。
这个分工的好处是 reload 配置时连接不断——master 启动新 worker 用新配置,旧 worker 处理完手中请求后退出。如果应用自己监听端口,重启就断连接;Nginx 在前面挡着,应用重启时 Nginx 把请求暂时转给其他后端,用户感知不到。
text
nginx: master process /usr/sbin/nginx
├── nginx: worker process
├── nginx: worker process
└── nginx: worker process三、安装
测试和普通生产环境用系统包最省事:
bash
# RHEL / Rocky / CentOS
yum install -y nginx
systemctl enable --now nginx
nginx -v
# Debian / Ubuntu
apt update
apt install -y nginx
systemctl enable --now nginx
nginx -v装完后常见路径(不同发行版可能有差异,用 rpm -ql nginx 或 dpkg -L nginx 确认):
| 路径 | 用途 |
|---|---|
/etc/nginx/nginx.conf | 主配置文件 |
/etc/nginx/conf.d/*.conf | 虚拟主机配置目录,主配置通过 include 引入 |
/usr/share/nginx/html | 包安装的默认静态文件目录 |
/var/log/nginx/access.log | 访问日志 |
/var/log/nginx/error.log | 错误日志 |
/usr/sbin/nginx | Nginx 主程序 |
四、配置长什么样
打开主配置 /etc/nginx/nginx.conf,第一眼会看到大括号一层套一层:
nginx
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}这些大括号就是 Nginx 配置的分层结构。events {} 里放连接数这种事件参数,http {} 里放 HTTP 全局设置。改配置时改错层会不生效——比如把 server_name 写在 http 外面、把 worker_processes 写进 http 里面,Nginx 都不认。
这几层从外到内是:
| 上下文 | 放什么 |
|---|---|
| 最外层(main) | 用户、worker 数、错误日志、pid |
events {} | worker 连接数等事件模型参数 |
http {} | HTTP 全局:日志格式、gzip、mime、include |
server {} | 虚拟主机:监听端口、域名、根目录 |
location {} | URI 匹配后的行为:读文件、返回状态、转发代理 |
后两层 server 和 location 一般不写在主配置里,而是写在 /etc/nginx/conf.d/*.conf 里——主配置最后一行的 include /etc/nginx/conf.d/*.conf; 把它们引进来。这样多个站点各自一个文件,互不干扰。
改配置要先检查语法:
bash
nginx -t # 检查语法和引用文件是否存在通过后 reload:
bash
systemctl reload nginxreload 是平滑的——不中断现有连接,旧 worker 处理完手中请求后退出,新 worker 用新配置。nginx -t 只看语法,不看逻辑——root 目录不存在它不会报错。还有一个常见的坑:主配置语法正确,但 conf.d 里某个 include 文件写错了,nginx -t 一样失败。
reload 和 restart 的区别要记牢:reload 平滑不掉连接,restart 暴力停进程再启动、连接全断。改 LimitNOFILE 这类 systemd 级别的参数才需要 restart,改 Nginx 配置绝大多数情况用 reload。
五、配一个静态站点
手上的网页还在 /data/www/example/index.html。Python 服务停掉,换成 Nginx 来服务它。
先把文件权限给对。Nginx worker 以 nginx(或 www-data)用户运行,目录没给这个用户读权限,访问会返回 403:
bash
mkdir -p /data/www/example
echo 'hello nginx' > /data/www/example/index.html
chown -R nginx:nginx /data/www/example # RHEL 系运行用户
# Debian/Ubuntu 运行用户是 www-data:
# chown -R www-data:www-data /data/www/example写一个虚拟主机配置 /etc/nginx/conf.d/example.conf:
nginx
server {
listen 80;
server_name example.local;
root /data/www/example;
index index.html;
location / {
try_files $uri $uri/ =404; # 依次尝试文件、目录,都不存在返回 404
}
access_log /var/log/nginx/example.access.log;
error_log /var/log/nginx/example.error.log warn;
}检查语法、reload、验证:
bash
nginx -t && systemctl reload nginx
curl -H 'Host: example.local' http://127.0.0.1/返回 hello nginx,说明请求命中了这个 server。
这里有个容易卡住的地方:直接 curl http://127.0.0.1/ 不带 Host 头,返回的可能不是这个站点的内容。原因是 Nginx 靠 Host 头匹配 server_name——浏览器访问 http://example.local/ 时,请求头里 Host: example.local,Nginx 据此找到这个 server。curl 默认不带这个头,请求会落到默认虚拟主机。所以验证时要加 -H 'Host: example.local' 模拟浏览器的 Host 头。
try_files 不写会怎样:Nginx 默认按 index 找文件,找不到就返回 403 或 404,行为不够明确。写 try_files $uri $uri/ =404 是显式控制——先找文件、再找目录、都没有返回 404。
六、静态资源与 API 混合
前后端分离应用最常见的配置:前端静态文件本机返回,接口请求转发给后端:
nginx
upstream app_api {
server 127.0.0.1:8080;
}
server {
listen 80;
server_name app.example.local;
root /data/www/app;
index index.html;
location / {
try_files $uri $uri/ /index.html; # SPA 路由:找不到文件时返回入口 HTML
}
location /static/ {
expires 7d; # 静态资源让浏览器缓存 7 天
try_files $uri =404;
}
location /api/ {
proxy_pass http://app_api; # 接口请求转给后端
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}try_files $uri $uri/ /index.html 是 SPA(单页应用)的标准写法——用户访问 /user/profile 时,服务器上没有这个文件,但前端路由需要 index.html 来接管这个路径。如果只是普通多页站点,用 try_files $uri $uri/ =404 就够了。混用的话,普通站点的 404 会被吞掉、返回首页内容,反而难排查。
排查思路:静态资源 404 多半是目录路径和权限问题,API 502/504 多半是后端连接或响应问题。先按 location 分流判断是哪一类。
七、虚拟主机
同一台 Nginx 可以跑多个站点,按 listen 端口和 Host 头区分:
nginx
server {
listen 80;
server_name app.example.local;
root /data/www/app;
location / {
try_files $uri $uri/ =404;
}
}
server {
listen 80;
server_name admin.example.local;
root /data/www/admin;
location / {
try_files $uri $uri/ =404;
}
}验证不同域名:
bash
curl -H 'Host: app.example.local' http://127.0.0.1/
curl -H 'Host: admin.example.local' http://127.0.0.1/没有匹配到 server_name 的请求会落到默认虚拟主机(第一个定义的 server,或显式标记 default_server 的那个)。建议显式写一个默认的拒绝站点,避免未知域名的探测请求打到真实业务:
nginx
server {
listen 80 default_server;
server_name _;
return 444; # Nginx 特有的状态码——直接关闭连接,不返回任何内容
}不写默认拒绝站点会怎样:随便一个 IP 直接访问机器 80 端口,Nginx 会用第一个 server 响应,把真实业务内容返回给未知来源的探测。
八、location 规则
location 决定一个 URI 怎么被处理。几种写法的优先级:
| 写法 | 含义 | 优先级 |
|---|---|---|
location = /health | 精确匹配,只有 /health 命中 | 最高 |
location ^~ /static/ | 前缀匹配,命中后不再检查正则 | 高于正则 |
location ~ \.php$ | 区分大小写的正则匹配 | 按配置顺序 |
location ~* \.(jpg|png)$ | 不区分大小写的正则匹配 | 按配置顺序 |
location /api/ | 普通前缀匹配 | 最低,但选最长匹配 |
示例:
nginx
server {
listen 80;
server_name example.local;
root /data/www/example;
location = /health {
return 200 "ok\n"; # 健康检查,直接返回文本
}
location ^~ /static/ {
expires 7d;
try_files $uri =404;
}
location / {
try_files $uri $uri/ =404;
}
}root 和 alias 的差别容易踩坑:
nginx
location /static/ {
root /data/www/example; # /static/a.png → /data/www/example/static/a.png
}
location /assets/ {
alias /data/cdn/; # /assets/a.png → /data/cdn/a.png (location 前缀被替换)
}root 是把 location 路径拼接在 root 目录后面;alias 是把 location 路径替换成 alias 目录。静态文件 404 时,先看 error_log 里 Nginx 实际去找的文件路径是什么——根据真实路径反推配置比猜要准。
九、常用命令
bash
nginx -t # 检查配置语法
nginx -T # 打印完整合并后的配置(include 展开后)
systemctl start nginx # 启动
systemctl reload nginx # 平滑重载配置(不中断连接)
systemctl restart nginx # 重启(连接会断开)
systemctl stop nginx # 停止
journalctl -u nginx -n 100 --no-pager
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.lognginx -T 比 nginx -t 多一步——它不仅检查语法,还把 include 后的完整配置打印出来。排查"配置明明改了但好像没用"时,nginx -T 能发现是改错了文件,还是被其他 include 覆盖了。
十、启动问题
| 现象 | 可能原因 | 排查入口 |
|---|---|---|
nginx -t 失败 | 配置语法错误、include 的文件不存在 | 错误输出、nginx -T |
| 端口被占用 | 80/443 已被其他进程监听 | ss -lntp | grep ':80' |
| 403 Forbidden | 目录权限不足、缺少 index 文件、被 deny 指令拦截 | error.log 会写具体原因 |
| 404 Not Found | root/alias 路径不对、URI 匹配不对 | error.log 里的实际文件路径 |
| 访问到了默认站点 | Host 头没匹配到目标 server_name | curl -H 'Host:' 验证 |
| reload 后配置没变 | reload 失败,旧 worker 仍在跑 | nginx -t 看新配置是否通过 |
排查思路顺着请求链路走:请求是否到达机器 → 是否命中正确的 server → 是否命中正确的 location → 最终是读文件还是转发。沿着这条线查,比直接翻一堆配置有效。