1. 先想清楚:用NGINX搭建web网站到底在解决什么问题
我接触过的站点里,十个有八个第一版都是用NGINX搭起来的。原因不复杂:它够轻、够稳、配置一眼能看懂,一台1核2G的小机器就能扛住一个中小型网站的日常流量。但很多人第一次上手时会卡在几个非常具体的地方——装完之后浏览器打不开、配置文件改完不生效、日志里一串报错不知道从哪看起。所以这篇我想换个写法,不从"什么是NGINX"讲起,而是按我实际搭站的顺序,把用NGINX搭建web网站这件事拆成选型、落地、加固、排错四段,把我自己踩过的坑和那些文档里不会写的细节摊开说。
先把适用范围讲清楚。这篇内容适合三类人:一是有台服务器、想把静态站或者前后端分离项目挂上去的人;二是手上有个小项目需要反向代理、但又不想引入更重组件的人;三是已经能用NGINX但配置全靠抄、出了问题只能重启试试的人。如果你的场景是高并发CDN边缘节点或者复杂的微服务网关,那这篇只能当基础铺垫,后面还得补更多东西。全文涉及的配置都在标准的Linux发行版上验证过,命令和路径以主流的包管理方式为准,你换成别的发行版无非是包名和路径差一点,思路完全一样。
我最想强调的一点是:NGINX的配置看起来是"写一段文本",实际上是"描述一棵请求处理树"。很多人配不明白,是因为把配置当成了开关列表,改一个指令期待一个结果,但NGINX的行为是多个指令叠加、按优先级匹配出来的。理解这一点,后面所有的坑都会变得可解释。
2. 方案选型与目录规划:动手前必须先定下来的几件事
2.1 静态托管这件事,NGINX的优势到底在哪
同样是挂一个静态站,可选项其实不少:语言自带的开发服务器、各种应用框架内置的服务、对象存储的静态托管。我选NGINX的理由集中在三个字:可预期。
用框架自带的服务器跑生产,最典型的问题是它的设计目标就不是长时间稳定运行——连接管理粗糙、静态文件没有缓存头、并发一上来就开始排队。对象存储托管确实省心,但涉及自定义响应头、URL重写、多域名共用一台机器时就会很别扭,而且调试链路不透明,出问题你只能看到结果看不到过程。NGINX的中间层是显式的:请求进来走哪个server块、命中哪个location、落到哪个文件、带什么响应头,全部写在配置里,出了问题用日志一对就能定位。
另一个常被忽略的优势是内存模型。NGINX用一个master进程加若干worker进程的结构,worker数量通常设成CPU核数,每个worker内部用事件驱动处理连接,一个连接在等待网络IO时不占线程。这意味着它处理一万个空闲长连接的内存开销,远小于"一个连接一个线程"的模型。对个人站点来说这可能感知不明显,但当你有大量移动端用户、连接经常半开半闭时,这个差别会直接体现在内存曲线上。
注意:不要把"事件驱动"理解成"什么都能扛"。如果请求落到后端应用上,瓶颈立刻转移到后端,NGINX再快也没用。它的强项是静态资源、连接管理、TLS卸载和请求分发。
2.2 版本与安装方式:包管理还是源码编译
这个问题我被问过很多次,我的答案是:除非你有明确理由,否则一律用包管理器安装。
包管理器装出来的版本,优势是全套系统集成——systemd单元文件、日志轮转、默认目录结构、安全更新通道,全都是现成的。你不需要自己去写unit文件、不需要操心日志被撑爆、出安全通告时一条升级命令就完事。源码编译的好处是能开特定模块、能改编译参数、能锁死版本,但代价是后续每次升级你都要重新走一遍编译流程,还得自己维护启动脚本。我见过太多人编译完之后半年不敢动,因为忘了当时加了什么参数。
判断标准很简单:如果你需要在配置里用到某个第三方模块(比如某些特殊的压缩或鉴权模块),那走编译;如果只是反代、静态托管、HTTPS、压缩、限流这些常规需求,包管理版本全都覆盖。
# 主流发行版上的安装,Debian/Ubuntu系 sudo apt update sudo apt install -y nginx # 确认版本与编译参数,重点看 --with- 开头的模块列表 nginx -V # 服务管理 sudo systemctl enable --now nginx sudo systemctl status nginx装完先别急着改配置,直接用浏览器访问一下默认页,确认基础链路通。这一步看着多余,实际上是给自己划一条基线:默认页能开,说明端口、防火墙、服务状态都没问题;后面改坏了,你至少知道退回哪里。
2.3 目录结构怎么划分,决定了你半年后还能不能看懂
NGINX的配置目录在Debian/Ubuntu系通常是/etc/nginx/,主配置nginx.conf,里面用include把conf.d/和sites-enabled/引进来。这套设计的用意是把"全局设置"和"单个站点设置"分开。
我的习惯是强制分层,任何人都不要在nginx.conf里写server块:
| 位置 | 放什么 | 谁维护 |
|---|---|---|
/etc/nginx/nginx.conf | 全局参数:worker数、日志格式、MIME、连接数上限 | 只在初期改一次 |
/etc/nginx/conf.d/ | 通用片段:SSL参数、代理公共头、限流区定义 | 按主题拆文件 |
/etc/nginx/sites-available/ | 每个站点的完整server块,一个域名一个文件 | 按站点拆文件 |
/etc/nginx/sites-enabled/ | 指向available的软链接,控制启用状态 | 只放链接 |
站点内容本身放在/var/www/下面,按域名建目录。这个结构最大的好处是"下线一个站点"变成删一个软链接的事,配置文件还在,将来要恢复随时链回来。用sites-available和sites-enabled分离的做法,本质上是一次人工的灰度控制。
sudo mkdir -p /var/www/example-site/html sudo chown -R www-data:www-data /var/www/example-site sudo chmod -R 755 /var/www/example-site # 站点配置建在available,启用靠软链接 sudo ln -s /etc/nginx/sites-available/example-site.conf /etc/nginx/sites-enabled/提示:软链接一定要用绝对路径。相对路径的软链接在NGINX解析include时经常解析失败,报错信息还很不直观,只说找不到文件。
2.4 运行用户与文件权限:403错误的根源多半在这
NGINX的worker进程默认以www-data(Debian系)或nginx(RHEL系)身份运行。这个用户在系统里权限很低,属于刻意为之——万一站点被攻破,攻击者拿到的也只是这个低权限身份。
问题就出在这:很多人用root把文件拷到/var/www/下,目录权限设成700,然后浏览器访问返回403,还以为是NGINX配置错了。实际上worker进程连目录都进不去。判断方法很直接,看一眼错误日志就能确认,日志里会明确写Permission denied。
正确的做法是保证整条路径对www-data可读可执行:目录需要x权限才能进入,文件需要r权限才能读取。常见的755/644组合基本够用。如果站点需要写权限(比如上传目录),只给那一个目录开写,不要图省事把整站设成777。
# 检查整条路径的权限,注意每一级目录都要有 x namei -l /var/www/example-site/html/index.html这条命令我强烈建议记住,它会把路径上每一级目录的权限、属主、属组全列出来,一眼就能看出是哪一级卡住了。比逐个ls -l快得多。
3. 最小可用站点:从配置到访问的完整链路
3.1 一个能跑起来的server块,逐行拆解
下面这段是我给新站点用的起手模板,去掉了所有花哨的东西,只保留必要项:
server { listen 80; listen [::]:80; server_name example-site.test www.example-site.test; root /var/www/example-site/html; index index.html index.htm; access_log /var/log/nginx/example-site.access.log; error_log /var/log/nginx/example-site.error.log warn; location / { try_files $uri $uri/ =404; } }逐条说。listen 80是IPv4监听,listen [::]:80是IPv6,两个都写是为了避免某些网络环境下IPv6用户访问不到。server_name决定这个server块响应哪个域名——注意它只是"匹配依据",真正把域名指到这台机器上靠的是DNS解析,这两件事不要混。
root定义文档根目录,index定义目录请求时默认找哪个文件。access_log和error_log我坚持每个站点单独一份,理由很实际:多站点共用一份日志时,排查问题要在别人的噪声里翻自己的记录,效率极低。日志级别用warn,info级别的日志量大到会快速吃掉磁盘,除非专门调试否则不要开。
try_files $uri $uri/ =404这行是重点。它的逻辑是:先按请求路径找文件,找不到就把它当目录再找一次,还找不到就返回404。如果不写这行,NGINX的默认行为在某些配置下会返回403而不是404,对SEO和排查都不友好。
3.2 location匹配的优先级,是配置里最容易搞错的地方
location的匹配规则,我总结成一句话:精确匹配最优先,其次是最长前缀匹配,正则匹配按出现顺序,^~前缀匹配会跳过正则。
location = /favicon.ico { } # 精确匹配,只命中这一个路径 location ^~ /static/ { } # 前缀匹配,命中后不再尝试正则 location ~* \.(jpg|png|css|js)$ { } # 正则匹配,不区分大小写 location / { } # 兜底前缀匹配为什么这个顺序重要?举个实际场景:你想让/static/下的所有请求走一套长缓存策略,同时又在全局配了一条"所有图片走另一个目录"的正则。如果/static/用的是普通前缀location /static/,那正则的优先级更高,图片请求会被抢走,缓存策略就不生效了。改成^~才能保证前缀优先。
我的经验是:能用精确匹配就用精确匹配,能用前缀就别用正则。正则虽然灵活,但匹配是逐个尝试的,location数量一多就有性能开销,而且调试起来只能靠日志反推。写到十个以上正则location的时候,我会回头审视一下是不是结构设计有问题。
注意:location里嵌套location时,嵌套层的前缀匹配规则和外层不完全一样,容易出意外。我一般避免嵌套超过一层,需要复杂路由时宁可拆成多个server块。
3.3 修改配置到生效:一套不会把自己锁在门外的流程
新手最容易犯的错是改完配置直接restart,结果配置有语法错误,服务起不来,站点直接挂掉。正确流程是三步,顺序不能变:
# 1. 语法检查,这一步不碰运行中的进程 sudo nginx -t # 2. 检查通过后平滑重载,旧worker处理完存量连接再退出 sudo systemctl reload nginx # 3. 确认进程和端口状态 sudo systemctl status nginx ss -lntp | grep nginxnginx -t会同时校验主配置和所有include进来的文件,报错会精确到文件和行号。这一步的成本只有几百毫秒,收益是避免一次线上事故,没有理由跳过。
reload和restart的区别值得说清楚。reload是给master进程发信号,master重新读取配置、启动新worker、通知旧worker优雅退出。整个过程监听socket不关闭,用户连接不会丢。restart是停服务再启动,中间的窗口期所有请求都会失败。所以除非你在改worker_processes、user这类只有master启动时才读取的指令,否则一律用reload。
我自己还加了一层保险:改配置前先cp一份带时间戳的备份,重载失败直接覆盖回去再reload。这个动作两秒钟,比在故障现场冷静分析配置要划算得多。
sudo cp /etc/nginx/sites-available/example-site.conf \ /etc/nginx/sites-available/example-site.conf.$(date +%Y%m%d%H%M)3.4 验证环节:别只盯着首页
配置生效后,检查要分层做,不要打开首页看到200就收工。
第一层是连通性,用curl -I看响应头和状态码。第二层是路径覆盖,把站点的几个典型URL都打一遍:带子目录的、带查询参数的、不存在的页面、静态资源。第三层是看日志,确认请求确实落在了你预期的server块和location里——有时候配置写错但结果"看起来对",是因为落到了默认server块,这种隐患不查日志根本发现不了。
curl -I http://example-site.test/ curl -I http://example-site.test/some-dir/ curl -I http://example-site.test/not-exist.html # 期望404 curl -I http://example-site.test/assets/app.css # 看缓存头是否生效如果站点还没配DNS,可以在本地hosts文件里临时指一条记录做测试,或者用curl --resolve指定解析结果,后者更干净,不污染系统配置:
curl -I --resolve example-site.test:80:127.0.0.1 http://example-site.test/这个技巧我在迁移和灰度时用得最多,能精确控制"这个域名解析到哪台机器",避免改hosts改乱了忘了还原。
4. 让站点真正可用:HTTPS、反向代理与性能参数
4.1 上HTTPS:证书只是第一步,参数才是关键
证书申请流程现在很成熟,走自动化的方式几分钟就能拿到。真正的门槛在配置参数的取舍上,因为不同年代的客户端支持的协议和加密套件不一样,配得太严会挡住老设备,配得太松等于没加固。
我的做法是只启用TLS 1.2和1.3,加密套件交给服务端优先顺序决定,同时开启会话复用减少握手开销。下面这段是我常用的SSL公共片段,放在conf.d/里被各站点include:
ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on;ssl_session_cache shared:SSL:10m里的10m是共享内存大小,1MB大约能存4000个会话,10MB够绝大多数中小站点用,这个数字不需要精确计算,按量级估就行。ssl_prefer_server_ciphers off在TLS 1.3下其实已经不起作用,但保留是为了兼容还在用1.2的客户端。
证书续期这件事必须上自动化,手工续期迟早会忘。自动化任务跑完之后一定要带一条重载命令,否则新证书落地了但NGINX还在用内存里的旧证书,浏览器照样报警告。
# 续期后重载,放在自动化任务的钩子里 nginx -t && systemctl reload nginx提示:证书文件路径的权限要检查。私钥文件如果对worker用户不可读,NGINX启动时就会报错退出,而且错误信息在
nginx -t阶段不一定暴露,因为检查时用的是当前用户身份。这种情况我遇到过,最后是靠error_log里的cannot load certificate key定位的。
4.2 前后端分离场景下的反向代理配置
现在纯静态站少了,多数是前端打包产物加一个后端服务。这种结构里NGINX承担两个角色:静态资源服务器和反向代理。
server { listen 443 ssl; http2 on; server_name example-site.test; ssl_certificate /etc/nginx/ssl/example-site.test.pem; ssl_certificate_key /etc/nginx/ssl/example-site.test.key; include /etc/nginx/conf.d/ssl-common.conf; root /var/www/example-site/html; index index.html; # 前端路由,history模式必须回落到index.html location / { try_files $uri $uri/ /index.html; } # 静态资源长缓存 location ^~ /assets/ { expires 30d; add_header Cache-Control "public, immutable"; access_log off; } # 接口转发 location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_http_version 1.1; 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; proxy_connect_timeout 5s; proxy_read_timeout 30s; } }几个细节值得单独说。proxy_pass http://127.0.0.1:8080/;末尾这个斜杠决定了路径怎么拼接:带斜杠时,/api/user转发到后端是/user;不带斜杠时是/api/user。这一个字符的差别,是反代配置里最高频的踩坑点,我建议你在写的时候就决定好后端是否需要/api前缀,然后在配置里加注释标明。
X-Forwarded-For这几个头是给后端用的。后端如果要记录真实客户端IP、要判断请求是不是HTTPS来的,全靠这几个头。不传的话后端看到的全是127.0.0.1,做限流、做审计、生成绝对URL全都会错。
超时设置要有意识地配。proxy_connect_timeout 5s意味着后端如果在5秒内没建立连接,直接返回504,而不是一直挂着把连接池占满。proxy_read_timeout 30s是等后端响应的最长时间,长轮询或者导出大文件的接口需要单独放宽,但不要全局设成几分钟,那等于放弃了超时保护。
access_log off放在静态资源location里,是我在小站点上省磁盘的常用手段。图片和JS的访问日志量往往占总量八成以上,价值却很低,关掉之后日志体积能降一个数量级。前提是你已经配了别的监控手段,别把唯一的观测窗口关掉。
4.3 压缩、连接数与缓冲区:几个需要算一算的参数
gzip基本是必开的,但要注意它只对文本类内容有效。图片、视频本身就是压缩格式,再压一遍纯属浪费CPU,还可能让响应变大。
gzip on; gzip_vary on; gzip_min_length 1024; gzip_comp_level 5; gzip_types text/plain text/css application/json application/javascript application/xml image/svg+xml;gzip_comp_level我固定用5。这个值的曲线很有意思:1到5之间压缩率提升明显,5到9之间提升很小但CPU开销接近线性增长。对一个CPU只有几核的小机器来说,5是性价比拐点。gzip_min_length 1024是避免对几百字节的小文件做压缩——压缩后的体积可能反而更大,还多了一次CPU运算。
worker_connections和worker_processes的关系值得算一下。单个worker能处理的最大连接数由worker_connections决定,但每个反向代理连接会同时占用一个客户端连接和一个后端连接,所以理论最大并发大约是worker_processes × worker_connections / 2。默认的512在多站点场景下偏小,我一般设到4096,同时把worker_rlimit_nofile提到65535以上,否则会被系统的文件描述符上限卡住,日志里出现too many open files。
worker_processes auto; worker_rlimit_nofile 65535; events { worker_connections 4096; multi_accept on; }worker_processes auto让NGINX自己读CPU核数,比手写数字更省心。multi_accept on让worker一次尽可能多地接受新连接,在突发流量下能减少唤醒次数。
缓冲区参数client_body_buffer_size和client_max_body_size是上传场景绕不开的。后者默认只有1MB,用户传个稍大的图就返回413,这个错误码很多人第一反应是"服务器挂了",实际是配置限制。按业务实际需求设,别一上来就开1G,那等于给内存攻击留了口子。
4.4 日志格式:为将来的排查预留信息
默认的combined格式够用,但我建议自定义一份,把请求耗时和上游耗时都加上。这两个字段在排查"慢"的问题时价值极高,能直接区分是NGINX自己慢还是后端慢。
log_format main_ext '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" rt=$request_time ' 'urt=$upstream_response_time uaddr=$upstream_addr'; access_log /var/log/nginx/example-site.access.log main_ext;$request_time是NGINX从收到第一个字节到发出最后一个字节的总耗时,$upstream_response_time是后端处理耗时。两者差值大,说明瓶颈在网络传输或者NGINX本身;两者接近且都很大,说明是后端的问题。这个判断逻辑我用了很多年,比上监控系统更快。
日志轮转交给系统的logrotate就好,默认策略是按天切、保留若干份、切完发信号让NGINX重新打开文件描述符。这里有个小坑:如果自定义了日志路径,要确认轮转配置覆盖到了新路径,否则日志会一直往同一个文件里写,最后把磁盘填满。
5. 故障排查实录:那几类错误到底该怎么定位
5.1 403、404、502、504的定位路径
这四类状态码覆盖了我遇到问题的九成以上,每一种的排查入口都不一样。
403 Forbidden基本都是权限问题。第一步看error_log,找Permission denied,然后用namei -l检查整条路径。还有一种情况是目录请求但没配index也没开autoindex,NGINX会拒绝列目录,同样返回403。第三种是SELinux开着但文件的安全上下文不对,这个在RHEL系上很常见,日志里会有denied字样。
404 Not Found相对好排查,就是路径没对上。要注意区分"文件真的不存在"和"root配错了"。我遇到过root写成相对路径导致解析到编译时的默认前缀下,文件明明在却始终404。检查方法是临时在location里加一行return 200 $document_root;把实际解析出的根目录打出来,确认后立刻删掉。
502 Bad Gateway是后端连不上或者返回了非法响应。按顺序查三件事:后端进程在不在、端口通不通、地址写没写错。proxy_pass里写127.0.0.1但后端只监听了0.0.0.0之外的某个地址,或者端口写错一位,都会502。用curl直接在服务器上打后端端口,能立刻区分是NGINX的问题还是后端的问题。
504 Gateway Timeout是连接上了但响应超时。这时候要看$upstream_response_time,如果后端确实在处理只是在慢慢跑,那就得优化后端或者单独放宽那个接口的超时;如果后端根本没收到请求,那问题可能在连接池耗尽,需要看upstream的健康状态。
5.2 三个隐形坑:端口占用、防火墙、配置未生效
有些问题不属于状态码范畴,但极耗时间,我把它单独列出来。
端口占用。启动失败但nginx -t通过,八成是端口被占。ss -lntp | grep :80能直接看到谁占着。常见的是系统里还跑着另一个http服务,或者上一次的NGINX进程没退干净。后者用ps aux | grep nginx确认,必要时先停再起。
防火墙。本机curl 127.0.0.1通、外部访问不通,基本就是防火墙或安全组。这一层要分两处查:系统内的防火墙规则,以及云平台侧的安全组。我见过配置改了半天,最后发现是控制台上的入站规则没放行443。
配置未生效。改完文件、nginx -t通过、也reload了,但行为没变。这时候先确认改的是不是真正被加载的那个文件——用nginx -T把所有生效配置打印出来搜一下关键词,能立刻发现是不是改到了sites-available但软链接没建。另一种情况是浏览器或中间层缓存,用curl加随机查询参数绕过缓存再测。
5.3 常见问题速查表
| 现象 | 最可能原因 | 快速验证 | 处理 |
|---|---|---|---|
| 403 Forbidden | 目录缺 x 权限或属主不对 | namei -l 路径 | 调整目录权限为755,属主改为worker用户 |
| 404 但文件存在 | root 路径解析错误 | location里临时输出$document_root | 修正 root 为绝对路径 |
| 502 Bad Gateway | 后端未启动或地址端口错 | 本机curl后端端口 | 启动后端或修正proxy_pass |
| 504 超时 | 后端处理慢或连接池耗尽 | 看$upstream_response_time | 优化后端或按接口放宽超时 |
| 413 上传失败 | client_max_body_size太小 | 查配置值 | 按业务需要调大 |
| 改配置无效果 | 改的文件未被include | nginx -T搜关键词 | 补软链接或修正include路径 |
| 静态资源无缓存头 | location被正则抢走 | 看响应头Cache-Control | 前缀匹配改用^~ |
| 启动报错但语法通过 | 端口被占 | ss -lntp | 释放端口或改监听 |
| 日志暴涨占满磁盘 | 静态资源也记了access_log | 看日志文件增长 | 静态location关日志并检查轮转 |
| 证书已续期但浏览器仍报旧 | 未reload | systemctl status看启动时间 | 续期钩子里加重载命令 |
这张表我建议打印出来贴在显示器边上,前三个月会高频用到。用得多了你会发现,绝大多数"诡异问题"最后都落在这十行里。
6. 长期维护:让这套配置半年后还能改得动
6.1 配置纳入版本管理,改之前先能回滚
配置这件事,最危险的状态是"只有线上那一份,谁也不敢动"。我现在的做法是把/etc/nginx整个目录纳入版本管理,每次改动都是一次提交,提交信息里写清楚为什么改。
cd /etc/nginx sudo git init sudo git add -A sudo git commit -m "baseline: 初始可用配置" # 后续每次改动 sudo git diff # 改完先看差异,确认没误删 sudo nginx -t && sudo systemctl reload nginx sudo git commit -am "调整静态资源缓存策略为30天"git diff这个动作看着简单,实际救过我几次——有次编辑时误删了一整段location,diff一眼就看出来了。没有版本管理的话,这种失误只能等到用户反馈才发现。
回滚也同样直接:git checkout -- 文件名然后reload。比起手工备份文件加时间戳,这个流程更干净,也不会在目录里堆一堆.bak。
6.2 安全加固的几条底线
NGINX本身的安全记录不错,出问题多半是配置层面的疏忽。这几条是我每个站点上线前必查的:
隐藏版本号。默认的响应头会带NGINX版本,虽然不算高危,但没必要主动暴露。加一行server_tokens off;就够。
限制敏感路径。.git、.env、备份文件这类东西一旦被扫到就是大事。用一条正则统一拦掉,返回403或444(444是NGINX特有的,直接断连接不回响应,比403更省资源):
location ~ /\.(?!well-known) { deny all; return 404; }(?!well-known)这个负向断言是为了放行证书验证用的/.well-known/路径,不加的话自动化签发会失败。
加基础限流。哪怕是小站,也经常被扫描器打到。用limit_req_zone按IP限速,把阈值设在正常用户不会触及的水平:
# 定义在 http 块里 limit_req_zone $binary_remote_addr zone=basic:10m rate=10r/s; # 在 location 里引用 limit_req zone=basic burst=20 nodelay;rate=10r/s是平均速率,burst=20是允许的突发量,nodelay表示突发额度不排队直接放行。这套组合的效果是正常浏览完全无感,而高频扫描会立刻被429挡掉。
关掉不需要的方法。静态站根本用不到PUT、DELETE,直接在server块里限制,减少攻击面。另外确认autoindex是关闭状态,目录列表泄露文件结构这种事,扫描器最喜欢。
注意:加固要一个个加、加完测。我见过一次性贴进去十几条限制,结果正常请求被误伤,回头排查哪条规则起作用花了很久。每加一条,就用真实请求验证一遍。
6.3 我个人的几条实操体会
最后说几个文档里不太会写、但实际影响很大的点。
第一,配置的可读性比简洁性重要。有人喜欢把所有站点塞进一个文件,美其名曰集中管理,实际结果是改A站影响B站的风险大增。一个站点一个文件,哪怕内容有重复,也比耦合在一起强。
第二,注释只写"为什么",不写"是什么"。# 设置根目录这种注释毫无价值,# 前端是history路由,未匹配路径需回落到index才有价值,因为它记录了当时的判断依据。半年后接手的人(包括你自己)需要的是判断依据,不是指令翻译。
第三,不要追求一次配到完美。我最早的配置里什么缓存、什么限流都没有,就是能让页面打开。后来遇到日志撑爆磁盘才加轮转,遇到扫描才加限流,遇到慢接口才调超时。需求驱动的配置演进,比照抄一份"最佳实践"更容易理解每一行的意义。抄来的配置,出问题时你不知道该删哪一行。
第四,上线前做一次"故意破坏"测试。把后端停掉、把权限改错、把配置写错一行,看看错误信息你能不能看懂、能不能在五分钟内定位。这个演练的成本很低,收益是在真实故障时不慌。
第五,监控不用复杂,但要有。我最低配的方案是两个:一个定时任务每分钟请求首页,状态码非200就告警;一个磁盘使用率检查,超过80%就提醒。这两条覆盖了我遇到过的绝大多数"网站突然不可用"的场景,剩下的靠日志回溯。
这套东西搭下来,一个中小型站点该有的都有了。再往后走,就是CDN、多机部署、灰度发布那些话题了,但那是另一个层面的问题——单机这套配置稳了,才有资格谈后面的事。