个人博客低成本CC防护：抵御85%+常见CC攻击
低成本CC防护：我的实战配置与效果喜报：被大规模CC了！平常的一天，突然受到了来自衣食父母华为云的服务器告警：虚拟机网络连接数突破15400个，CPU占用超过 85% 。就知道肯定是被CC了。果然网站被打是催更技术类博主最好的方式（雾不过我之前是设置过缓存的，有点好奇怎么绕过缓存，事后我抓取了对应时间的Nginx日志access.log：43.152.29.37 - - [30/Oct/2025:23:27:16 +0800] "GET /3bb6bfea9/90a2c9c9fc?limit=ef616a8b1b145df&token=176fe0f01f&utm_source=a5d4f247077b HTTP/1.0" 404 29335 "http://www.nekopara.uk/3bb6bfea9/90a2c9c9fc?limit=ef616a8b1b145df&token=176fe0f01f&utm_source=a5d4f247077b" "Mozilla/5.0 (Windows NT 11.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0" "91.209.77.93, 43.152.29.37" 43.152.24.20 - - [30/Oct/2025:23:27:16 +0800] "GET /profile/config/delete?search=9844b7e151&version=504397e6490c&utm_campaign=bd5783ef290&page=4b9e8cc670d HTTP/1.0" 404 29335 "http://www.nekopara.uk/profile/config/delete?search=9844b7e151&version=504397e6490c&utm_campaign=bd5783ef290&page=4b9e8cc670d" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" "138.2.109.39, 43.152.24.20" 43.152.143.43 - - [30/Oct/2025:23:27:16 +0800] "GET /health/admin?utm_medium=eb37025 HTTP/1.0" 404 29335 "http://www.nekopara.uk/health/admin?utm_medium=eb37025" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36" "163.61.245.58, 43.152.143.43" 43.174.55.12 - - [30/Oct/2025:23:27:16 +0800] "GET /5de3c/0727afb/info?type=f06d22885b8 HTTP/1.0" 404 29335 "http://www.nekopara.uk/5de3c/0727afb/info?type=f06d22885b8" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15" "103.184.54.67, 43.174.55.12" 43.152.14.36 - - [30/Oct/2025:23:27:16 +0800] "GET /edit/a97b/cba81414/content.htm?offset=09cd68b44dd60&key=030f41284 HTTP/1.0" 404 29335 "http://www.nekopara.uk/edit/a97b/cba81414/content.htm?offset=09cd68b44dd60&key=030f41284" "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36" "49.145.113.40, 43.152.14.36" 43.152.14.27 - - [30/Oct/2025:23:27:16 +0800] "GET /status/356037c/list/app.json?id=8ccefcb24&key=257c67462c3ef80&token=45b973f&lang=373143c9654e06f HTTP/1.0" 404 29335 "http://www.nekopara.uk/status/356037c/list/app.json?id=8ccefcb24&key=257c67462c3ef80&token=45b973f&lang=373143c9654e06f" "Mozilla/5.0 (Windows NT 11.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0" "138.2.109.39, 43.152.14.27" 43.152.24.20 - - [30/Oct/2025:23:27:16 +0800] "GET /upload/view/detail?utm_source=0a511c HTTP/1.0" 404 29335 "http://www.nekopara.uk/upload/view/detail?utm_source=0a511c" "Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Mobile Safari/537.36" "119.93.135.193, 43.152.24.20" 101.33.20.48 - - [30/Oct/2025:23:27:16 +0800] "GET /05ad3/list/view/data.txt?timestamp=7cf23ee09d6dc HTTP/1.0" 404 29335 "http://www.nekopara.uk/05ad3/list/view/data.txt?timestamp=7cf23ee09d6dc" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15" "187.49.87.255, 101.33.20.48" 43.175.30.25 - - [30/Oct/2025:23:27:16 +0800] "GET /profile/4e95d5?limit=b15210f1d9b08&token=63e194b&type=bfea84c37a29f8&nonce=b222a4b1b38a HTTP/1.0" 404 29335 "http://www.nekopara.uk/profile/4e95d5?limit=b15210f1d9b08&token=63e194b&type=bfea84c37a29f8&nonce=b222a4b1b38a" "Mozilla/5.0 (Linux; Android 15; Pixel 9 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Mobile Safari/537.36" "206.84.201.101, 43.175.30.25" 43.152.29.37 - - [30/Oct/2025:23:27:16 +0800] "GET /8ac4af716/info.php?ref=b09658abd0&page=3d48aaa2614300&page=5b84ad8c3&token=717bad8cac HTTP/1.0" 404 16 "http://www.nekopara.uk/8ac4af716/info.php?ref=b09658abd0&page=3d48aaa2614300&page=5b84ad8c3&token=717bad8cac" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" "41.139.226.11, 43.152.29.37" 43.152.26.48 - - [30/Oct/2025:23:27:16 +0800] "GET /info?offset=34297ebb3908dec&timestamp=91d8330f0f6330&sort=8d7f7fe923&category=9f7158e6486f01f HTTP/1.0" 404 29335 "http://www.nekopara.uk/info?offset=34297ebb3908dec&timestamp=91d8330f0f6330&sort=8d7f7fe923&category=9f7158e6486f01f" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0" "2.139.62.85, 43.152.26.48" 43.152.143.41 - - [30/Oct/2025:23:27:16 +0800] "GET /6cc1/download?category=248162dd8&token=fc785d9170235a&id=e6c8ef9&limit=b852d3ab3d28 HTTP/1.0" 404 29335 "http://www.nekopara.uk/6cc1/download?category=248162dd8&token=fc785d9170235a&id=e6c8ef9&limit=b852d3ab3d28" "Mozilla/5.0 (iPhone; CPU iPhone OS 18_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Mobile/15E148 Safari/604.1" "203.76.222.119, 43.152.143.41" 好家伙，随机请求URL，每一次都不一样，难怪能MISS，CPU直接被干爆了！而且，我还惊奇的发现，记录下来的都是EdgeOne的IP，这也暴露了之前设置，传递来源IP没做好。既然发现了弱点和问题，那我们就来解决它！修复来源IP传递异常首先，这个问题出现在同VPC的跳板服务器上，之前只是添加了请求头，但是没有信任上一级也就是来源于CDN的IP的X-Forwarded-For请求头。并且还重复添加了请求头导致最终源服务器上呈现的是两个不一样的X-Forwarded-For，第一个是真实的IP,第二个是CDN的IP。修复方法很简单，不在跳板服务器添加请求头，并且信任CDN的X-Forwarded-For请求头就好了，直接传递过去。下面是配置文件：server { listen 443 ssl; server_name www.nekopara.uk; set_real_ip_from 0.0.0.0/0; real_ip_header X-Forwarded-For; ssl_certificate /data/certs/nekopara_uk_cf.pem; ssl_certificate_key /data/certs/nekopara_uk_cf.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; location / { client_max_body_size 32m; proxy_pass https://www.nekopara.uk:443; proxy_set_header X-Forwarded-Proto $scheme; }注意！我这里设置信任整个IP段的前提是，我已经在安全组设置了访问白名单，仅CDN的IP可以访问443和80端口，所以这样子设置是安全的。如果你没有设置访问白名单，这样子会有较大的安全风险！你应该在这部分逐个加入CDN的IP进行过滤，并且考虑屏蔽其他IP的请求。能够正常传递来源IP是后面进行设置的基础，如果没有正常传递来源IP，源站的Nginx配置规则会屏蔽掉CDN的访问导致访问全部被拒绝！一定要注意！为源站设置请求限流因为大量这种随机请求的资源消耗是相当可观的，我们需要进行严格的限制。第一次尝试的解决方案我第一次考虑的做法是考虑在Nginx的配置文件中添加limit_req_zone $binary_remote_addr zone=dynamic_limit:10m rate=1r/s;和limit_req zone=dynamic_limit burst=2 nodelay;进行限制，普通用户正常访问不以极高的手速快速刷新页面的话基本上不会被阻止。同时，使用limit_req_zone $binary_remote_addr zone=static_limit:10m rate=50r/s;和limit_req zone=static_limit burst=50 nodelay;对于静态的文件进行独立限流，因为访问网站会产生多个静态资源文件请求，限流规则应该设置得更为宽松，避免误伤正常用户。还有就是，如果设置了限流，默认Nginx会返回503错误代码并显示：503 Service Temporarily Unavailable。我认为不够友好，于是定义将错误返回为429，也就是Too Many Requests，更好的传达实际情况，并且自定义了429界面。请注意，返回的429界面需要尽可能的小和简单，传达清楚意思即可。如果太过于华丽，文件肯定会比较大，会占用较大的服务器上行带宽。以下是详细配置文件：主配置文件nginx.conf:# /etc/nginx/nginx.conf user nginx; # Set number of worker processes automatically based on number of CPU cores. worker_processes auto; # Enables the use of JIT for regular expressions to speed-up their processing. pcre_jit on; # Configures default error logger. error_log /var/log/nginx/error.log warn; # Includes files with directives to load dynamic modules. include /etc/nginx/modules/*.conf; # Include files with config snippets into the root context. include /etc/nginx/conf.d/*.conf; events { # The maximum number of simultaneous connections that can be opened by # a worker process. worker_connections 1024; } http { # 动态页面限流配置（严格） limit_req_zone $binary_remote_addr zone=dynamic_limit:10m rate=1r/s; # 静态资源限流配置（宽松） limit_req_zone $binary_remote_addr zone=static_limit:10m rate=50r/s; # 定义 FastCGI 缓存路径 fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=my_fastcgi_cache:16m inactive=60m; # 定义缓存键，通常包含请求方法、主机名和 URI fastcgi_cache_key "$scheme$request_method$host$request_uri"; # Includes mapping of file name extensions to MIME types of responses # and defines the default type. include /etc/nginx/mime.types; default_type application/octet-stream; # Name servers used to resolve names of upstream servers into addresses. # It's also needed when using tcpsocket and udpsocket in Lua modules. #resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001]; # Don't tell nginx version to the clients. Default is 'on'. server_tokens off; # Specifies the maximum accepted body size of a client request, as # indicated by the request header Content-Length. If the stated content # length is greater than this size, then the client receives the HTTP # error code 413. Set to 0 to disable. Default is '1m'. client_max_body_size 1m; # Sendfile copies data between one FD and other from within the kernel, # which is more efficient than read() + write(). Default is off. sendfile on; # Causes nginx to attempt to send its HTTP response head in one packet, # instead of using partial frames. Default is 'off'. tcp_nopush on; # Enables the specified protocols. Default is TLSv1 TLSv1.1 TLSv1.2. # TIP: If you're not obligated to support ancient clients, remove TLSv1.1. ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; # Path of the file with Diffie-Hellman parameters for EDH ciphers. # TIP: Generate with: `openssl dhparam -out /etc/ssl/nginx/dh2048.pem 2048` #ssl_dhparam /etc/ssl/nginx/dh2048.pem; # Specifies that our cipher suits should be preferred over client ciphers. # Default is 'off'. ssl_prefer_server_ciphers on; # Enables a shared SSL cache with size that can hold around 8000 sessions. # Default is 'none'. ssl_session_cache shared:SSL:2m; # Specifies a time during which a client may reuse the session parameters. # Default is '5m'. ssl_session_timeout 1h; # Disable TLS session tickets (they are insecure). Default is 'on'. ssl_session_tickets off; # Enable gzipping of responses. #gzip on; # Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'. gzip_vary on; # Helper variable for proxying websockets. map $http_upgrade $connection_upgrade { default upgrade; '' close; } # Specifies the main log format. log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; # Sets the path, format, and configuration for a buffered log write. access_log /var/log/nginx/access.log main; # Includes virtual hosts configs. include /etc/nginx/http.d/*.conf; }网站配置文件nekopara.conf:server { listen 443 ssl; server_name nekopara.uk; ssl_certificate /data/certs/nekopara_uk.pem; ssl_certificate_key /data/certs/nekopara_uk.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384'; location / { return 302 https://www.nekopara.uk$request_uri; } } # Typecho HTTPS服务器块 server { listen 443 ssl; #proxy_protocol; server_name www.nekopara.uk; #set_real_ip_from 127.0.0.1; set_real_ip_from 192.168.1.255; real_ip_header X-Forwarded-For; #real_ip_recursive on; #set $real_host $http_x_forwarded_host; #if ($http_x_forwarded_host = '') { # set $real_host $host; #} # 添加这一行，确保生成的重定向不带后端端口 #port_in_redirect off; # 设置最大上传文件大小为 32M client_max_body_size 32M; # SSL证书和密钥路径 ssl_certificate /data/certs/nekopara_uk.pem; ssl_certificate_key /data/certs/nekopara_uk.key; # SSL设置（可选但推荐用于安全） ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # 网站根目录 root /data/typecho; index index.html index.php; # 1. 静态资源限流（宽松，50r/s） location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|eot|svg|mp4|webp|zip|rar|7z|psb)$ { limit_req zone=static_limit burst=50 nodelay; try_files $uri $uri/ =404; add_header Cache-Control "public"; } # PHP处理，并启用FastCGI缓存 location ~ \.php$ { limit_req zone=dynamic_limit burst=2 nodelay; fastcgi_ignore_headers Cache-Control Expires Set-Cookie; try_files $uri =404; fastcgi_pass unix:/var/run/php-fpm82/php8.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; # 启用 FastCGI 缓存 fastcgi_cache my_fastcgi_cache; fastcgi_cache_valid 200 301 302 404 10s; # 设置缓存的有效时间 fastcgi_cache_methods GET HEAD; # 只缓存 GET 和 HEAD 请求 # 不缓存 POST 请求和其他非幂等性请求 set $skip_cache 0; if ($request_method = POST) { set $skip_cache 1; } if ($query_string != "") { set $skip_cache 1; } # 如果存在特定会话 cookie，则不缓存 if ($http_cookie ~* "PHPSESSID|__typecho_uid|__typecho_authCode") { set $skip_cache 1; } fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; # 添加头部信息显示缓存状态 add_header X-FastCGI-Cache $upstream_cache_status; } # 3. 默认路由（用于Typecho伪静态） location / { try_files $uri $uri/ /index.php?$args; autoindex off; } #自定义503为429 Too Many Requests error_page 503 =429 /429.html; location = /429.html { internal; root /etc/nginx/error_pages; #添加响应头告诉客户端或者爬虫请10秒后重试 add_header Retry-After 10 always; } # Deny access to hidden files location ~ /\.ht { deny all; } }自定义429界面429.html： 喵~别急！429 Too Many Requests 喵呜~刷新太快啦！ 429 Too Many Requests 小喵需要休息10秒~ ฅ^•ﻌ•^ฅ 但是在后续自己模拟单点慢速攻击的时候发现了一个问题，如果是持续不断的请求，即使大部分是429拒绝的相应，仍有夹杂着404的查询相应。虽然单个IP影响不大，但是如果攻击规模较大，仍然会拖垮服务器，并且也会比返回429错误消耗更多的带宽，需要更精准的限制和打击。这是测试用的Python程序：import requests import time import argparse import csv import os import string import random from datetime import datetime def generate_random_path(length=15): """生成指定长度的随机字符串（包含大小写字母和数字）""" characters = string.ascii_letters + string.digits return ''.join(random.choices(characters, k=length)) def main(): parser = argparse.ArgumentParser(description='随机URL请求测试脚本（用于验证服务器处理能力）') parser.add_argument('domain', help='目标域名（例如: example.com）') parser.add_argument('--interval', type=int, default=1000, help='请求间隔（毫秒，默认1000毫秒=1秒）') parser.add_argument('--count', type=int, default=100, help='请求总次数（默认100次）') parser.add_argument('--output', default='random_request_test.csv', help='输出CSV文件名（默认: random_request_test.csv）') parser.add_argument('--timeout', type=int, default=5, help='请求超时时间（秒，默认5秒）') parser.add_argument('--user-agent', default='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', help='自定义User-Agent') args = parser.parse_args() # 验证域名格式 if not args.domain.startswith('http'): args.domain = f"https://{args.domain}" # 创建请求头 headers = { 'User-Agent': args.user_agent, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.5', 'Connection': 'keep-alive' } # 准备结果列表 results = [] start_time = time.time() print(f"开始随机URL请求测试 (总请求: {args.count}, 间隔: {args.interval}ms, 超时: {args.timeout}s)") print(f"目标域名: {args.domain}") print(f"输出文件: {args.output}\n") # 创建CSV文件 with open(args.output, 'w', newline='', encoding='utf-8') as csvfile: fieldnames = ['timestamp', 'url', 'status', 'response_time_sec', 'content_length_bytes'] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() # 执行请求 for i in range(1, args.count + 1): # 生成随机路径 random_path = generate_random_path() url = f"{args.domain}/{random_path}" # 记录请求开始时间 request_start = time.time() try: # 发送请求 response = requests.get( url, headers=headers, timeout=args.timeout ) response_time = time.time() - request_start status = response.status_code content_length = len(response.content) # 记录结果 result = { 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'url': url, 'status': status, 'response_time_sec': response_time, 'content_length_bytes': content_length } results.append(result) # 输出进度 print(f"[{i}/{args.count}] {url} | 状态: {status} | 响应时间: {response_time:.3f}s | 大小: {content_length}B") # 写入CSV writer.writerow(result) except Exception as e: error_msg = str(e) result = { 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'url': url, 'status': 'ERROR', 'response_time_sec': 0, 'content_length_bytes': 0 } results.append(result) print(f"[{i}/{args.count}] {url} | 错误: {error_msg}") # 间隔处理（最后一个请求不等待） if i < args.count: time.sleep(args.interval / 1000.0) # 生成报告 total_time = time.time() - start_time print(f"\n测试完成! 总耗时: {total_time:.2f}秒") print(f"结果已保存至: {args.output}") if __name__ == "__main__": main()这是测试的输出：[vanilla@NekoWorks ~]$ python3 random_request_test.py www.nekopara.uk --interval 50 --count 500 开始随机URL请求测试 (总请求: 500, 间隔: 50ms, 超时: 5s) 目标域名: https://www.nekopara.uk 输出文件: random_request_test.csv [1/500] https://www.nekopara.uk/6pKJpS6oAgTGGg9 | 状态: 404 | 响应时间: 0.430s | 大小: 29335B [2/500] https://www.nekopara.uk/h9utooL2wE8Iu9R | 状态: 404 | 响应时间: 0.373s | 大小: 29335B [3/500] https://www.nekopara.uk/VQr7eBnBMePGezI | 状态: 404 | 响应时间: 0.389s | 大小: 29335B [4/500] https://www.nekopara.uk/r87rqw8ftyrOV4E | 状态: 404 | 响应时间: 0.364s | 大小: 29335B [5/500] https://www.nekopara.uk/V8BBeJqFidADG8a | 状态: 429 | 响应时间: 0.274s | 大小: 266B [6/500] https://www.nekopara.uk/2FModsb5p1imNZY | 状态: 404 | 响应时间: 0.360s | 大小: 29335B [7/500] https://www.nekopara.uk/IGr3v06kf9xbw2L | 状态: 429 | 响应时间: 0.217s | 大小: 266B [8/500] https://www.nekopara.uk/vMUNbNh12WseZ79 | 状态: 429 | 响应时间: 0.265s | 大小: 266B [9/500] https://www.nekopara.uk/1qkNpklBD60HZlS | 状态: 429 | 响应时间: 0.218s | 大小: 266B [10/500] https://www.nekopara.uk/cg5pKmaTfI0pJF0 | 状态: 404 | 响应时间: 0.307s | 大小: 29335B [11/500] https://www.nekopara.uk/xPgKNHa3UTBVzBV | 状态: 429 | 响应时间: 0.217s | 大小: 266B [12/500] https://www.nekopara.uk/HvwRcAMHNb24egX | 状态: 429 | 响应时间: 0.293s | 大小: 266B [13/500] https://www.nekopara.uk/A7f0eWXeO35zUg3 | 状态: 404 | 响应时间: 0.287s | 大小: 29335B [14/500] https://www.nekopara.uk/SdadEt4mQFNUI2P | 状态: 429 | 响应时间: 0.278s | 大小: 266B [15/500] https://www.nekopara.uk/3ZJncTYxzQovjIA | 状态: 429 | 响应时间: 0.227s | 大小: 266B [16/500] https://www.nekopara.uk/gljFhsR4cYYk3aP | 状态: 404 | 响应时间: 0.301s | 大小: 29335B [17/500] https://www.nekopara.uk/CaFhCaIwy98bSRj | 状态: 429 | 响应时间: 0.216s | 大小: 266B [18/500] https://www.nekopara.uk/os3ZwlQY9d9gikA | 状态: 429 | 响应时间: 0.228s | 大小: 266B [19/500] https://www.nekopara.uk/Ai1OU2q1ucI5dRV | 状态: 404 | 响应时间: 0.366s | 大小: 29335B [20/500] https://www.nekopara.uk/HSzYv7RTC1bJBsD | 状态: 429 | 响应时间: 0.214s | 大小: 266B [21/500] https://www.nekopara.uk/QiX7850u9CB29wj | 状态: 429 | 响应时间: 0.218s | 大小: 266B [22/500] https://www.nekopara.uk/mczKcopcmVCSgO0 | 状态: 429 | 响应时间: 0.223s | 大小: 266B [23/500] https://www.nekopara.uk/zbodIK0sYxhY4Yy | 状态: 404 | 响应时间: 0.300s | 大小: 29335B [24/500] https://www.nekopara.uk/B5KkwqUZLWra8Ec | 状态: 429 | 响应时间: 0.235s | 大小: 266B [25/500] https://www.nekopara.uk/uy2IuH3f8gD9hfP | 状态: 429 | 响应时间: 0.210s | 大小: 266B [26/500] https://www.nekopara.uk/lpZdcdLIUEifinU | 状态: 404 | 响应时间: 0.297s | 大小: 29335B可以很明显的发现，每隔1秒就会有一个绕过防护的404请求返回，持续消耗服务器资源。现在只用了Nginx自带的限流功能，能做到的也就这么多了。虽然对于单个恶意IP，成功请求一次404消耗的资源极少，服务器压根就没动静；但是，如果是成百上千个恶意IP进行高强度请求，累计负载将变得相当可观！于是，我想要自己写防御规则，去更加智能的进行限制和防御。进阶：使用自定义规则进行防护这部分我们需要安装Nginx的Lua拓展，用Lua来实现防御的逻辑。apk add nginx-mod-http-lua说点题外话，在尝试进阶方案的时候，我终于深深感受到了国内外大语言模型的技术能力差距。我请教了通义千问，换了几种表述，并且把现有网站配置文件，已经安装的相关软件包都告诉他了，但是它生成的解决方案代码全部报错。试了五次没有一次成功。后面直接找ChatGPT，一次就秒了！看来国内大语言模型还是有待提高啊。实现思路AI只能为你写代码，但是具体的程序逻辑这个最好还是要根据自己博客运营的现实情况自己想防护策略，确保精准打击恶意访问，但是不影响正常访客。我设想的封禁策略是这样的：对于动态响应的请求，这部分一般是博客的页面返回的html，设置限流为单个IP每秒一个请求，对于个人博客来说这已经足以满足正常的访客需求了。并且允许进行两个突发请求，也就是在第一次的一秒内，允许三个请求，来应对第一次页面加载的情况。对于静态文件请求，这部分可以宽松一点，毕竟我实际上看完整加载一次网站静态资源数量会有大约50个，并且静态资源对服务器性能的消耗极少，主要是带宽的消耗。而对于带宽的话在之前的博客已经通过防火墙限制了单个IP能够发起的并发连接数，再加上文件小，基本上不太拉得起来带宽。所以考虑是适当放宽，额外允许50个突发请求，也就是第一秒内允许100个突发请求，足以满足一般个人博客对静态资源的需求。对于随机图片的API，因为后端PHP逻辑简单，代码量少，即使被较高并发轮询也不会消耗太多资源。且一般情况下访问主页，最极端的情况下，也就是文章封面全部是随机封面，且访客不停地往下拉，每秒10个查询请求已经足够，再加上突发再给20个请求，足以应付首页往下拉可以看到20个封面的情况了。剩下的页面需要点加载更多，这就已经需要数秒的时间，此时策略的令牌桶已经补充了10个令牌，足以满足下一步浏览产生的请求。特别的，为了防止前面提到的即使限制了请求速率，持续不断的恶意请求仍然会每秒产生一个404页面消耗资源，我还做出了进一步的限制策略：从访问开始，就会以10秒为单位作为时间窗口观察请求的数量和结果。如果IP请求在这10秒内请求的结果中429错误占到5次及以上，则进行为期10秒的临时封禁，期间任何请求均返回429错误。在封禁的10秒内，如果请求数量仍达到3次及以上，则延长10秒封禁时间，并在新的封禁时间内继续计数，如果请求数量仍达到3次及以上，继续延长封禁时间，以此类推。当最新的10秒观察窗口期内，请求数量降低到2个及以下，则解除封禁，可以继续正常访问。这样的策略，给了正常访客一定的折腾空间，只要封禁前访问请求次数不超限制，基本上就是正常浏览。但是对于恶意请求，一旦触发了封禁机制，就会被一直封禁。因为解除封禁的规则比判定封禁更严格。实现判定封禁的配置文件基于这个逻辑，我向ChatGPT详细描述了我的需求和实现思路，并且附上了现有的Nginx配置文件，以及告诉他目前已经安装的相关软件包和正在使用的发行版版本，询问是否需要补充安装软件包。这样详细的提示词给到ChatGPT，它直接轻松秒杀：修改后的nginx.conf：# /etc/nginx/nginx.conf user nginx; # Set number of worker processes automatically based on number of CPU cores. worker_processes auto; # Enables the use of JIT for regular expressions to speed-up their processing. pcre_jit on; # Configures default error logger. error_log /var/log/nginx/error.log warn; # Includes files with directives to load dynamic modules. include /etc/nginx/modules/*.conf; # Include files with config snippets into the root context. include /etc/nginx/conf.d/*.conf; events { # The maximum number of simultaneous connections that can be opened by # a worker process. worker_connections 1024; } http { lua_shared_dict abuse 10m; lua_shared_dict tb 10m; # 静态资源限流配置（宽松） limit_req_zone $binary_remote_addr zone=static_limit:10m rate=50r/s; # 定义 FastCGI 缓存路径 fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=my_fastcgi_cache:16m inactive=60m; # 定义缓存键，通常包含请求方法、主机名和 URI fastcgi_cache_key "$scheme$request_method$host$request_uri"; # Includes mapping of file name extensions to MIME types of responses # and defines the default type. include /etc/nginx/mime.types; default_type application/octet-stream; # Name servers used to resolve names of upstream servers into addresses. # It's also needed when using tcpsocket and udpsocket in Lua modules. #resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001]; # Don't tell nginx version to the clients. Default is 'on'. server_tokens off; # Specifies the maximum accepted body size of a client request, as # indicated by the request header Content-Length. If the stated content # length is greater than this size, then the client receives the HTTP # error code 413. Set to 0 to disable. Default is '1m'. client_max_body_size 1m; # Sendfile copies data between one FD and other from within the kernel, # which is more efficient than read() + write(). Default is off. sendfile on; # Causes nginx to attempt to send its HTTP response head in one packet, # instead of using partial frames. Default is 'off'. tcp_nopush on; # Enables the specified protocols. Default is TLSv1 TLSv1.1 TLSv1.2. # TIP: If you're not obligated to support ancient clients, remove TLSv1.1. ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; # Path of the file with Diffie-Hellman parameters for EDH ciphers. # TIP: Generate with: `openssl dhparam -out /etc/ssl/nginx/dh2048.pem 2048` #ssl_dhparam /etc/ssl/nginx/dh2048.pem; # Specifies that our cipher suits should be preferred over client ciphers. # Default is 'off'. ssl_prefer_server_ciphers on; # Enables a shared SSL cache with size that can hold around 8000 sessions. # Default is 'none'. ssl_session_cache shared:SSL:2m; # Specifies a time during which a client may reuse the session parameters. # Default is '5m'. ssl_session_timeout 1h; # Disable TLS session tickets (they are insecure). Default is 'on'. ssl_session_tickets off; # Enable gzipping of responses. #gzip on; # Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'. gzip_vary on; # Helper variable for proxying websockets. map $http_upgrade $connection_upgrade { default upgrade; '' close; } # Specifies the main log format. log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; # Sets the path, format, and configuration for a buffered log write. access_log /var/log/nginx/access.log main; # Includes virtual hosts configs. include /etc/nginx/http.d/*.conf; }这里新增了两个共享内存块abuse和tb用于存储相关的请求信息。修改后的nekopara.conf:server { listen 443 ssl; server_name nekopara.uk; ssl_certificate /data/certs/nekopara_uk.pem; ssl_certificate_key /data/certs/nekopara_uk.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384'; location / { return 302 https://www.nekopara.uk$request_uri; } } # Typecho HTTPS服务器块 server { listen 443 ssl; #proxy_protocol; server_name www.nekopara.uk; set_real_ip_from 192.168.1.255; real_ip_header X-Forwarded-For; # 设置最大上传文件大小为 32M client_max_body_size 32M; # SSL证书和密钥路径 ssl_certificate /data/certs/nekopara_uk.pem; ssl_certificate_key /data/certs/nekopara_uk.key; # SSL设置（可选但推荐用于安全） ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # 网站根目录 root /data/typecho; index index.html index.php; # 1. 静态资源限流（宽松，50r/s） location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|eot|svg|mp4|webp|zip|rar|7z|psb)$ { limit_req zone=static_limit burst=50 nodelay; try_files $uri $uri/ =404; add_header Cache-Control "public"; } # PHP处理，并启用FastCGI缓存 location ~ \.php$ { fastcgi_ignore_headers Cache-Control Expires Set-Cookie; try_files $uri =404; fastcgi_pass unix:/var/run/php-fpm82/php8.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; # 启用 FastCGI 缓存 fastcgi_cache my_fastcgi_cache; fastcgi_cache_valid 200 301 302 404 10s; # 设置缓存的有效时间 fastcgi_cache_methods GET HEAD; # 只缓存 GET 和 HEAD 请求 # 不缓存 POST 请求和其他非幂等性请求 set $skip_cache 0; if ($request_method = POST) { set $skip_cache 1; } if ($query_string != "") { set $skip_cache 1; } # 如果存在特定会话 cookie，则不缓存 if ($http_cookie ~* "PHPSESSID|__typecho_uid|__typecho_authCode") { set $skip_cache 1; } fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; # 添加头部信息显示缓存状态 add_header X-FastCGI-Cache $upstream_cache_status; # 动态页面封禁逻辑规则 access_by_lua_block { local abuse = ngx.shared.abuse -- 计数/封禁存储 local tb = ngx.shared.tb -- token-bucket 存储 local ip = ngx.var.remote_addr or "unknown" local uri = ngx.var.uri or "/" -- 参数（可按需调整） local RATE = 1 -- 平均速率：1 r/s local BURST = 2 -- burst 值（与 nginx 的 burst 对应） local CAPACITY = BURST + 1 -- token 桶容量 (burst + 1) local TRIGGER_WINDOW = 10 -- 触发封禁的观察窗口（秒） local TRIGGER_COUNT = 5 -- 在观察窗口内达到此次数则封禁 local BAN_TTL = 10 -- 初始封禁时长（秒） local BAN_EXT_HIT_WINDOW = 10 -- 封禁期间统计窗口（秒） local BAN_EXT_HIT_COUNT = 3 -- 封禁期间若在最近 BAN_EXT_HIT_WINDOW 秒内请求次数 >= 3 则延长 -- 宽松策略（仅用于特定豁免路径） local PIC_RATE = 10 -- 更高速率，按需调整 local PIC_BURST = 20 -- 更大突发，按需调整 local PIC_CAPACITY = PIC_BURST + 1 -- 哪些 URI 被豁免为“宽松模式”（只对精确入口豁免） local function is_pic_exempt(u) if not u then return false end if u == "/pic_api" or u == "/pic_api/" or u == "/pic_api/index.php" then return true end return false end local pic_exempt = is_pic_exempt(uri) -- helper keys local ban_key = "ban:" .. ip local lim_key = "lim:" .. ip -- 5s 触发计数 key local banhit_key = "banhit:" .. ip -- 封禁期内计数 key -- 如果不是 pic_exempt，先检查封禁（pic_exempt 跳过 ban 检查） if not pic_exempt then local is_banned = abuse:get(ban_key) if is_banned then -- 记录封禁期内的请求计数以便延长封禁 local ok, err = abuse:incr(banhit_key, 1, 0, BAN_EXT_HIT_WINDOW) if not ok then abuse:set(banhit_key, 1, BAN_EXT_HIT_WINDOW) ok = 1 end if ok >= BAN_EXT_HIT_COUNT then abuse:set(ban_key, 1, BAN_TTL) abuse:set(banhit_key, 0, BAN_EXT_HIT_WINDOW) end return ngx.exit(503) end end -- token-bucket 通用实现（根据是否豁免使用不同参数） local now = ngx.now() local tokens_key = "tokens:" .. ip local last_key = "last:" .. ip local tokens = tb:get(tokens_key) local last = tb:get(last_key) if tokens == nil then if pic_exempt then tokens = PIC_CAPACITY else tokens = CAPACITY end end if last == nil then last = now end local delta = now - tonumber(last) if delta < 0 then delta = 0 end local rate = (pic_exempt and PIC_RATE) or RATE local capacity = (pic_exempt and PIC_CAPACITY) or CAPACITY local new_tokens = tonumber(tokens) + delta * rate if new_tokens > capacity then new_tokens = capacity end if new_tokens >= 1 then -- 允许请求，消耗 1 token new_tokens = new_tokens - 1 tb:set(tokens_key, new_tokens, 60) tb:set(last_key, now, 60) return else -- 被限流：如果是 pic_exempt，则我们不走封禁逻辑，只返回 503（表现为 429 页面） if pic_exempt then -- 可选：可以不计入 abuse 触发计数，避免误封 return ngx.exit(503) end -- 严格路径：记录触发计数并在达到阈值时封禁 local cnt, err = abuse:incr(lim_key, 1, 0, TRIGGER_WINDOW) if not cnt then abuse:set(lim_key, 1, TRIGGER_WINDOW) cnt = 1 end if cnt >= TRIGGER_COUNT then abuse:set(ban_key, 1, BAN_TTL) abuse:delete(lim_key) end return ngx.exit(503) end } } # 3. 默认路由（用于Typecho伪静态） location / { try_files $uri $uri/ /index.php?$args; autoindex off; } #自定义503为429 Too Many Requests error_page 503 =429 /429.html; location = /429.html { internal; root /etc/nginx/error_pages; #添加响应头告诉爬虫重试时间间隔 add_header Retry-After 10 always; } # Deny access to hidden files location ~ /\.ht { deny all; } }主要是新增了access_by_lua_block这个Lua脚本块来实现封禁逻辑，注释已经很清楚了，需要的进行按需修改即可。（虽然我不懂Lua，问LLM就完了）测试效果同样的，我们来尝试使用之前的测试脚本进行测试，查看返回的结果：[vanilla@NekoWorks ~]$ python3 random_request_test.py www.nekopara.uk --interval 50 --count 500 开始随机URL请求测试 (总请求: 500, 间隔: 50ms, 超时: 5s) 目标域名: https://www.nekopara.uk 输出文件: random_request_test.csv [1/500] https://www.nekopara.uk/tsq5L3DXmTP3pgt | 状态: 404 | 响应时间: 0.430s | 大小: 29758B [2/500] https://www.nekopara.uk/0imQYTBUKXMaWEe | 状态: 404 | 响应时间: 0.431s | 大小: 29758B [3/500] https://www.nekopara.uk/mb2uMeZihKa6Qx3 | 状态: 404 | 响应时间: 0.369s | 大小: 29758B [4/500] https://www.nekopara.uk/ik6TaHfby3dVnO6 | 状态: 404 | 响应时间: 0.446s | 大小: 29758B [5/500] https://www.nekopara.uk/kOaNyipoAoJp7oz | 状态: 429 | 响应时间: 0.222s | 大小: 266B [6/500] https://www.nekopara.uk/SQvv9uNbBOukoCM | 状态: 404 | 响应时间: 0.302s | 大小: 29758B [7/500] https://www.nekopara.uk/3N0aRXMhpnbn1gP | 状态: 429 | 响应时间: 0.310s | 大小: 266B [8/500] https://www.nekopara.uk/d0G1xWPmmVNqbHw | 状态: 429 | 响应时间: 0.224s | 大小: 266B [9/500] https://www.nekopara.uk/o2IpKDOdVMCD9r2 | 状态: 404 | 响应时间: 0.435s | 大小: 29758B [10/500] https://www.nekopara.uk/EdpSSsWhxNtcQ0E | 状态: 429 | 响应时间: 0.291s | 大小: 266B [11/500] https://www.nekopara.uk/V1GaQNNrt9dz4YP | 状态: 429 | 响应时间: 0.311s | 大小: 266B [12/500] https://www.nekopara.uk/qQiIxZrIBEAtbeB | 状态: 429 | 响应时间: 0.219s | 大小: 266B [13/500] https://www.nekopara.uk/c4jCFSFD5bgFwrw | 状态: 429 | 响应时间: 0.241s | 大小: 266B [14/500] https://www.nekopara.uk/ukHBaBKx9Dn1uIY | 状态: 429 | 响应时间: 0.315s | 大小: 266B [15/500] https://www.nekopara.uk/KcvHSqSHoamkTb9 | 状态: 429 | 响应时间: 0.277s | 大小: 266B [16/500] https://www.nekopara.uk/VQPfGcKjPSSx1mn | 状态: 429 | 响应时间: 0.279s | 大小: 266B [17/500] https://www.nekopara.uk/WCXISBiDEuuEze5 | 状态: 429 | 响应时间: 0.321s | 大小: 266B [18/500] https://www.nekopara.uk/LxsC9KgFCGojRVQ | 状态: 429 | 响应时间: 0.228s | 大小: 266B [19/500] https://www.nekopara.uk/aXwLD0nIDKrEw6z | 状态: 429 | 响应时间: 0.220s | 大小: 266B [20/500] https://www.nekopara.uk/mn5SgwFI1fB3pPL | 状态: 429 | 响应时间: 0.217s | 大小: 266B [21/500] https://www.nekopara.uk/cDtZOUEUDBeIam6 | 状态: 429 | 响应时间: 0.218s | 大小: 266B [22/500] https://www.nekopara.uk/xdikUSlbaqhkQBV | 状态: 429 | 响应时间: 0.227s | 大小: 266B [23/500] https://www.nekopara.uk/fYmwQCdwuUEvb6Q | 状态: 429 | 响应时间: 0.216s | 大小: 266B [24/500] https://www.nekopara.uk/bI0npEBugVcHenG | 状态: 429 | 响应时间: 0.217s | 大小: 266B [25/500] https://www.nekopara.uk/ecUcKf1We2GjNZE | 状态: 429 | 响应时间: 0.218s | 大小: 266B可以看到，一旦触发封禁，后面快速请求的结果都是429，非常的成功！总结与反思经过一番测试和调试，我可以很自信地说：我的博客已经能够有效抵御常见的中小规模 CC 攻击。对于一个纯粹的个人博客而言，这样的防护能力，我认为已经相当不错了。但你要问——这套方案能不能完全无视所有 CC 攻击？ 我必须很遗憾且负责任地告诉你：不行！目前的防御策略，对单 IP 高频、多 IP 高频这类“显眼包”式攻击有不错的拦截效果。但如果是海量 IP 发起的低频、慢速、行为模拟正常的 CC 攻击，那我的服务器大概率会当场暴毙。 毕竟，这类请求和真实用户的访问行为太像了，总不能为了防攻击，把正常访客也一并拒之门外吧 😅所以，如果你真的想用 CC 打垮我的网站，那我也不妨“贴心”地给你“指明方向”，顺便帮你算笔账：🎯 攻击指南（兼成本核算）IP 资源：你需要一个至少 1000+ 独立 IP 的僵尸网络。市面上靠谱的 DDoS 僵尸网络租赁服务，日租成本约 50–100 美元。攻击频率：每个 IP 每秒发起一次请求，或更低频但持续不断，还得随机访问不同 URL，避免触发我的频率封禁规则——别让我在日志里看出破绽。持续时间：想让我网站瘫痪一整天？那你得连续攻击 24 小时不停歇。这意味着：僵尸网络租赁：$100技术维护与调度（别让 IP 被批量封，算是DLC，可选项）：$200+总体成本保守估计：$100–500 美元/天而我这边呢？ 服务器是白嫖的华为云资源，带宽和计算资源基本零成本。你砸几百美元攻击我一天，我关掉网站喝杯咖啡，等你钱烧完，我秒恢复上线。🤔 所以，请问：你真的愿意花大几百美元，只为让一个不盈利、没广告、日均访问量不到百人的个人博客下线几天？这笔钱，够买几十杯星巴克，够报一门编程课，够买台树莓派折腾半年。 相比之下，把它砸在一个“打不赢也赚不到”的小博客上，真的值得吗？因此，对于那些脚本小子、网络喷子、或者闲着无聊想搞点事情的小学生来说——这套防护机制已经足够让你“出师未捷身先死”。而如果你真愿意花大价钱，动用专业资源对我持续攻击…… 那我只能说：承蒙厚爱，荣幸之至 😊
优化网站访问并发高占用问题也是成功让CPU占用从100%降低到了10%。前言之前尝试过高并发压力测试自己的网站，发现打到128并发基本上就趴窝了。后面与友链的一个站长交流，得知可以通过缓存的方式解决这种高并发处理问题。因为如果不设置缓存的话，即使是同样的请求，也会让php进行反复处理，消耗大量资源。所以，我想尝试一下能不能改善一下这个情况。实验环境准备为了保证我网站能在我进行实验期间正常运行，我决定对我网站数据进行克隆，迁移到我云服务器里面性能最孱弱的那个进行测试。那就是，白嫖的华为云：母鸡CPU: Intel(R) Xeon(R) Gold 6348 (2) @ 2.60 GHz双核CPU512MB内存20G硬盘然后是软件环境，操作系统为Alpine Linux 3.20然后我按照前面我那片迁移博客的方法，把博客迁移了过去。原始表现迁移过程就进行省略了，和前面的博客方法大体相同。我测试下来，32并发就是这玩意的极限了，CPU彻底吃满。再往上加线程已经没有意义了，因为已经寄了。可以看出来，云服务器相比于实体鸡性能差别还是很大的。优化改用Unix Socket进行PHP对接首先是php对接的问题，默认Alpine使用的是TCP网络协议进行本地回环，开销较大。而使用Unix Socket则可以解决这个问题，他具有低延迟和资源占用少的优点。特征TCP 端口Unix Socket通信范围支持远程通信仅限于同一主机内的进程间通信性能较高开销，适合长距离通信更高效，尤其在本地通信中表现出色安全性需要额外的安全措施，如防火墙规则依赖于操作系统权限控制，天然更安全配置复杂度需要设置IP地址和端口号只需指定文件路径为了实现这个目标，我们需要对PHP的配置文件和Nginx的配置文件进行更改。1. 修改 PHP-FPM 配置文件首先，我们需要编辑 PHP-FPM 的池配置文件 /etc/php82/php-fpm.d/www.conf，将其 listen 设置更改为一个 Unix socket，并设置适当的权限。[www] ; 更改 listen 为 Unix socket listen = /var/run/php-fpm82/php8.2-fpm.sock ; 设置 socket 文件的所有者和组 listen.owner = nginx listen.group = nginx listen.mode = 0660确保 /var/run/php-fpm82/ 目录存在并且有正确的权限：sudo mkdir -p /var/run/php-fpm82/ sudo chown root:nginx /var/run/php-fpm82/ sudo chmod 755 /var/run/php-fpm82/2. 更新 Nginx 配置文件然后，编辑你的 Nginx 站点配置文件，例如 /etc/nginx/http.d/nekopara.conf，将 fastcgi_pass 指令更新为指向新的 Unix socket：location ~ \.php$ { try_files $uri =404; fastcgi_pass unix:/var/run/php-fpm82/php8.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }3. 重启服务保存所有更改后，重启 PHP-FPM 和 Nginx 服务以使新配置生效。# 重启 PHP-FPM rc-service php-fpm82 restart # 重新加载 Nginx rc-service nginx reload修改完成后，再进行测试，似乎没有太大改善，还是CPU爆炸。那就尝试一下设置缓存了。为Nginx配置缓存规则1. 定义 FastCGI 缓存区首先，在 Nginx 的主配置文件 /etc/nginx/nginx.conf 添加一个 fastcgi_cache_path 指令来定义缓存存储的位置和参数。http { # 其他配置... # 定义 FastCGI 缓存路径 fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=my_fastcgi_cache:16m inactive=60m; # 定义缓存键，通常包含请求方法、主机名和 URI fastcgi_cache_key "$scheme$request_method$host$request_uri"; }在主配置文件加上以上这段即可。确保 /var/cache/nginx/ 目录存在并且有适当的权限：sudo mkdir -p /var/cache/nginx/fastcgi sudo chown nginx:nginx /var/cache/nginx/fastcgi sudo chmod 755 /var/cache/nginx/fastcgi2. 修改站点配置以启用 FastCGI 缓存接下来，在你的站点配置文件，例如 /etc/nginx/http.d/nekopara.conf 中修改处理 PHP 请求的 location ~ \.php$ 块，加入 FastCGI 缓存相关指令。以下是我的示例配置文件：server { listen 4443 ssl proxy_protocol; server_name www.nekopara.uk; #确保后端正确跳转而不带上端口号 set_real_ip_from 0.0.0.0; real_ip_header proxy_protocol; set $real_host $http_x_forwarded_host; if ($http_x_forwarded_host = '') { set $real_host $host; } # 添加这一行，确保生成的重定向不带后端端口 port_in_redirect off; # 设置最大上传文件大小为 8M client_max_body_size 8M; # SSL证书和密钥路径 ssl_certificate /data/certs/nekopara_uk.pem; ssl_certificate_key /data/certs/nekopara_uk.key; # SSL设置（可选但推荐用于安全） ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # 网站根目录 root /data/typecho; index index.html index.php; # Serve static files directly location / { try_files $uri $uri/ /index.php?$args; autoindex off; } # PHP处理，并启用FastCGI缓存 location ~ \.php$ { try_files $uri =404; fastcgi_pass unix:/var/run/php-fpm82/php8.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; # 启用 FastCGI 缓存 fastcgi_cache my_fastcgi_cache; fastcgi_cache_valid 200 301 302 404 1m; # 设置缓存的有效时间 fastcgi_cache_methods GET HEAD; # 只缓存 GET 和 HEAD 请求 # 不缓存 POST 请求和其他非幂等性请求 set $skip_cache 0; if ($request_method = POST) { set $skip_cache 1; } if ($query_string != "") { set $skip_cache 1; } if ($http_cookie ~* "PHPSESSID|_sf|your-session-cookie") { # 如果存在特定会话 cookie，则不缓存 set $skip_cache 1; } fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; # 添加头部信息显示缓存状态 #add_header X-FastCGI-Cache $upstream_cache_status; } # Deny access to hidden files location ~ /\.ht { deny all; } }3. 测试并应用配置保存所有更改后，测试 Nginx 配置文件是否正确：nginx -t如果一切正常，重新加载 Nginx 使新配置生效：rc-service nginx reload此时在进行并发压力测试，感觉就是在挠痒痒：即使上压力到256线程，也就那样：Nginx FastCGI 缓存配置参数解释1. fastcgi_cache_pathfastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=my_fastcgi_cache:16m inactive=60m;/var/cache/nginx/fastcgi：指定缓存文件存储的路径。levels=1:2：定义缓存目录结构为两级子目录，有助于分散文件，避免单个目录下文件过多导致性能问题。keys_zone=my_fastcgi_cache:16m：创建一个名为 my_fastcgi_cache 的共享内存区域，大小为 16MB。这个区域用于存储缓存键（key）和元数据（如过期时间）。16MB 可以存储大约 80,000 个缓存项。inactive=60m：设置缓存条目在多长时间内没有被访问就会被认为是不活跃的并从缓存中移除。这里设置为 60 分钟。2. fastcgi_cache_keyfastcgi_cache_key "$scheme$request_method$host$request_uri";这个指令定义了用来生成缓存键的变量组合。每个唯一的 URL 请求（包括协议、请求方法、主机名和 URI）都会生成一个唯一的缓存键，确保不同的请求不会互相干扰。3. fastcgi_cache_validfastcgi_cache_valid 200 301 302 404 1m;指定哪些 HTTP 状态码应该被缓存以及它们的有效期。例如，这里表示状态码 200 (OK)、301 (永久重定向) 和 302 (临时重定向) 404 (未找到页面) 的响应会被缓存 1 分钟。4. fastcgi_cache_methodsfastcgi_cache_methods GET HEAD;指定只缓存特定的 HTTP 请求方法，这里是 GET 和 HEAD 请求。POST 请求通常包含表单提交等操作，不应该被缓存。5. $skip_cache 和相关条件set $skip_cache 0; if ($request_method = POST) { set $skip_cache 1; } if ($query_string != "") { set $skip_cache 1; } if ($http_cookie ~* "PHPSESSID|_sf|your-session-cookie") { set $skip_cache 1; } fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache;$skip_cache：这是一个自定义变量，用于标记是否跳过缓存。初始值设为 0，意味着默认情况下启用缓存。条件判断：通过一系列 if 语句检查请求是否是 POST 请求、是否有查询字符串或者是否存在特定的会话 cookie。如果任意条件满足，则将 $skip_cache 设置为 1，即禁用缓存。fastcgi_cache_bypass 和 fastcgi_no_cache：这两个指令分别控制是否绕过缓存（即直接发送请求给后端）和是否不让缓存当前请求的结果。当 $skip_cache 为 1 时，两者都生效，确保动态或个性化内容不被缓存。6. add_header X-FastCGI-Cacheadd_header X-FastCGI-Cache $upstream_cache_status;向 HTTP 响应头添加 X-FastCGI-Cache 字段，显示缓存的状态（如 HIT, MISS 等），便于调试和监控。这部分是可以不要的。理解了这些缓存参数的意义，你就可以根据需要自己调节了。博客状态我发布这篇文章的时候，已经完成了这些优化并且把博客迁移上云了。毕竟白送的服务器，不用白不用。虽然云服务器隐私安全一直是一个问题，云服务器提供商能不能靠得住这点存疑。但是，如果只是博客这种，反正都是要公开的内容，上云又何尝不可？当然，家里云还是会保留的，毕竟它的主要任务是NAS，让我随时随地可以访问我的文件，博客之前只是想在上面顺带弄着玩的玩具。随着博客访问量逐渐大了起来，迁移上云给家里云减负何尝不是一个更好的选择？这个云服务器已经白嫖续费到2029年了，所以这个博客至少也能在上面运行差不多五年时间，已经比我购买的域名时间还长了。迁移吧！定期把内容备份到家里云就是了。PS:不知道你有没有注意到网站底部的服务器运行时间，从200多天变成了80多天？
让Nginx反向代理Stable Diffusion并鉴权访问说在前面的话......我希望能在公网直接访问我的Stable Diffusion WebUI，虽然说可以直接通过端口隐射或者内网穿透的方式引出来，但是由于WebUI本身是不带身份验证的，万一被某些无聊的人乱搞就不好了。于是，我就想到用Nginx做反向代理，并且加入基本身份验证。也就是你打开网站的时候浏览器会弹出一个窗口要你填写身份信息进行认证。这样做的话，相对而言比较省事，不需要去增加功能和代码。所需软件/工具Stable Diffusion WebUIFRP内网穿透工具NginxHAproxy(如果有多个服务需要同时访问的话）Stable Diffusion配置允许外部访问在Stable Diffusion的目录找到webui-user.sh文件，取消注释export COMMANDLINE_ARGS=""，加上--listen。例如：export COMMANDLINE_ARGS="--xformers --enable-insecure-extension-access --listen"然后按照常规方式启动Stable Diffusion:./webui.sh配置FRP内网穿透下载FRP后，解压，frps是服务端，配置文件为frps.toml，frpc是客户端，配置文件为frpc.toml。服务端配置文件如下：bindPort = 7000 auth.token = "your_passwd"bindPort是服务器监听端口。auth.token是设置的连接密码。客户端配置文件如下：serverAddr = "172.16.0.1" serverPort = 7000 auth.token = "your_passwd" proxies name = "Stable_Diffusion" type = "tcp" localIP = "127.0.0.1" localPort = 7860 remotePort = 7860serverAddr是服务器的IP地址。serverPort是服务器设置的端口，需要和服务器保持一致。auth.token是自定义的密码，需要和服务器保持一致。localIP是要访问服务的本地IP。localPort是本地服务的端口。remotePort是把本地服务暴露出去在外面访问的端口。所有相关文件位于同一目录，设置好后，服务端运行frps:./frps -c ./frps.toml客户端运行frpc：./frpc -c ./frpc.toml注意，这一步可以防火墙放行相关端口看看是否成功，但是弄完之后一定要关掉这个端口！配置Nginx反向代理配置基础身份验证为了添加基础身份验证，需要生成一个密码文件。使用htpasswd工具来创建这个文件。如果没有安装htpasswd，可以通过安装 apache2-utils包来获得：apt install apache2-utils然后，创建一个密码文件，并添加一个用户：sudo htpasswd -c /etc/nginx/.htpasswd user配置Web服务器配置文件Web服务器配置文件内容如下：server { server_name yourdomain.example.com; listen 10086 ssl; listen [::]:10086 ssl; ssl_certificate /path/to/your/cert.pem; ssl_certificate_key /path/to/your/certkey.key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; auth_basic "Restricted Area"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_http_version 1.1; proxy_cache_bypass $http_upgrade; proxy_set_header Upgrade $http_upgrade; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Connection "upgrade"; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; proxy_set_header Host $host; client_max_body_size 20m; proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; location / { proxy_pass http://127.0.0.1:7860/; } location ~ /\.(?!well-known) { deny all; } location = /favicon.ico { log_not_found off; access_log off; } location = /robots.txt { log_not_found off; access_log off; } }以上是模板，要做以下修改：yourdomain.example.com填你自己的域名。/path/to/your/cert.pem填你服务器证书的位置。/path/to/your/certkey.key填你服务器证书私钥路径。保存为你自己想要的名称.conf并且放到/etc/nginx/http.d/，例如/etc/nginx/http.d/neko.conf。重启Nginxsystemctl restart nginx如果你按照我这样配置完成后，理论上访问https://yourdomain.example.com:10086并且输入用户和密码验证身份后，即可看到你的Stable Diffusion WebUI了。哦对了，别忘了添加域名的DNS解析记录。配置HAproxy实现端口复用（可选）如果你的服务器像我一样运行了多个不同的服务，并且你有域名的话，就可以用这个方法实现端口复用。也就是都走443端口进行访问。这里我贴出来我的HAproxy配置文件：#--------------------------------------------------------------------- # 全局设定部分 #--------------------------------------------------------------------- global defaults log global option dontlognull option http-server-close option redispatch retries 3 timeout http-request 10s timeout queue 1m timeout connect 10s timeout client 1m timeout server 1m timeout http-keep-alive 10s timeout check 10s maxconn 3000 #--------------------------------------------------------------------- # 定义DNS解析器 #--------------------------------------------------------------------- resolvers dns1 nameserver internal-dns 1.1.1.1:53 resolve_retries 3 timeout resolve 10s timeout retry 10s hold other 30s hold refused 30s hold nx 30s hold timeout 30s hold valid 10s hold obsolete 30s #--------------------------------------------------------------------- # 接管HTTP流量实现302跳转HTTPS #--------------------------------------------------------------------- frontend http bind :80 #accept-proxy mode http redirect scheme https code 302 if { hdr(Host) -i www.nekopara.uk } !{ ssl_fc } redirect scheme https code 302 if { hdr(Host) -i sd.nekopara.uk } !{ ssl_fc } #--------------------------------------------------------------------- # 通过SNI Proxy对HTTPS流量实现四层转发 #--------------------------------------------------------------------- frontend https_proxy mode tcp bind :443 tcp-request inspect-delay 5s tcp-request content accept if { req_ssl_hello_type 1 } acl is_www.nekopara.uk req_ssl_sni -i www.nekopara.uk use_backend www.nekopara.uk if is_www.nekopara.uk acl is_sd.nekopara.uk req_ssl_sni -i sd.nekopara.uk use_backend sd.nekopara.uk if is_sd.nekopara.uk backend www.nekopara.uk mode tcp balance roundrobin option tcp-check server ddns-1 org.nekopara.uk:3939 resolvers dns1 resolve-prefer ipv4 check send-proxy server backup-server 127.0.0.1:8443 backup backend sd.nekopara.uk mode tcp balance roundrobin option tcp-check server backup-server 127.0.0.1:10086 其中，www.nekopara.uk是我博客的主域名，而sd.nekopara.uk则是我自己要运行的Stable Diffusion WebUI服务。这样一设置，我就可以让我的好几个不同的Web服务一起在443端口访问了，也就是不需要加端口了。要注意一点，这里的端口号是要填写Nginx反代出来的端口号，也就是10086，如果填FRP穿透出来的端口号，两个结果，要么无法访问，要么所有人都可以访问。一定要注意！最终效果访问时会弹出身份认证：不认证不给访问：认证成功后进入界面：Nice!