Nginx 是个人网站服务器上最常见的搭档:轻量、稳定、配置直观。但很多人的 Nginx 配置都是"抄来能用就行"——能跑,却说不出每一行的意思,遇到问题也不知道从哪查起。这篇文章把 Nginx 配置从头讲一遍:文件结构、server 与 location 的匹配规则、静态站点的常用配置,以及最容易踩的坑。

文中配置以本站(一个部署在 Nginx 上的纯静态站)为例,所有片段都经过实践验证。下面的示例统一使用占位域名与目录——www.your-domain.com/var/www/your-domain——实际操作时替换成你自己的域名和站点目录即可。系统是 Debian/Ubuntu 系的布局,其他发行版路径略有差异,稍加对照即可。

一、Nginx 怎么读配置

Nginx 的配置不是一个大文件,而是分层的。最核心的是主配置 /etc/nginx/nginx.conf,它定义了进程模型(worker_processes)、事件模型(events)以及最外层的 http 块。而每个网站的配置,通常放在 http 块里通过 include 引入:

# /etc/nginx/nginx.conf(节选)
user www-data;
worker_processes auto;   # 一般等于 CPU 核心数

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    sendfile on;

    include /etc/nginx/conf.d/*.conf;        # 通用片段
    include /etc/nginx/sites-enabled/*;      # 各站点配置(Debian 系布局)
}

Debian/Ubuntu 上的约定是:每个站点一个文件放在 /etc/nginx/sites-available/,然后用软链接把它"启用"到 /etc/nginx/sites-enabled/。这样禁用一个站点只需删软链接,原文件还在,方便回滚:

sudo ln -s /etc/nginx/sites-available/your-domain /etc/nginx/sites-enabled/your-domain
sudo nginx -t                      # 语法检查
sudo systemctl reload nginx        # 平滑重载
习惯:改完配置先 nginx -treload,不要用 restart。reload 只重载配置、不断开现有连接;restart 会中断所有正在进行的请求。

二、最小的 server 块

每个网站的核心是一个 server 块:它声明"我要监听哪个端口、为哪个域名服务、文件在哪里"。一个纯静态站的最小配置长这样:

server {
    listen 80;
    server_name www.your-domain.com;

    root /var/www/your-domain;
    index index.html;

    error_page 404 /404.html;
    location = /404.html { internal; }
}

逐行解释:

  • listen 80:监听 80 端口(HTTP 默认端口);
  • server_name:这个站点的域名。Nginx 收到请求后,按 Host 头里的域名挑选对应的 server 块;
  • root:站点根目录。请求 /style.css 时,Nginx 去 /var/www/your-domain/style.css 找文件;
  • index:访问目录时默认找的文件,所以访问 / 会加载 index.html
  • error_page 404 /404.html:发生 404 时交给我们自己的 404 页面;配一个 location = /404.html { internal; } 可以防止访客直接访问这个内部页面以外的路径。

没有写 server_name 命中逻辑时,Nginx 会把第一个 server 块当作默认站点。因此如果你有多站配置,记得给不想当默认的那个也写上明确的 server_name

三、server_name 与 location 匹配规则

整个 Nginx 配置里最值得花时间理解的,是 location 的匹配优先级。规则就五条,从高到低:

  • = 精确匹配location = /404.html 只匹配这一个路径,优先级最高;
  • ^~ 前缀匹配:命中后直接使用,不再继续查下面的正则;
  • 正则匹配~ 区分大小写、~* 不区分):按配置文件里的书写顺序,命中即停;
  • 普通前缀匹配:在所有普通前缀里取最长的那个;
  • 如果以上都没命中,就走 location / 这个"兜底"。

静态站最常用的一个技巧,是把图片、CSS、JS 这类静态资源单独匹配出来做缓存:

location ~* \.(svg|webp|png|jpg|jpeg|ico)$ {
    expires 30d;
    add_header Cache-Control "public";
}

location ~* \.(html|css|js|json)$ {
    expires 1h;
}

这里用正则把请求按文件类型分流:图片类缓存 30 天,页面类缓存 1 小时——重新发布后刷新浏览器即可看到更新,同时日常浏览又不至于每次全量下载。

四、root、alias 与 try_files

rootalias 是新手最容易搞混的一对。记住一句话:root 会拼接 location 的路径,alias 不会

# root:/img/a.png → 实际找 /var/www/site/img/a.png
location /img/ {
    root /var/www/site;
}

# alias:/img/a.png → 实际找 /var/www/static/a.png
location /img/ {
    alias /var/www/static/;
}

对纯静态站来说,还有一个实用指令 try_files:按顺序尝试找文件,都找不到就走最后一个兜底。它的经典用途是让"不存在的路径"统一交回首页或 404:

location / {
    try_files $uri $uri/ =404;
}

本站这类"只有真文件"的站,用 =404 结尾最合适:文件不存在就直接交给 error_page 处理,不产生多余的转发。

五、开启 gzip 压缩

文本类资源(HTML、CSS、JS、JSON、SVG)压缩后能小 60%–80%,是性价比极高的一行配置。在 http 块(或 server 块)里开启:

gzip on;
gzip_min_length 1k;                 # 小于 1k 的文件不压,压了反而更大
gzip_comp_level 6;                  # 1-9,6 是体积与 CPU 的平衡点
gzip_types text/plain text/css application/javascript application/json
           image/svg+xml application/xml;
gzip_vary on;                       # 输出 Vary: Accept-Encoding

注意:图片(jpg/png/webp)本身已是压缩格式,再开 gzip 只会浪费 CPU,所以不在 gzip_types 里列它们。

六、安全响应头:add_header 与它的坑

给响应加安全头,是让网站"看起来专业、用起来安全"的重要一步。本站 HTML 里已经用 meta 写了一份 CSP 作为静态托管的兜底,但服务器响应头优先级更高,正式部署建议在 Nginx 里再配一份:

add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;

# CSP:值与页面 meta 保持一致即可(按实际引用的 CDN 调整;本站只引用 cdnjs,图片全部为站内资源)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; img-src 'self' data:; font-src 'self' https://cdnjs.cloudflare.com; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self';" always;

这里有一个几乎人人踩过的坑:add_header 的继承规则。server 层(或 http 层)定义的 add_header,只有当某个 location 里没有定义任何 add_header时才会被继承;一旦 location 里自己写了一个 add_header,server 层的就全部失效。所以上面第三节给静态资源加 Cache-Control 的 location,会"丢掉"所有安全头——解决办法是把安全头也写进那些 location,或者把安全头统一放在一个被 include 的片段里。

验证方式很简单,用 curl 看响应头:

curl -I http://www.your-domain.com/
# 预期能看到上面配置的 X-Frame-Options、CSP 等

七、日志:网站的另一双眼睛

Nginx 默认把访问日志写在 /var/log/nginx/access.log,错误日志在 /var/log/nginx/error.log。访问日志默认格式 main 大致是:

log_format main '$remote_addr - $remote_user [$time_local] '
                '"$request" $status $body_bytes_sent '
                '"$http_referer" "$http_user_agent"';

日常最常用的两个动作:一是看某段时间的访问量,二是排查"为什么页面打不开"。页面 500/502 时第一件事就是看 error.log,而不是瞎猜:

sudo tail -f /var/log/nginx/error.log    # 实时看错误
sudo tail -n 100 /var/log/nginx/access.log
sudo grep " 404 " /var/log/nginx/access.log | tail -20   # 谁在访问不存在的页面

结合上一篇文章里的服务器安全实践,定期瞄一眼 404 日志还有个额外好处:很多扫描器就是在不断探测不存在的路径——异常密集的 404 往往意味着有人在对你的服务器做探测。

八、常见坑速查

  • 改完没生效:多半忘了 reload;先 nginx -t 确认语法,再 systemctl reload nginx
  • 别的站的配置污染了我的站:确认每个 server 块都写了唯一的 server_name,否则请求会落到"第一个 server";
  • 安全头时有时无:八成是 location 里写了 add_header,把上层安全头"顶掉"了(见第六节);
  • 404 页面样式全丢:检查 error_page 指向的路径、以及该页面的 CSS 相对路径是否在站点根下;
  • 想限制上传/请求体大小:用 client_max_body_size 10m;(纯静态站通常用不到,反向代理时常见)。

结语

到这里,一个静态站点的 Nginx 配置从结构、匹配规则到缓存、压缩、安全头就都齐了。这套配置的完整形态,其实就是本站 README 里那份 Nginx 示例的来历——如果你照着自己配一遍,再看 README,每一行都能对上号。

还没讲的部分是 HTTPS:用 certbot 签免费证书、把 80 端口跳转到 443、给安全头补上 HSTS——那是让地址栏亮起小锁的最后一步,留到下一篇。在那之前,如果你还没做过服务器侧的入口防护,建议先读这篇《服务器 SSH 加固实践》,把门锁好再装修。