接入 Nginx: 静态资源部署与 location 路径匹配
纲要
「部署静态资源」,是Nginx三大应用的第一个:
- 什么是静态资源:服务端真实存在、可直接返回的文件
- 为什么不用
Tomcat部署静态资源:sendfile零拷贝、epoll模型、无JVM开销 - 部署方式:把文件复制到
html目录即可 - 逐条解释配置:
listen、server_name、location、root、index - 实操演进:部署
hello.html→ 设为默认首页 → 改端口 → 多server共存 - 真实踩坑:漏分号导致
reload失败、浏览器缓存导致改了没生效
一、什么是静态资源
静态资源指在服务端真实存在、内容固定、可以直接返回给客户端的文件,不需要任何计算或数据库查询:
| 类型 | 扩展名 | 说明 |
|---|---|---|
| 页面 | .html/.htm | 静态HTML |
| 样式 | .css | 层叠样式表 |
| 脚本 | .js | JavaScript |
| 图片 | .jpg/.png/.gif/.svg | 图片资源 |
| 字体 | .woff/.woff2/.ttf | 字体文件 |
| 音视频 | .mp4/.mp3 | 媒体文件 |
外卖平台里的具体例子:移动端H5页面、后台管理的Vue打包产物、菜品图片、商家logo。
与之相对的是动态资源:需要程序运算生成的响应,比如Spring Boot返回的JSON订单列表——每次请求结果都可能不同,必须交给应用服务器。
二、为什么用 Nginx 而不是 Tomcat
Tomcat也能部署静态资源(放webapps下即可),但生产上几乎不会这么做。差距来自三点:
sendfile零拷贝
Tomcat读文件发送要经历 4 次数据搬运、2 次上下文切换:
磁盘 → 内核缓冲区 → 用户缓冲区 → socket 缓冲区 → 网卡Nginx开启sendfile on后,数据在内核态直接从文件描述符送到socket:
磁盘 → 内核缓冲区 → 网卡省掉两次CPU拷贝和全部用户态/内核态切换。传大文件(菜品图片、视频)时差距尤其明显。
并发模型
Tomcat一请求一线程,并发受限于maxThreads(默认 200)。静态资源请求虽然处理快,但会长时间占住线程(尤其是慢速客户端)。Nginx的epoll单worker就能维持数万连接,慢客户端几乎不消耗额外资源。
无 JVM 开销
Nginx是C写的,常驻内存几MB;Tomcat光JVM堆就要几百MB起步。把宝贵的JVM资源留给业务逻辑是常识。
典型架构:Nginx在最外层,命中静态资源的直接返回,动态请求才proxy_pass给Tomcat。
三、部署实操
第一步:准备静态页面
课程提供了一个hello.html,内容如下:
<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><title>hello Nginx</title></head><body><h2>hello Nginx ...</h2></body></html>把它放到/根目录(也可以放在任意位置,只要cp时写对路径)。
第二步:复制到 html 目录
cp/hello.html /usr/local/nginx/html/验证:
cd/usr/local/nginx/htmlls# 50x.html hello.html index.html这就是全部部署动作——因为默认配置里root html;已经指向这个目录。
第三步:启动并访问
nginxps-ef|grepnginx浏览器访问:
http://192.168.138.100/hello.html看到hello Nginx ...即部署成功。
访问http://192.168.138.100(不带文件名)则显示默认的Welcome to nginx!,因为默认首页是index.html。
四、关键配置逐条解释
打开配置文件:
vim/usr/local/nginx/conf/nginx.conf核心部分:
server { listen 80; # 监听端口 server_name localhost; # 服务器名称(域名) location / { # 匹配客户端请求 URI root html; # 指定静态资源根目录 index index.html index.htm; # 指定默认首页 } }listen—— 监听端口
listen 80;Nginx在哪个端口等待连接。常见写法:
| 写法 | 含义 |
|---|---|
listen 80; | 监听所有网卡的80端口 |
listen 81; | 监听81端口 |
listen 127.0.0.1:80; | 只监听本机回环,外部访问不到 |
listen 443 ssl; | HTTPS |
listen 80 default_server; | 作为该端口的默认虚拟主机 |
改成81后,原来的80就访问不到了。这是课程里演示过的一步:改端口 →reload→ 用80访问失败 → 用81访问成功。
server_name—— 服务器名称
server_name localhost;学习阶段没有域名就写localhost;生产环境写真实域名,如server_name www.example.com;。
它的真正作用在于多个server监听同一端口时的分流:Nginx用请求头里的Host值去匹配各server的server_name,匹配到哪个就交给哪个处理。
server { listen 80; server_name admin.example.com; # 后台管理 location / { root html/admin; } } server { listen 80; server_name m.example.com; # 移动端 location / { root html/mobile; } }这样一个80端口就能托管两个站点。支持通配符(*.example.com)和正则(~^www\d+\.example\.com$)。
location /—— 路径匹配
location / { ... }location后面跟的是匹配规则,/表示匹配所有请求。
常见匹配方式(优先级从高到低):
| 写法 | 类型 | 含义 |
|---|---|---|
location = /path | 精确匹配 | 只匹配完全相同的URI |
location ^~ /static/ | 前缀匹配(优先) | 匹配前缀,且不再检查正则 |
location ~ \.php$ | 正则匹配(区分大小写) | 按正则匹配 |
location ~* \.png$ | 正则匹配(不区分大小写) | 按正则匹配 |
location / | 通用前缀匹配 | 兜底,优先级最低 |
外卖平台的典型配置:
# 精确匹配首页 location = / { root html; index index.html; } # 静态资源走本地,设置长缓存 location ^~ /static/ { root html; expires 7d; } # 图片单独配置 location ~* \.(jpg|jpeg|png|gif|ico|svg)$ { root html; expires 30d; access_log off; } # 动态请求转发后端 location /api/ { proxy_pass http://192.168.138.101:8080; }root—— 静态资源根目录
root html;html是相对路径,基准是Nginx安装目录/usr/local/nginx,所以实际路径是/usr/local/nginx/html。
也可以写绝对路径,语义更清晰:
root /usr/local/nginx/html;请求http://192.168.138.100/hello.html的解析过程:
URI = /hello.html root = /usr/local/nginx/html 最终文件路径 = /usr/local/nginx/html + /hello.html = /usr/local/nginx/html/hello.htmlindex—— 默认首页
index index.html index.htm;当请求的URI以/结尾(即访问目录而非具体文件)时,Nginx依次尝试这些文件。
- 找到第一个存在的就返回
- 都不存在且未开启目录列表,返回
403 Forbidden(目录不可访问) - 路径本身不存在,返回
404 Not Found
多个值用空格分隔,不是逗号:
index index.html index.htm index.php;五、把 hello.html 设为默认首页
修改配置
vim/usr/local/nginx/conf/nginx.conf找到index那一行,注释掉原配置,另起一行:
location / { root html; # index index.html index.htm; index hello.html; }保存退出(:wq)。
踩坑:漏写分号
如果写成:
index hello.html ← 少了分号 }nginx -s reload会报:
nginx: [emerg] unexpected "}" in /usr/local/nginx/conf/nginx.conf:46注意报错行号 46 是花括号所在行,真正的问题在上一行。Nginx把下一行的}当成了index指令的延续,直到遇见{或不匹配的括号才报错。
修正:
vim/usr/local/nginx/conf/nginx.conf :set nu# 显示行号# 找到对应行,补上分号这个坑的教训:改配置后先nginx -t,它会直接报directive "index" is not terminated by ";",比reload的报错更准确。
重新加载
nginx-t# nginx: the configuration file ... syntax is ok# nginx: configuration file ... test is successfulnginx-sreload验证
访问http://192.168.138.100(不带文件名),现在直接显示hello Nginx ...。
如果还是显示旧页面,是浏览器缓存。做一次强制刷新:
Ctrl + F5 (Windows) Cmd + Shift + R (Mac)或者在开发者工具Network面板勾选Disable cache。这是前端调试时的高频困惑——配置明明生效了,但浏览器拿的是本地缓存。
六、修改监听端口
server { listen 81; # 从 80 改为 81 server_name localhost; location / { root html; index hello.html; } }nginx-tnginx-sreload验证:
http://192.168.138.100→ 访问不到(80已无监听)http://192.168.138.100:81→ 正常访问
注意:非80端口必须在URL里显式写出。同时别忘了防火墙放行:
firewall-cmd--zone=public --add-port=81/tcp--permanentfirewall-cmd--reload七、配置多个 server
一个http块里可以有多个server,同时监听不同端口:
http { include mime.types; default_type application/octet-stream; sendfile on; # 站点一:80 端口,默认首页 server { listen 80; server_name localhost; location / { root html; index hello.html; } } # 站点二:81 端口,原默认页面 server { listen 81; server_name localhost; location / { root html; index index.html index.htm; } } }nginx-tnginx-sreload现在80和81都能访问,且返回不同页面。
也可以让两个server监听同一端口,用server_name区分(需要配置hosts或真实域名才能验证):
server { listen 80; server_name admin.reggie.com; location / { root /usr/local/nginx/html/admin; index index.html; } } server { listen 80; server_name m.reggie.com; location / { root /usr/local/nginx/html/mobile; index index.html; } }八、外卖平台的前端部署结构
把这套思路套到外卖平台上,最终的静态资源目录会是:
/usr/local/nginx/html/ ├── backend/ # 后台管理系统(Vue 打包产物) │ ├── index.html │ ├── static/ │ │ ├── css/ │ │ ├── js/ │ │ └── fonts/ │ └── favicon.ico ├── front/ # 移动端 H5 │ ├── index.html │ └── static/ └── img/ # 上传的菜品图片 ├── dish-xxxx.jpg └── setmeal-yyyy.png对应配置:
server { listen 80; server_name localhost; # 后台管理系统 location /backend/ { alias /usr/local/nginx/html/backend/; index index.html; try_files $uri $uri/ /backend/index.html; # 支持 history 路由 } # 移动端 location /front/ { alias /usr/local/nginx/html/front/; index index.html; try_files $uri $uri/ /front/index.html; } # 上传的图片 location /img/ { alias /usr/local/nginx/html/img/; expires 30d; access_log off; } # 动态请求转发后端 location /api/ { proxy_pass http://192.168.138.101:8080/; } }三个要点:
try_files是SPA必需的:Vue Router用history模式时,/backend/dish/list这个URI在磁盘上不存在对应文件,必须回退到index.html交给前端路由处理,否则刷新页面会 404alias比root更直观:/backend/→/usr/local/nginx/html/backend/,不需要理解拼接规则- 图片配
expires+access_log off:长缓存头减少重复请求,关掉访问日志减少磁盘IO
九、常见问题
| 现象 | 原因 | 解决 |
|---|---|---|
| 访问返回 404 | 文件不在root指定的目录 | 检查文件路径与root配置 |
| 访问返回 403 | 目录不可读,或首页文件都不存在 | 检查文件权限、index配置 |
CSS/JS加载后页面样式全无 | 未include mime.types或MIME类型不对 | 检查http块里的include |
| 改了配置没生效 | 忘记reload | nginx -s reload |
reload后仍是旧页面 | 浏览器缓存 | Ctrl + F5强制刷新 |
reload报unexpected "}" | 上一行漏了分号 | vim里:set nu定位,补分号 |
| 改了端口访问不到 | 防火墙未放行新端口 | firewall-cmd --add-port=81/tcp --permanent |
| 图片上传报 413 | client_max_body_size默认1m | 在http块调大,如client_max_body_size 100m; |
API 速览
| 指令 | 作用 |
|---|---|
listen <port> | 监听端口 |
server_name <name> | 虚拟主机域名,用于同端口多站点分流 |
location [= | ^~ | ~ | ~*] <uri> | 路径匹配块,符号决定匹配方式与优先级 |
root <path> | 静态资源根目录,最终路径 =root+ 完整URI |
alias <path> | 路径别名,最终路径 =alias+ 去掉location前缀的剩余部分 |
index <file>... | 默认首页,空格分隔多个,依次查找 |
try_files <file>... <fallback> | 依次尝试文件,都不存在则回退(如/index.html) |
expires <time> | 设置响应缓存头Cache-Control/Expires |
access_log off | 关闭该location的访问日志 |
client_max_body_size <size> | 允许的最大请求体,默认1m |
cp <src> <dest> | 复制文件到html目录完成部署 |
vim +:set nu | 编辑时显示行号,便于定位报错行 |
官方文档
Nginx静态资源服务:https://nginx.org/en/docs/http/ngx_http_core_module.html#locationNginxBeginner's Guide静态内容:https://nginx.org/en/docs/beginners_guide.html#staticNginxroot指令:https://nginx.org/en/docs/http/ngx_http_core_module.html#rootNginxalias指令:https://nginx.org/en/docs/http/ngx_http_core_module.html#aliasNginxindex指令:https://nginx.org/en/docs/http/ngx_http_index_module.htmlNginxtry_files指令:https://nginx.org/en/docs/http/ngx_http_core_module.html#try_files
总结
部署静态资源就是把文件复制到html目录。默认配置的root html;已经指向/usr/local/nginx/html,location /匹配所有请求,index index.html指定默认首页。
Nginx处理静态资源强于Tomcat,核心是sendfile零拷贝 +epoll高并发 + 无JVM开销。生产上静态资源一律走Nginx,只把动态请求转发给应用服务器。
五个关键指令要分清:listen管端口、server_name管域名分流、location管路径匹配、root/alias管文件根路径、index管默认首页。其中root与alias的拼接规则不同——root拼完整URI,alias替换掉location前缀,这是静态资源 404 的头号原因。
改配置的完整流程:vim改 →nginx -t检查 →nginx -s reload生效 → 浏览器Ctrl + F5强制刷新验证。四步缺一不可,尤其是最后一步——浏览器缓存造成的"改了没生效"是最高频的假故障。
漏分号的报错行号会滞后一行。看到unexpected "}"就往上翻一行找分号;用vim的:set nu能快速定位。先nginx -t再reload能拿到更准确的错误信息(directive is not terminated by ";")。
SPA部署必须加try_files。否则Vue的history路由在刷新时会出现 404,因为服务端磁盘上不存在/backend/dish/list这样的文件。
下一篇讲Nginx最重要的能力:反向代理——把动态请求转发给后端的Spring Boot,让前后端路径统一在同一个域名和端口下。