Skip to content

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 nginxdpkg -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/nginxNginx 主程序

四、配置长什么样

打开主配置 /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 匹配后的行为:读文件、返回状态、转发代理

后两层 serverlocation 一般不写在主配置里,而是写在 /etc/nginx/conf.d/*.conf 里——主配置最后一行的 include /etc/nginx/conf.d/*.conf; 把它们引进来。这样多个站点各自一个文件,互不干扰。

改配置要先检查语法:

bash
nginx -t        # 检查语法和引用文件是否存在

通过后 reload:

bash
systemctl reload nginx

reload 是平滑的——不中断现有连接,旧 worker 处理完手中请求后退出,新 worker 用新配置。nginx -t 只看语法,不看逻辑——root 目录不存在它不会报错。还有一个常见的坑:主配置语法正确,但 conf.d 里某个 include 文件写错了,nginx -t 一样失败。

reloadrestart 的区别要记牢: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 据此找到这个 servercurl 默认不带这个头,请求会落到默认虚拟主机。所以验证时要加 -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;
    }
}

rootalias 的差别容易踩坑:

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.log

nginx -Tnginx -t 多一步——它不仅检查语法,还把 include 后的完整配置打印出来。排查"配置明明改了但好像没用"时,nginx -T 能发现是改错了文件,还是被其他 include 覆盖了。

十、启动问题

现象可能原因排查入口
nginx -t 失败配置语法错误、include 的文件不存在错误输出、nginx -T
端口被占用80/443 已被其他进程监听ss -lntp | grep ':80'
403 Forbidden目录权限不足、缺少 index 文件、被 deny 指令拦截error.log 会写具体原因
404 Not Foundroot/alias 路径不对、URI 匹配不对error.log 里的实际文件路径
访问到了默认站点Host 头没匹配到目标 server_namecurl -H 'Host:' 验证
reload 后配置没变reload 失败,旧 worker 仍在跑nginx -t 看新配置是否通过

排查思路顺着请求链路走:请求是否到达机器 → 是否命中正确的 server → 是否命中正确的 location → 最终是读文件还是转发。沿着这条线查,比直接翻一堆配置有效。