起因 事情的起因来自一封邮件,年前上线了一个项目官网,在过年期间,突然收到了一封来自安全部门邮件,说上线的官网存在以上安全风险,让尽快处理。现在看来解决这个问题很容易,只需要在Nginx上的server 块中配置 server_name为备案域名,再设置一个默认的server模块,匹配所有未明确指定 server_name
的请求并返回403就OK,但是当时对Nginx并不熟悉,导致走了些弯路。所以这篇文章主要是前端在项目部署中,针对常见的Nginx功能的介绍。基于目前前端项目部署方式大多采用Docker的CI/CD工作流来进行,所以也会涉及到Docker以及常见项目部署配置的相关知识。
Nginx介绍 Nginx是由伊戈尔·赛索耶夫在2002年创建的,最初是为了解决C10K问题,也就是同时处理上万个并发连接的问题。Apache服务器当时在处理大量并发时效率不高,所以Nginx采用了事件驱动的异步架构,性能更好,资源消耗更低。后来在2004年公开发布,凭借其轻量级、事件驱动、异步非阻塞的架构,迅速成为高性能 Web 服务器和反向代理的热门选择。
前端常用的Nginx功能 我们将从以下方面介绍前端开发与项目部署中常见的Nginx相关功能,主要涉及:基于Docker的Nginx安装、配置文件、静态资源托管、反向代理、负载均衡、HTTPS 配置(SSL 证书)等
基于Docker的Nginx安装 基于Mac电脑,假设你已经安装好看Docker,通过下面的一行命令就可以安装基于Docker容器的Nginx
docker run -d -p 80:80 --name my-nginx nginx
这段命令用于运行一个 Nginx Docker 容器,并将其端口映射到主机的端口。
docker run
:这是 Docker 命令,用于创建并运行一个新的容器。
-d
:这是一个选项,表示以分离模式(detached mode)运行容器。容器将在后台运行,而不会占用当前终端。
-p 80:80
:这是一个端口映射选项,格式为 主机端口:容器端口, 80:80 表示将主机的端口 80 映射到容器的端口 80。这样,访问主机的端口 80 时,实际上是在访问容器的端口 80。
--name my-nginx
:这是一个选项,用于为容器指定一个名称。在这里,容器的名称被指定为 my-nginx。 这样你可以通过这个名称来管理和引用这个容器,而不需要使用容器的 ID。
nginx
:这是要运行的镜像名称。在这里,使用的是官方的 Nginx 镜像。 Docker 会从 Docker Hub 拉取这个镜像(如果本地没有的话),并基于这个镜像创建并运行一个新的容器。
当我们在浏览器访问: http://localhost,若看到欢迎页即安装成功。
配置文件 当我们在主机上运行my-nginx容器后,可以通过命令:
docker exec -it my-nginx /bin/sh
来进入到容器中,查看和修改nginx的相关配置,nginx的主配置文件为: /etc/nginx/nginx.conf,执行
cat nginx.conf
查看文件为:
user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; # 引入站点配置 include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; }
这是 Nginx 的核心配置文件,定义了全局配置(如 Worker 进程数、日志格式、事件模型等)。通常不直接在这里配置具体的站点,而是通过 include 指令引入其他配置文件。我们一般不会修改这里的主配置文件,而是通过下面的/etc/nginx/conf.d/ 或 /etc/nginx/sites-available/来进行站点配置文件的配置。
站点配置目录: /etc/nginx/conf.d/
或 /etc/nginx/sites-available/
为什么有两个目录?
/etc/nginx/conf.d/: 这是一个简单的配置目录,通常用于存放单个站点的配置文件。每个文件通常对应一个站点或服务,文件名以 .conf 结尾。适合小型项目或简单场景。也是我们前端开发最常用的目录。
/etc/nginx/sites-available/ 和 /etc/nginx/sites-enabled/: 这是一个更灵活的配置管理方式,适合复杂的多站点场景。
/sites-available/
: 存放所有站点的配置文件(相当于“配置仓库”), /sites-enabled/
: 存放当前启用的站点配置,这种方式可以方便地启用或禁用站点,而无需删除配置文件。
执行 cat /etc/nginx/conf.d/default.conf
查看默认的配置文件
server { listen 80; listen [::]:80; server_name localhost; root /usr/share/nginx/html; index index.html index.htm; #access_log /var/log/nginx/host.access.log main; location / { try_files $uri $uri/ /index.html; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
我们部署前端项目,通过Nginx实现的相关功能,都是在这个文件中进行相关字段的配置而实现的。
server
: 用于定义一个虚拟主机(Virtual Host),它决定了 Nginx 如何处理来自不同域名或 IP 的请求。
listen 80
:监听 80 端口(HTTP 默认端口)。
server_name localhost
:指定匹配的域名,用于虚拟主机识别,当请求的 Host
头匹配 localhost
时,该服务器块会处理请求。
root /usr/share/nginx/html;
:指定服务器的根目录,用于查找静态文件。
index index.html:设置默认的首页文件
: 用于指定默认的索引文件,当请求的路径是目录时,Nginx 会尝试返回 index.html
或 index.htm
location
块: 根据请求的 URL 路径定义不同的处理规则。
error_page
:定义错误页面的处理规则。当服务器返回 500、502、503 或 504 错误时,返回 /50x.html
location = /50x.html
: 精确匹配 /50x.html
路径,并指定其根目录, 也就是 /50x.html
文件会从 /usr/share/nginx/html
目录中查找
根据这些字段的配置就可以解决上面邮件中提到的问题了。只需要在Nginx上的server 块中配置 server_name为备案域名,再设置一个默认的server快,匹配所有未明确指定 server_name
的请求并返回403就可以了。
server { listen 80; listen [::]:80; server_name localhost; #access_log /var/log/nginx/host.access.log main; location / { root /usr/share/nginx/dist; index index.html index.htm; try_files $uri $uri/ /index.html; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } # 默认 server 块,拒绝非法请求 server { listen 80 default_server; server_name _; return 403; }
上面我们定义了两个server块,只有当来自host为localhost的请求才会匹配第一个server块,第二个是默认的server,匹配任意的host,我们直接返回403状态码。
注意:server_name 是必须的吗?不是必须的,但强烈建议配置。如果 server_name 未指定,Nginx 可能会匹配默认服务器,导致: 潜在的安全问题: 恶意用户可以通过 IP 直接访问 你的 Nginx 服务器,可能会暴露本应受限的服务。 错误的路由: 多个站点可能会被错误地解析到默认的 server 块,导致访问到错误的网站或泄露敏感信息。
静态资源托管 Nginx 采用事件驱动架构,能够高效处理大量并发请求,适合托管静态资源(如 HTML、CSS、JavaScript、图片等),最长见的方式是托管一个单页应用(SPA)。我们将前端打包好的 dist
文件直接拷贝到Nginx的静态资源目录,并进行Nginx的相关配置就可以实现SPA应用的部署。
docker cp ./Desktop/dist my-nginx:/usr/share/nginx/
将本地的dist文件拷贝到容器的 /usr/share/nginx/
目录下,你可以通过 docker exec -it my-nginx /bin/bash
进入容器,查看是否拷贝成功。
在本地修改 default.conf
配置文件,然后将其拷贝到Nginx主配置文件下进行覆盖
server { listen 80; listen [::]:80; server_name localhost; #access_log /var/log/nginx/host.access.log main; # 这个静态资源目录进行了修改 root /usr/share/nginx/dist; index index.html index.htm; location / { try_files $uri $uri/ /index.html; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } # 设置缓存时间 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public"; } } # 默认 server 块,拒绝非法请求 server { listen 80 default_server; server_name _; return 403; }
注意将原来的静态资源目录修改为
root /usr/share/nginx/dist;
这个是刚才我们拷贝的打包资源的目录。
执行覆盖命令
docker cp ./Desktop/default.conf my-nginx:/etc/nginx/conf.d/default.conf
执行完成后,重新启动 Docker 容器: docker restart my-nginx
, 浏览器访问 http://localhost/
就可以看到项目部署成功了。
try_files
指令的设置为:** try_files $uri $uri/ /index.html;
** 当访问指定路径时, $uri
:尝试查找请求的文件,
$uri/
:尝试查找请求的目录,/index.html :如果未找到文件或目录,则返回
index.html。SPA 只有一个 index.html,前端路由由 JavaScript 处理,所有它最终都返回 index.html,由前端来解析路径。这适用于 Vue Router、React Router、Angular Router
的 history 模式,避免 404 的问题。
反向代理与负载均衡 为什么前端要了解Nginx的反向代理?
解决跨域问题:对于前后端分离的项目比如:如前端部署在 http://frontend.com
,后端在 http://api.example.com
),通过 Nginx 将前后端请求统一到同一域名下(如前端 http://example.com
,API http://example.com/api
),浏览器认为这是同源请求,避免跨域限制。 隐藏后端服务:另一方面可以避免直接暴露后端服务的 IP 或端口存在安全风险,通过反向代理方案,后端服务仅对 Nginx 可见,外部请求通过 Nginx 转发,隐藏真实后端地址。
高并发场景下的负载均衡:高并发场景下,单台后端服务器可能无法处理所有请求。通过反向代理,Nginx 将流量分发到多个后端实例,提升系统吞吐量和容错能力。 如何实现反向代理:
还是基于上面的例子,假如我们有个后端的服务地址为: http://example.com/api
, 而前端是本地的localhost,可以通过如下配置实现反向代理:
server { listen 80; server_name localhost; # 前端静态资源 location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; # 支持 SPA 路由 } # 反向代理到后端 API location /api/ { proxy_pass http://example.com; # 后端服务地址 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_pass :将匹配的请求转发到后端服务。这里需要注意location后面的路径与proxy_pass 的匹配规则,当前的设置会匹配以 /api/
开头的请求(例如 /api/user
、 /api/data
),**
proxy_pass
**末尾不带 /
,则会将客户端请求的 URI 完整追加 到目标地址后,
实际的代理路径为:http://example.com/api/user 与http://example.com/api/data, 若 proxy_pass
包含路径且以 /
结尾,Nginx 会替换 location
匹配部分,实际路径为:http://example.com/user,http://example.com/data
proxy_set_header :传递客户端真实 IP 和域名信息到服务端。方便后端记录和分析。后端可以通过 X-Real-IP 读取用户 IP,用于日志记录、IP 限制、用户追踪等操作。
负载均衡
要配置 Nginx 实现负载均衡,你需要在 Nginx 配置文件中定义一个 upstream 块,并在服务器块中使用 proxy_pass 指令将请求转发到定义的上游服务器组。以下是一个示例配置,展示了如何配置 Nginx 实现负载均衡:
upstream backend { server backend1.example.com weight=3; server backend2.example.com; server backend3.example.com; } server { listen 80; server_name example.com; # 反向代理到上游服务器组 location / { proxy_pass http://backend; 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; } }
上游服务器组:使用 upstream 指令定义一个上游服务器组,包含多个后端服务器。 负载均衡策略:默认情况下,Nginx 使用轮询(round-robin)策略进行负载均衡。你可以通过设置权重(weight)来调整服务器的负载分配,权重用于控制请求的分配比例: 反向代理:在服务器块中使用 proxy_pass 指令将请求转发到上游服务器组。 HTTPS 配置(SSL 证书) 通过 Nginx 配置网站的 HTTPS 协议访问是一个常见且重要的任务,首先我们准备好 SSL/TLS 证书 ,一般是从权威证书颁发机构(CA)来获取,证书通常包括以下两个文件:
将证书文件拷贝到Nginx服务的目录下一般放在: /etc/nginx/ssl/
目录下
进行Nginx的配置:
server { listen 443 ssl; server_name example.com; # SSL 证书和私钥路径 ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; # SSL 配置 ssl_protocols TLSv1.2 TLSv1.3; # 推荐使用 TLS 1.2 和 1.3 ssl_ciphers HIGH:!aNULL:!MD5; # 推荐使用安全的加密套件 ssl_prefer_server_ciphers on; root /usr/share/nginx/html; index index.html index.htm; # 处理请求 location / { try_files $uri $uri/ =404; } }
监听443端口,并且通过 certificate
与 ssl_certificate_key
设置对应的证书与key的路径
为了确保所有访问都通过 HTTPS,可以配置 HTTP 请求自动重定向到 HTTPS:
server { listen 80; server_name example.com; # 重定向所有 HTTP 请求到 HTTPS return 301 https://$host$request_uri; }
以上就是前端常见的Nginx 的配置方式,下面我们结合docker来实际部署我们项目。
部署SPA项目 假设你已经打包好了你的项目,并且生成了dist文件,下面是文件目录结构:
my-nginx-spa/ ├─ dist/ │ ├─ index.html │ └─ styles.css ├─ default.conf └─ Dockerfile
default.conf
server { listen 80; listen [::]:80; server_name localhost; #access_log /var/log/nginx/host.access.log main; location / { root /usr/share/nginx/dist; index index.html index.htm; try_files $uri $uri/ /index.html; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } # 默认 server 块,拒绝非法请求 server { listen 80 default_server; server_name _; return 403; }
Dockerfile
# 使用官方 Nginx 镜像作为基础镜像 FROM nginx: 1.25 -alpine # 将本地的 default.conf 复制到容器中 COPY default.conf /etc/nginx/conf.d/ #将本地的 dist 目录复制到容器中的 Nginx 默认静态文件目录 COPY dist /usr/share/nginx/dist # 暴露 80 端口 EXPOSE 80 # 启动 Nginx CMD [ "nginx" , "-g" , "daemon off;" ]
执行docker构建命令打包镜像
docker build -t nginx-spa-image .
基于镜像运行docker容器
docker run --name nginx-spa-container -d -p 80:80 nginx-spa-image
运行后浏览器访问: http://localhost 就可以看到我们部署的项目了。
部署nuxtjs的SSR项目 相比SPA项目,SSR项目的部署要麻烦一点,因为不仅需要一个Nginx服务来托管我们的静态资源,还需要一个Node服务来进行服务端的渲染逻辑。
这里介绍两种部署方案:由于需要两个容器来分别进行node服务与Nginx服务的启动,我们需要使用docker-compose 来进行容器的管理与编排,这就是方案1的思路,当然也可以不使用docker-compose,而是将node与Nginx服务放在一个容器中来简化部署,这就是方案2的部署思路。
方案1: my-nginx-ssr/ ├─ .output/ │ ├─ public/ │ └─ server/ ├─ node/ │ └─ Dockerfile ├─ nginx/ │ └─ nginx.conf ├─ docker-compose.yml └─ package.json
nginx.conf
server { listen 80; server_name localhost; # 静态资源处理(由 Nginx 直接提供) location /_nuxt/ { alias /usr/share/nginx/html/_nuxt/; # 静态资源目录(需与 Docker 挂载路径一致) expires 1d; add_header Cache-Control "public"; try_files $uri $uri/ =404; } # 反向代理到 Node 服务(处理 SSR 请求) location / { proxy_pass http://node-service:3000; # 使用 Docker 服务名通信 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; } } # 默认 server 块,拒绝非法请求 server { listen 80 default_server; server_name _; return 403; }
Dockerfile
FROM node: 18 -alpine # 设置工作目录 WORKDIR /app # 1. 复制依赖清单文件(利用 Docker 层缓存加速构建) COPY package*.json ./ # 2. 安装生产依赖 RUN npm install # 3. 复制构建产物(.output 目录) COPY .output/ ./.output/ # 4. 暴露服务端口(与 Nuxt 配置的端口一致,默认 3000) EXPOSE 3000 # 5. 启动命令(直接运行 Node 服务) CMD [ "node" , ".output/server/index.mjs" ]
docker-compose.yml
version: '3.8' services: # Node.js 服务 node-service: build: context: . # 上下文路径为项目根目录 dockerfile: node/Dockerfile # 指定 Dockerfile 路径 container_name: nuxt-node environment: - NODE_ENV=production networks: - nuxt-network # Nginx 服务 nginx: image: nginx:alpine # 使用官方 Nginx Alpine 镜像 container_name: nuxt-nginx ports: - "80:80" # 映射宿主机 80 端口到容器 80 端口 volumes: # 挂载 Nginx 配置文件(覆盖默认配置) - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf # 挂载静态资源目录(Nginx 直接提供 _nuxt 内容) - ./.output/public/_nuxt:/usr/share/nginx/html/_nuxt depends_on: - node-service # 确保 Node 服务先启动 networks: - nuxt-network # 定义共享网络(容器间通过服务名通信) networks: nuxt-network: driver: bridge
为了减轻node服务的压力,实现静态资源的高效访问,这里利用了Nginx的静态资源托管的能力,将Nuxt的静态资源通过docker-compose的volumes来挂载到Nginx对应的目录上,静态资源处理由 Nginx 直接提供。
需要运行 docker-compose
命令来进行镜像的打包与容器的启动
#构建镜像并启动容器(后台运行) docker-compose up -d --build
方案2:
my-nginx-node-ssr/ ├─ .output/ │ ├─ public/ │ └─ server/ ├─ node/ │ └─ Dockerfile ├─ nginx/ │ └─ nginx.conf └─ package.json
不使用docker-compose,而是将node与Nginx服务放在一个容器中来简化部署的方案:
nginx.conf
server { listen 80; server_name localhost; location /_nuxt/ { alias /usr/share/nginx/html/_nuxt/; expires 1d; add_header Cache-Control "public"; try_files $uri $uri/ =404; } location / { proxy_pass http://localhost:3000; 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; } } server { listen 80 default_server; server_name _; return 403; }
Dockerfile
# 使用 Node.js 官方镜像(Alpine 版本体积更小) FROM node: 18 -alpine # 设置工作目录 WORKDIR /app # 1. 复制依赖清单文件(利用 Docker 层缓存加速构建) COPY package*.json ./ # 2. 安装生产依赖 RUN npm install # 3. 复制构建产物(.output 目录) COPY .output/ ./.output/ # 4. 安装 Nginx RUN apk add --no-cache nginx # 5. 复制 Nginx 配置文件 COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf # 6. 确保 Nginx 主配置文件包含必要的基本结构 RUN echo "user nginx; \ worker_processes auto; \ error_log /var/log/nginx/error.log warn; \ pid /var/run/nginx.pid; \ events { \ worker_connections 1024; \ } \ http { \ include /etc/nginx/mime.types; \ default_type application/octet-stream; \ log_format main '\$remote_addr - \$remote_user [\$time_local] \"\$request\" ' \ '\$status \$body_bytes_sent \"\$http_referer\" ' \
'\"\$http_user_agent\" \"\$http_x_forwarded_for\"'; \ access_log /var/log/nginx/access.log main; \ sendfile on; \ keepalive_timeout 65; \ include /etc/nginx/conf.d/*.conf; \ }" > /etc/nginx/nginx.conf # 7. 检查 Nginx 配置语法 RUN nginx -t # 8. 复制静态资源到 Nginx 目录 COPY .output/public/_nuxt /usr/share/nginx/html/_nuxt # 9. 暴露端口 EXPOSE 80 # 10. 启动命令 CMD sh -c "npm run start & nginx -g 'daemon off;'"
同样的先运行docker镜像构建命令然后运行docker命令
# 构建镜像 docker build -t nginx-node-image -f node/Dockerfile . # 基于镜像启动容器 docker run -d -p 80:80 --name nginx-node-container nginx-node-image
总结 通过掌握上述Nginx核心功能及Docker化部署技巧,前端开发者可独立完成从本地开发到生产上线的全链路工作,构建高效、安全、易维护的Web应用。
- E N D -
如果您关注前端+AI 相关领域可以扫码进群交流
添加小编微信进群😊
关于奇舞团
奇舞团是 360 集团最大的大前端团队,非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。