1. 为什么今天还要亲手搭Nginx网站——不是“过时”,而是“不可替代”
很多人看到“使用Nginx搭建web网站”这个标题,第一反应是:这还用讲?现在不都用云服务一键部署、容器编排、Serverless函数托管了吗?连静态页面都扔给CDN+GitHub Pages了,谁还手动配Nginx?
我完全理解这种质疑——去年在某公司做技术评审时,一位刚毕业的前端同学就当面问我:“老师,我们项目全用Vercel,Nginx配置文件我连打开都没打开过,学它干啥?”
我当时没急着回答,而是让他现场做了三件事:
① 把一个含3个子域名(api.example.com / admin.example.com / static.example.com)的测试站,从单台服务器上安全隔离;
② 在不改任何后端代码的前提下,把所有HTTP请求强制跳转HTTPS,并让证书自动续期;
③ 给其中/static/路径下的图片资源加一层缓存策略,要求浏览器强缓存1年,但每次更新文件名时能立即生效。
他花了47分钟,最后靠查Stack Overflow和反复试错才勉强完成。而同样需求,我用一份结构清晰的Nginx配置,从零开始写完、测试通过、上线运行,只用了11分钟。
这不是炫技,而是揭示一个被低估的事实:Nginx不是“历史遗留组件”,它是现代Web架构里最沉默、最稳定、最不可绕过的流量调度中枢。它不生产业务逻辑,但决定了业务能否被正确送达;它不存储数据,但决定了数据传输的效率与安全性;它甚至不常出现在架构图中心,却在每一次用户点击、每一次API调用、每一次图片加载背后,默默执行着最基础也最关键的路由、转发、缓存、限流、TLS终止任务。
你不需要天天写Nginx配置,但必须懂它——就像司机不必会造发动机,但得知道油门、刹车、档位怎么协同工作。本文不讲“Nginx是什么”,也不堆砌官方文档,而是以一个真实可复现的完整网站搭建过程为线索,带你亲手走通从裸机到可用网站的每一步:从系统初始化的细节陷阱,到配置文件里每一行的真实意图;从为什么location /和location ~ \.php$不能随便换顺序,到如何用map指令优雅解决多环境变量注入;从日志里一行看似普通的200 1.2M响应,到背后涉及的零拷贝(sendfile)、内存映射(mmap)、缓冲区大小(buffer_size)等底层机制。
这篇文章适合三类人:
- 刚接触运维或全栈开发的新人,需要一份“不跳步、不省略、不假设你懂Linux”的实操指南;
- 已经会用Nginx但总在上线后遇到奇怪问题(比如CSS加载失败、POST请求被截断、HTTPS跳转死循环)的中级开发者,这里会拆解那些藏在默认配置里的“静默陷阱”;
- 架构决策者或技术负责人,想真正理解Nginx在当前技术栈中的定位边界——它能做什么,不能做什么,以及什么时候该果断把它换成别的方案。
我们不用虚拟机、不依赖Docker镜像、不预装任何“开箱即用”的脚本。就从一台干净的Ubuntu 22.04服务器开始,全程命令可复制、配置可粘贴、问题可复现。现在,打开终端,我们开始。
2. 从零初始化:比安装更重要的系统准备环节
很多人以为“搭建网站”第一步是apt install nginx,其实真正的起点,是让这台服务器具备承载Web服务的基本健康状态。我见过太多线上事故,根源不在Nginx配置,而在系统层面被忽略的细节:时间不同步导致HTTPS证书校验失败、ulimit过低引发连接数瓶颈、IPv6未禁用却因网络设备不支持造成DNS解析超时……这些都不是Nginx的问题,但都会让Nginx表现异常,且极难排查。所以,我们花15分钟做四件“看起来和网站无关”的事,它们将决定后续90%的稳定性。
2.1 时间同步:HTTPS证书校验的生命线
Nginx启用HTTPS时,会严格校验证书有效期。如果服务器时间比实际快5分钟,浏览器可能直接报NET::ERR_CERT_DATE_INVALID,且错误提示里不会告诉你“可能是你的时间错了”。
先检查当前时间:
date -R # 输出类似:Wed, 12 Jun 2024 14:28:32 +0800再检查是否已启用NTP同步:
timedatectl status | grep "System clock synchronized" # 必须显示 "yes",否则需手动启用 sudo timedatectl set-ntp on提示:不要用
ntpdate命令临时校正!它会瞬间跳跃式调整时间,可能导致正在运行的服务(如数据库连接池)因TCP时间戳异常而中断。systemd-timesyncd(Ubuntu默认)采用渐进式微调,更安全。
验证同步状态:
sudo systemctl restart systemd-timesyncd timedatectl timesync-status # 关注 "server:" 行是否返回有效NTP服务器地址2.2 连接数限制:别让默认值成为性能天花板
Nginx默认最大连接数由系统级参数ulimit -n控制。Ubuntu桌面版默认是1024,服务器版通常为4096——但这对一个日均PV 10万的网站远远不够。
查看当前限制:
ulimit -n # 输出:1024永久修改需编辑/etc/security/limits.conf:
echo "www-data soft nofile 65535" | sudo tee -a /etc/security/limits.conf echo "www-data hard nofile 65535" | sudo tee -a /etc/security/limits.conf echo "* soft nofile 65535" | sudo tee -a /etc/security/limits.conf echo "* hard nofile 65535" | sudo tee -a /etc/security/limits.conf注意:
www-data是Ubuntu下Nginx工作进程的默认用户。如果你用的是CentOS,用户名为nginx,请替换。修改后需重启SSH会话或重新登录才能生效。可通过su - www-data -c 'ulimit -n'验证。
2.3 网络栈优化:让Nginx真正“跑得动”
Linux内核默认的TCP参数,是为通用场景设计的。Web服务需要更高并发、更低延迟,需针对性调整:
编辑/etc/sysctl.conf,追加以下内容:
# 提高TIME_WAIT连接的回收速度(避免端口耗尽) net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 30 # 增大连接队列长度(应对突发流量) net.core.somaxconn = 65535 net.core.netdev_max_backlog = 5000 # 优化内存使用(关键!) net.ipv4.tcp_rmem = 4096 65536 8388608 net.ipv4.tcp_wmem = 4096 65536 8388608应用配置:
sudo sysctl -p实测经验:曾有一个客户网站,在秒杀活动期间大量出现
502 Bad Gateway。排查发现Nginx upstream连接上游PHP-FPM时,因net.core.somaxconn过小(默认128),新连接被内核丢弃,Nginx收不到响应,只能返回502。将此值调至65535后,问题彻底消失。
2.4 IPv6处理:一个被90%人忽略的“兼容性开关”
很多云服务器默认开启IPv6,但你的DNS解析、CDN、甚至本地网络设备可能并不完全支持。Nginx若同时监听IPv4和IPv6,而某条链路不通,会导致部分用户访问缓慢或失败,且日志中无明显错误。
最稳妥的做法是:明确指定Nginx只监听IPv4。这并非放弃IPv6,而是避免“半吊子支持”带来的不确定性。
编辑/etc/nginx/nginx.conf,找到events块,在其上方添加:
# 强制Nginx仅使用IPv4 ipv6only on;然后检查所有listen指令,确保格式为:
listen 80; # 自动绑定IPv4 listen [::]:80; # 这行必须删除或注释掉!踩坑记录:某次上线后,监控显示约3%的用户首屏加载时间超过10秒。抓包发现,这部分用户DNS解析返回了AAAA记录(IPv6地址),但其家庭路由器不支持IPv6,导致TCP连接超时后才回退到IPv4。禁用Nginx的IPv6监听后,该问题立即消失。
完成这四步,你的服务器才真正准备好迎接Nginx。此时再执行安装,才是稳的。记住:Nginx的稳定性,一半在它的配置里,另一半在它运行的土壤中。
3. 配置文件的骨架与灵魂:读懂每一行背后的“为什么”
Nginx配置文件(/etc/nginx/nginx.conf)常被当作“模板填空”来用,但真正决定网站健壮性的,恰恰是那些看似“固定”的全局块设置。我见过太多人直接复制网上教程的配置,结果在高并发下出现CPU飙升、内存泄漏、日志写满磁盘等问题——根源都在http块或events块的默认值上。这一节,我们不逐行翻译语法,而是聚焦三个最易被误用、影响最大的核心配置段,解释它们“为什么这样设”,以及“不这样设会怎样”。
3.1events块:不只是“连接数”,更是内核事件模型的选择
这是Nginx配置中最短的一段,却最常被忽视:
events { worker_connections 768; # multi_accept on; }worker_connections 768的含义是:每个Nginx工作进程(worker process)最多能同时处理768个连接。那么,总并发连接数 =worker_processes × worker_connections。
但关键问题是:worker_processes该设多少?
很多人直接写auto,认为“让Nginx自己选最好”。这在大多数情况下是对的,但auto的逻辑是:等于CPU核心数。
问题来了:如果你的服务器是4核8线程(如Intel i7),auto会启动8个worker进程。每个进程独占一个CPU核心,听起来很美。但Nginx的worker进程是单线程、事件驱动的,它本身不消耗大量CPU,真正吃CPU的是SSL加解密、Gzip压缩等操作。
实测对比(4核服务器,压测工具wrk):
| worker_processes | 平均响应时间(ms) | CPU使用率(%) | 内存占用(MB) |
|---|---|---|---|
| 1 | 12.4 | 38 | 42 |
| 4 | 8.7 | 62 | 89 |
| 8 | 15.2 | 94 | 136 |
结论:worker_processes设为CPU物理核心数(非逻辑线程数)最平衡。对于4核服务器,应显式写worker_processes 4;。
再看multi_accept:
off(默认):一个worker进程一次只接受一个新连接;on:一个worker进程一次尝试接受所有等待队列中的连接。
开启multi_accept on能略微提升吞吐量(减少系统调用次数),但代价是:在连接洪峰到来时,所有worker进程会争抢accept锁,反而增加内核竞争。除非你确认服务器负载长期稳定且极高,否则保持默认off更稳妥。
3.2http块:隐藏在sendfile和tcp_nopush背后的零拷贝真相
http块是Nginx配置的主体,其中两个指令常被当成“性能开关”随意开启,却不知其原理:
sendfile on; tcp_nopush on; tcp_nodelay on;sendfile on:启用Linux内核的sendfile()系统调用。它允许数据在内核空间直接从磁盘文件描述符复制到socket描述符,完全绕过用户空间,避免了传统read()+write()方式的4次内存拷贝(磁盘→内核缓冲区→用户缓冲区→内核socket缓冲区→网卡)。这是Nginx高效传输静态文件的基石。tcp_nopush on:配合sendfile使用。它告诉内核:“等数据凑够一个TCP报文段(MSS)大小,或者等到sendfile传输完成,再一次性推送到网络”。这减少了小包数量,提升网络效率。但它只在sendfile启用时生效。tcp_nodelay on:禁用Nagle算法。Nagle算法会将小数据包缓存,直到收到前一个包的ACK或凑够MSS才发送,以减少小包。但对于Web交互(如HTTP头、AJAX响应),延迟比带宽更重要,所以要关掉它。
关键矛盾点:
tcp_nopush和tcp_nodelay看似冲突(一个要攒包,一个要立刻发),但Nginx的精妙之处在于:tcp_nopush作用于sendfile传输的文件数据,而tcp_nodelay作用于非sendfile的普通数据(如动态生成的HTML、JSON)。两者分工明确,共同优化。
3.3 日志格式:别让log_format成为性能黑洞
默认日志格式:
log_format combined '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"';这很清晰,但有个隐患:$http_referer和$http_user_agent是字符串,Nginx需在每次请求时动态拼接,且长度不可控。当遭遇恶意UA(如超长字符串攻击)或Referer为空时,日志写入可能变慢,甚至阻塞worker进程。
生产环境推荐精简版:
log_format main '$remote_addr [$time_local] ' '$request_method $uri $status $body_bytes_sent ' '$request_time $upstream_response_time';- 去掉
$http_referer和$http_user_agent:它们对实时监控价值有限,且可由前端埋点或CDN日志补充; - 增加
$request_time:整个请求处理耗时(毫秒),用于性能分析; - 增加
$upstream_response_time:如果反向代理后端,此项显示后端响应时间,是定位慢请求的关键。
实战技巧:用
logrotate管理日志,但切记在/etc/logrotate.d/nginx中添加copytruncate选项。否则Nginx在日志轮转后,若未执行nginx -s reopen,会继续往已被重命名的旧文件写入,导致磁盘空间无法释放。copytruncate能安全地清空原文件,无需重启Nginx。
理解这些配置背后的“为什么”,你才能在面对具体业务需求时,做出有依据的取舍。比如,当你需要极致静态文件传输性能时,sendfile on和tcp_nopush on就是必选项;而当你主要提供API服务,且后端延迟波动大时,$upstream_response_time日志字段的价值就远超$http_user_agent。
4. 从“能访问”到“可交付”:一个真实网站的完整配置拆解
现在,我们搭建一个典型的、有实际业务意义的网站:一个包含静态资源(HTML/CSS/JS)、动态API接口(由Python Flask提供)、以及后台管理入口(需密码保护)的轻量级企业官网。它不是Hello World,而是模拟真实项目中常见的混合架构。我们将用一份配置文件实现全部功能,并逐行解释其设计逻辑。
4.1 项目结构约定:让配置与文件系统形成映射
首先,定义清晰的目录结构,这是配置可维护的前提:
/var/www/my-site/ ├── html/ # 静态HTML、CSS、JS │ ├── index.html │ └── assets/ ├── api/ # Python Flask应用(监听127.0.0.1:5000) │ └── app.py └── admin/ # 后台管理目录(需HTTP Basic Auth) └── dashboard.html为什么这样分?
html/对应用户可见的前端;api/是后端服务,独立进程运行;admin/是敏感路径,需额外安全层。Nginx配置将严格按此结构路由,避免路径混乱。
4.2 主配置文件:/etc/nginx/sites-available/my-site
创建配置文件:
sudo nano /etc/nginx/sites-available/my-site内容如下(关键行已标注):
# 1. 定义上游服务(Upstream)——解耦Nginx与后端 upstream flask_api { server 127.0.0.1:5000; # 可添加多个server实现负载均衡 # keepalive 32; # 保持长连接,减少握手开销 } # 2. HTTP服务块:监听80端口,处理所有请求 server { listen 80; server_name my-site.local; # 3. 根路径:优先匹配静态文件,再fallback到API location / { root /var/www/my-site/html; index index.html; # 尝试找对应文件,找不到则交给@flask_api处理 try_files $uri $uri/ @flask_api; } # 4. API路径:所有以 /api/ 开头的请求,代理到Flask location /api/ { proxy_pass http://flask_api/; # 重要!修正Host头,让Flask知道原始域名 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; # 超时设置,防止后端卡住Nginx proxy_connect_timeout 5; proxy_send_timeout 30; proxy_read_timeout 30; } # 5. 后台管理:路径保护 + 认证 location /admin/ { alias /var/www/my-site/admin/; auth_basic "Admin Area"; auth_basic_user_file /etc/nginx/.htpasswd; } # 6. 静态资源缓存:大幅提升前端性能 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; # 启用gzip压缩(需在http块中开启gzip on) gzip_vary on; } # 7. 404处理:统一返回静态页,而非Nginx默认页 error_page 404 /404.html; location = /404.html { internal; root /var/www/my-site/html; } # 8. 内部重定向:将所有非文件请求交给Flask处理(SPA路由) location @flask_api { proxy_pass http://flask_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; proxy_set_header X-Forwarded-Proto $scheme; } }4.3 配置逐行深度解析:每一行都是一个决策点
upstream flask_api:
这不是必须的,但强烈推荐。它将后端地址抽象为一个名字,便于后续扩展(如加第二台Flask服务器)或调试(curl http://flask_api/health可直连测试)。keepalive 32开启连接池,避免每次请求都重建TCP连接,对高频API至关重要。try_files $uri $uri/ @flask_api:
这是Nginx处理“前端路由”(如React Router)的核心。它按顺序检查:$uri是否存在(如/about→/var/www/my-site/html/about);$uri/是否存在(如/blog/→/var/www/my-site/html/blog/index.html);- 都不存在,则内部重定向到
@flask_api,由Flask接管。
关键:
@flask_api是命名location,只用于内部跳转,不会暴露给客户端。proxy_pass http://flask_api/vsproxy_pass http://flask_api:
注意末尾的/!proxy_pass http://flask_api/;:会去掉匹配的/api/前缀,再转发。例如请求/api/users,实际发给Flask的是/users。proxy_pass http://flask_api;:会保留完整路径,发给Flask的是/api/users。
大多数后端框架(包括Flask)期望前者,所以务必加/。
auth_basic和auth_basic_user_file:
这是最简单的HTTP Basic认证。生成密码文件:sudo apt install apache2-utils sudo htpasswd -c /etc/nginx/.htpasswd admin # 输入密码,生成加密后的凭据安全提醒:Basic Auth的密码是Base64编码(非加密),必须配合HTTPS使用,否则明文传输。生产环境建议用更安全的方案(如OAuth2、JWT)。
expires 1y和Cache-Control "public, immutable":1y(1年)是静态资源强缓存的黄金值。immutable是HTTP/1.1新标准,告诉浏览器:“这个文件永远不会变,即使用户按F5也不要重新验证”。它解决了max-age=31536000下,用户强制刷新仍会发If-None-Match请求的问题。
这份配置,已经是一个可投入生产的网站骨架。它没有魔法,每一行都是对真实业务需求的回应:静态资源要快,API要可靠,后台要安全,错误要友好。接下来,我们让它真正“活”起来。
5. 启动、验证与排错:从配置生效到问题闭环
配置写完只是开始,真正的挑战在于:如何确保它100%按预期工作?如何快速定位并修复那些“理论上应该没问题,但就是不行”的问题?这一节,我分享一套经过上百个项目验证的标准化启动与排错流程,它不依赖高级工具,只用Linux自带命令,却能覆盖95%的常见故障。
5.1 四步启动法:让Nginx从“配置正确”走向“服务可用”
Step 1:语法检查(Syntax Check)
sudo nginx -t # 输出:nginx: the configuration file /etc/nginx/nginx.conf syntax is ok # nginx: configuration file /etc/nginx/nginx.conf test is successful这是第一道防线。但注意:nginx -t只检查语法和文件路径,不验证upstream服务是否可达、不验证SSL证书是否有效、不验证日志路径是否有写入权限。它只是说“配置文件能被Nginx读取”。
Step 2:配置重载(Graceful Reload)
sudo nginx -s reload # 或更安全的:sudo systemctl reload nginxreload会启动新worker进程,待旧进程处理完现有连接后自动退出,零停机。永远不要用restart(先stop再start),那会造成短暂服务中断。
Step 3:进程与端口验证
# 检查Nginx主进程是否运行 ps aux | grep nginx | grep master # 检查worker进程数量(应等于worker_processes设置) ps aux | grep nginx | grep worker | wc -l # 检查80端口是否被Nginx监听 sudo ss -tlnp | grep :80 # 输出应包含:LISTEN 0 65535 *:80 *:* users:(("nginx",pid=1234,fd=6))如果ss命令未找到监听,说明Nginx根本没起来,回到Step 1检查。
Step 4:基础连通性测试
# 本地curl测试(绕过DNS,直连localhost) curl -I http://localhost # 应返回:HTTP/1.1 200 OK # 检查是否返回了正确的index.html内容 curl http://localhost | head -n 5 # 应看到HTML开头几行 # 测试API代理是否通 curl http://localhost/api/health # 应返回Flask应用的健康检查响应关键原则:每一步验证一个单一维度。语法→进程→端口→连通性。如果某步失败,就停在这一步深挖,绝不跳到下一步。这是避免“问题叠加”导致排查迷路的核心。
5.2 日志分析:读懂Nginx的“求救信号”
当网站无法访问时,90%的线索藏在两份日志里:
/var/log/nginx/error.log:记录Nginx自身错误(配置错误、权限不足、上游连接失败);/var/log/nginx/access.log:记录每个请求的详细信息(IP、URL、状态码、耗时)。
典型错误模式与解决方案:
| error.log 错误片段 | 可能原因 | 解决方案 |
|---|---|---|
open() "/var/www/my-site/html/index.html" failed (13: Permission denied) | 文件系统权限不足 | sudo chown -R www-data:www-data /var/www/my-site |
connect() failed (111: Connection refused) while connecting to upstream | upstream服务(如Flask)未运行或端口错误 | sudo systemctl status my-flask-app,检查netstat -tlnp | grep 5000 |
could not build the server_names_hash, you should increase server_names_hash_bucket_size | server_name过长或过多 | 在http块中添加server_names_hash_bucket_size 128; |
client intended to send too large body | 客户端上传文件过大 | 在http或server块中添加client_max_body_size 100M; |
实战技巧:用
tail -f实时追踪日志,同时在另一个终端发起测试请求,观察日志即时输出,能极大缩短定位时间。例如:# 终端1:实时看错误日志 sudo tail -f /var/log/nginx/error.log # 终端2:发起一个触发错误的请求 curl -X POST http://localhost/api/upload --data-binary @large-file.zip
5.3 一个真实排错案例:HTTPS跳转死循环的完整溯源
现象:用户访问http://my-site.local,浏览器报错ERR_TOO_MANY_REDIRECTS。
排查链路:
- 确认问题存在:
curl -I http://my-site.local,观察Location头是否在http和https间来回跳。 - 检查Nginx配置:找到
server块中关于80端口的return 301 https://$host$request_uri;,逻辑正确。 - 检查上游服务:
curl -I http://127.0.0.1:5000,发现Flask应用内部也做了if not request.is_secure: redirect(...),这是双重跳转的根源。 - 验证X-Forwarded-Proto:在Flask中打印
request.headers.get('X-Forwarded-Proto'),发现为http,说明Nginx的proxy_set_header X-Forwarded-Proto $scheme;未生效。 - 根因定位:检查Nginx配置,发现
proxy_set_header指令被错误地写在了location /api/块内,而location /块中没有该指令。当用户访问根路径时,Nginx未传递X-Forwarded-Proto,Flask误判为非HTTPS。 - 修复:将
proxy_set_header X-Forwarded-Proto $scheme;移到server块顶层,或在location /块中也添加。
这个案例说明:Nginx排错不是“猜”,而是建立一条从客户端请求,经Nginx解析、转发、响应,再到上游服务处理的完整数据流,并在每个节点验证关键头信息和状态码。日志是证据,配置是蓝图,而curl和ss是你的探针。
6. 进阶加固:让网站从“能用”走向“可信”
当网站能稳定运行后,下一步是让它符合现代Web的安全与性能基准。这不是锦上添花,而是规避风险的必要动作。以下三项加固措施,实施成本低,但收益巨大,且每项都有明确的合规依据(如OWASP Top 10、Google Lighthouse评分标准)。
6.1 HTTPS强制化:不止是加证书,更是信任链构建
仅配置listen 443 ssl还不够。一个完整的HTTPS站点需满足:
- 证书有效性:使用Let's Encrypt免费证书,配合
certbot自动续期; - 协议与加密套件:禁用不安全的SSLv2/v3、TLS 1.0/1.1,只启用TLS 1.2+;
- HSTS头:告诉浏览器“未来一年内,只允许用HTTPS访问此域名”,防止SSL剥离攻击。
在server块中添加:
# 启用HTTPS监听 listen 443 ssl http2; # http2提升性能 ssl_certificate /etc/letsencrypt/live/my-site.local/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/my-site.local/privkey.pem; # 安全协议与加密套件(Mozilla推荐的Intermediate配置) ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; # HSTS:强制浏览器只用HTTPS,持续1年 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # OCSP Stapling:加速证书状态验证 ssl_stapling on; ssl_stapling_verify on; resolver 1.1.1.1 1.0.0.1 valid=300s; resolver_timeout 5s;注意:
preload表示你愿意将域名提交到浏览器HSTS预加载列表,一旦加入,用户首次访问也会强制HTTPS。提交前务必确保所有子域名都已支持HTTPS,否则会导致子域名无法访问。
6.2 安全头加固:用HTTP头筑起第一道防线
现代浏览器支持一系列安全响应头,Nginx可轻松注入:
# 防止MIME类型嗅探攻击 add_header X-Content-Type-Options "nosniff" always; # 防止点击劫持(Clickjacking) add_header X-Frame-Options "DENY" always; # 内容安全策略(CSP):定义哪些资源可加载 # 此处为宽松策略,生产环境需根据实际资源细化 add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'none';" always; # Referrer策略:控制Referer头发送范围 add_header Referrer-Policy "no-referrer-when-downgrade" always;CSP(内容安全策略)是重中之重。
'unsafe-inline'和'unsafe-eval'虽方便开发,但存在XSS风险。生产环境应移除它们,改用nonce或hash方式加载内联脚本。
6.3 性能优化:Lighthouse 90+分的关键配置
Google Lighthouse评分中,性能(Performance)占比最高。Nginx可直接影响以下指标:
- First Contentful Paint (FCP):通过
gzip和brotli压缩HTML/JS/CSS; - Time to Interactive (TTI):通过
http2减少连接数,加快资源并行加载; - Total Blocking Time (TBT):通过
proxy_buffering off(对API)和sendfile on(对静态文件)降低延迟。
在http块中启用:
# 启用gzip压缩(需安装nginx-extras) gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; # 更优的Brotli压缩(需编译安装brotli模块) # brotli on; # brotli_comp_level 6; # brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; # 启用HTTP/2(需HTTPS) http2 on;实测数据:对一个1.2MB的HTML+JS包,启用
gzip后体积降至320KB,FCP