初识 Nginx
你好,标题是那个梗的谐音。只是突然想起的一首歌。
最近文章更新异常频繁,是因为一旦开始在意就停不下来,正好到了这个时期。
Nginx
到目前为止,我一直将其作为反向代理兼缓存服务器来搭建,但随着对 IPv6 的支持,我开始在意起来,于是重新审视了包括重构在内的设置。
文件结构
虽然隐藏了一部分,但大概是这个样子:
├── http.d
│ ├── bot_rate_limit.conf
│ ├── gzip.conf
│ ├── proxy_cache_zones.conf
│ └── proxy_common.conf
├── mime.types
├── mime.types-dist
├── nginx.conf
├── scgi_params
├── selfsigned.crt
├── selfsigned.key
├── sites-enabled
│ ├── 1btc.love.conf
│ ├── btclol.xyz.conf
│ ├── damepo.jp.conf
│ ├── git.soulminingrig.com.conf
│ ├── soulminingrig.com.conf
│ ├── starlink.soulminingrig.com.conf
│ ├── stg.api.1btc.love.conf
├── snippets
│ ├── common_error_pages.conf
│ ├── proxy_headers.conf
│ └── ssl_common.conf
├── uwsgi_params
└── win-utf
我曾纠结于如何命名管理按 location 指令加载的 conf 文件夹,在咨询了 ChatGPT 后决定采用现在的方案。由于 http.d 中的文件是从 nginx.conf 的 http 指令中 include 进来的,所以我觉得这样很合理。
虽然觉得命名为 snippets 有点微妙,但也就这样吧。
让我们来看看各个设置项。
http.d/bot_rate_limit.conf
因为 Meta 的爬虫太疯狂了,所以我决定不仅使用 fail2ban,还要根据 UA 级别进行限制。
另外,关于 feed/RSS,虽然有人特意联系过我,但考虑到应用限制也没什么意义,而且基本都是缓存响应,不太会给源站(Origin)带来访问负载,所以我就开了个“后门”(排除在外)。
# 仅针对 Bot 或链接展开爬虫进行速率限制
map $http_user_agent $is_bot {
default 0;
~*bot 1;
~*crawler 1;
~*spider 1;
~*facebookexternalhit 1;
~*slackbot 1;
~*discordbot 1;
~*twitterbot 1;
~*linkedinbot 1;
~*embedly 1;
~*quora 1;
~*skypeuripreview 1;
~*whatsapp 1;
~*telegrambot 1;
~*applebot 1;
~*pingdom 1;
~*uptimerobot 1;
}
# stg.api.1btc.love 用于验证,因此即使是 Bot 也不受速率限制
# 如果 key 为空字符串,则 limit_req_zone 不会计数
map $server_name $bot_limit_host_key {
stg.api.1btc.love "";
default $binary_remote_addr;
}
# feed.xml / feed.json 即使是 Bot 也不受速率限制
map $uri $is_feed_path {
default 0;
~*feed\.(xml|json)$ 1;
}
# 仅当是 Bot 且不是 feed.xml / feed.json 时,才使用基于 IP 的 key
map "$is_bot:$is_feed_path" $bot_limit_key {
default "";
"1:0" $bot_limit_host_key;
}
limit_req_zone $bot_limit_key zone=bot:10m rate=1r/s;
limit_req_status 429;
limit_req zone=bot burst=5 nodelay;
当然,由于是应用于 http 指令的,所以基本上是全局生效的,但我确保了可以进行最低限度的排除。我认为这些设置本来就应该默认启用。
如果伪造 UA 或伪装浏览器进行明显的 DoS 攻击,则会被 fail2ban 拦截并处理为 drop,一段时间内无法发送请求,这形成了一套双重防御。
http.d/gzip.conf
以前我曾尝试支持 brotli,但因为需要单独构建,升级版本时很麻烦,所以放弃了。能通过 pkg/apt 进行更新的优点非常大。
gzip on;
gzip_vary off;
gzip_proxied any;
gzip_min_length 1024;
gzip_comp_level 7;
gzip_http_version 1.1;
gzip_types text/plain
text/xml
text/css
text/javascript
image/gif
image/png
image/svg+xml
application/javascript
application/json
application/xml
application/x-javascript
application/font-woff
application/font-woff2
application/font-ttf
application/octet-stream;
没什么特别要说的。以前没加,但从几年前开始我加上了 gzip_min_length。当时觉得如果进行低效率的压缩也是浪费,所以决定好好设置一下。
http.d/proxy_cache_zones.conf
虽然看起来缩进有点乱,但那是格式化工具的限制。
删掉不需要的东西后,zone 从 4 开始或者变得零散了,不过嘛……
关于 inactive,如果 7 天没有命中就会被删除;关于 use_temp_path,我设置为直接在缓存路径中进行缓存。即使设置了像 proxy_temp_path /tmp/nginx; 这样的配置,它也会绕过该路径直接缓存,据说这样会更快。
proxy_cache_path /tmp/nginx/zone4 levels=1:2 keys_zone=zone4:10m
inactive=7d
max_size=3g
use_temp_path=off;proxy_cache_path /tmp/nginx/posts levels=1:2 keys_zone=posts:10m
inactive=7d
max_size=2g
use_temp_path=off;
proxy_cache_path /tmp/nginx/git levels=1:2 keys_zone=git:10m
inactive=7d
max_size=2g
use_temp_path=off;
proxy_cache_path /tmp/nginx/static levels=1:2 keys_zone=static_cache:10m
inactive=7d
max_size=1g
use_temp_path=off;
proxy_cache_path /tmp/nginx/1btc_cache levels=1:2 keys_zone=1btc_cache:10m
inactive=7d
max_size=512m
use_temp_path=off;
http.d/proxy_common.conf
我将 proxy_cache_valid 设置为通用规则,其余部分则在 location 指令中进行覆盖。
这样即使没有专门设置缓存,也能进行缓存运行。
通过设置 proxy_cache_bypass $http_cookie,可以防止带有 Cookie 的请求(例如登录后)仍然显示登录前的页面。
此外,对重定向、错误以及 any 进行缓存响应是为了防范攻击。这样做可以确保至少有缓存响应,从而防止异常流量到达源站 (Origin)。
虽然 proxy_temp_path 本来最好指向可以持久化的路径,但基本上在我的用途中没有这种场景,所以指向了 /tmp。在有一定流量的网站上这样做,虽然有刚才提到的那些不使用的选项,但可能服务器重启后缓存会全部消失,导致源站负载升高,存在故障风险。
proxy_buffering on;
proxy_cache_bypass $http_cookie;
proxy_cache_background_update on;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_revalidate on;
proxy_cache_use_stale updating;
proxy_connect_timeout 60;
proxy_no_cache $http_cookie;
proxy_read_timeout 90;
proxy_send_timeout 60;
proxy_temp_path /tmp/nginx;
proxy_cache_valid 200 201 60s;
proxy_cache_valid 301 1d;
proxy_cache_valid 302 3h;
proxy_cache_valid 304 1d;
proxy_cache_valid 404 1m;
proxy_cache_valid any 5s;
proxy_cache_lock on;
nginx.conf
只是设置了在直接访问 A 记录 IP 时返回专用错误,没有太多可说的。
开启 multi_accept 是因为我的反向代理兼缓存服务器运行在一个配置不高的弱小实例上。
原本直接写在 http 指令中的设置现在按目的进行 include,可读性大大提高了。
worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 65535;
events {
multi_accept on;
worker_connections 65535;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
types_hash_max_size 4096;
client_max_body_size 16M;
# MINE
include mime.types;
default_type application/octet-stream;
include ./http.d/bot_rate_limit.conf;
include ./http.d/proxy_common.conf;
include ./http.d/proxy_cache_zones.conf;
include ./http.d/gzip.conf;
server {
listen 80;
listen [::]:80;
server_name 163.44.113.145 91.98.169.80 2400:8500:2002:3317:163:44:113:145;
include snippets/common_error_pages.conf;
return 444;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name 163.44.113.145 91.98.169.80 2400:8500:2002:3317:163:44:113:145;
ssl_certificate ./selfsigned.crt;
ssl_certificate_key ./selfsigned.key;
include snippets/common_error_pages.conf;
# 拒绝访问 IP 地址
return 444;
}
### Damepo.jp
include ./sites-enabled/damepo.jp.conf;
### Soulminingrig My Blog
include ./sites-enabled/soulminingrig.com.conf;
# ~~~略~~~~
}
sites-enabled/soulminingrig.com.conf
作为示例,我只介绍 site-enabled 下的一个配置。这是本站的设置。
另外,我不使用符号链接只是为了能快速删除不需要的东西。
实际上有些地方仍然是硬编码的,请谅解,目前还在调整中。
顺便说一下,最近我改成了 www.soulminingrig.com,但之前是用不带 www 的根域名发布的,所以目前仍然接受根域名且不进行重定向。
在验证期间,我设置了响应头,以便清楚地辨别是服务器缓存响应还是客户端缓存响应。
还有,我在想图片、字体、CSS 之类的缓存规则能不能再优化一下……唉,真的没办法了吗……这个……
设置 upstream 是为了在后端增加时能快速应对。虽然目前只有一个,但放在最上面可以很容易看清配置指向哪个后端。
upstream backend_sm {
server 10.1.0.228:8888 max_fails=3 fail_timeout=3s;
keepalive 16;
keepalive_timeout 30s;
}
map $uri $static_cache {
~\.(jpg|jpeg|png|webp|gif|mp4|css|js|ico|woff2)(\?.*)?$ "public, max-age=604800";
~\.html$ "public, max-age=600";
default "public, max-age=600";
}
map $upstream_cache_status $server_cache_status {
default $upstream_cache_status;
"" "NONE";
}
map "$http_if_none_match:$http_if_modified_since" $client_cache_request {
default "MISS";
"~.+:.+" "REVALIDATE";
"~.+:" "REVALIDATE";
"~:.+" "REVALIDATE";
}
server {
listen 80;
listen [::]:80;
server_name soulminingrig.com www.soulminingrig.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl reuseport backlog=65535 rcvbuf=256k sndbuf=256k fastopen=256 so_keepalive=on;
listen [::]:443 ssl reuseport backlog=65535 rcvbuf=256k sndbuf=256k fastopen=256 so_keepalive=on ipv6only=on;
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
http2 on;
http3 on;
server_name soulminingrig.com www.soulminingrig.com;
client_max_body_size 50M;
location ~* \.(jpg|jpeg|png|webp|gif|ico|mp4|js|css|woff2)(\?.*)?$ {
proxy_pass http://backend_sm;
include snippets/proxy_headers.conf;
proxy_http_version 1.1;
proxy_redirect off;
proxy_cache static_cache;
proxy_cache_valid 200 301 302 7d;
proxy_cache_valid 404 1m;
proxy_cache_revalidate on;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;
proxy_connect_timeout 3s;
proxy_read_timeout 15s;
expires 7d;
add_header X-Cache-Status $upstream_cache_status always;
add_header X-Server-Cache-Status $server_cache_status always;
add_header X-Client-Cache-Request $client_cache_request always;
add_header X-Client-Cache-Policy "public, max-age=604800" always;
add_header Cache-Control "public, max-age=604800" always;
}
location / {
proxy_pass http://backend_sm/;
include snippets/proxy_headers.conf;
proxy_http_version 1.1;
proxy_redirect off;
proxy_cache posts;
proxy_cache_key $scheme$host$request_uri;
proxy_cache_valid 200 10m;
proxy_cache_valid 301 1h;
proxy_cache_valid 404 1m;
proxy_cache_revalidate on;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504 http_403 http_404;
proxy_cache_background_update on;
proxy_cache_lock on;
proxy_connect_timeout 3s;
proxy_read_timeout 15s;
expires $static_cache;
add_header X-Cache-Status $upstream_cache_status always;
add_header X-Server-Cache-Status $server_cache_status always;
add_header X-Client-Cache-Request $client_cache_request always;
add_header X-Client-Cache-Policy $static_cache always;
add_header Cache-Control $static_cache always;
}
include snippets/common_error_pages.conf;
include snippets/ssl_common.conf;
ssl_certificate /hoge/fullchain.pem; # 由 Certbot 管理
ssl_certificate_key /hoge/oulminingrig.com/privkey.pem;
# 由 Certbot 管理
}
额外福利: nginxfmt.py
这个真的很好用。
虽然它是一个格式化工具,但我觉得它的质量是最高的。
可以在 AUR 中找到。
yay -S nginx-config-formatter
用法是使用 nginxfmt.py example.conf 进行格式化,所以
find . -name "*conf" | xargs -I{} nginxfmt.py {}
这样就可以批量格式化了。
不久前我还在使用 nginxbeautifier,但由于它会破坏重定向相关的语法,所以我换成了这个。