慣性聚合 関心のあるブログ、ニュース、テクノロジーを効率的に追跡
原文を読む 慣性聚合で開く

おすすめ購読元

V
V2EX
博客园 - 叶小钗
Y
Y Combinator Blog
大猫的无限游戏
大猫的无限游戏
博客园 - 【当耐特】
酷 壳 – CoolShell
酷 壳 – CoolShell
D
Docker
WordPress大学
WordPress大学
Blog — PlanetScale
Blog — PlanetScale
博客园 - Franky
G
Google Developers Blog
爱范儿
爱范儿
Google DeepMind News
Google DeepMind News
Stack Overflow Blog
Stack Overflow Blog
云风的 BLOG
云风的 BLOG
Engineering at Meta
Engineering at Meta
aimingoo的专栏
aimingoo的专栏
V
Visual Studio Blog
M
MIT News - Artificial intelligence
Hugging Face - Blog
Hugging Face - Blog

蛋蛋之家

SUMIFS和LET函数——让 Excel 自己动起来 我用这个开源项目,把 AI 绘画提示词变成了"代码"——awesome-gpt-image-2 深度体验报告 群晖部署 LX Music Server:浏览器就是你的音乐播放器【详细版】 500道Word题库太痛苦?于是我写了个智能学习平台 别用一个人的奔跑,耗尽两个人的余生——《你凭什么认为我会一直喜欢你呢》 博客评论自动填充新方案:兼容 Vue/React 响应式绑定与跨架构填充逻辑 2026 生产级指南:Halo 2.x + PostgreSQL 自动化部署与性能优化手册 - 蛋蛋之家 从 Typecho 转向 Halo 2.x:全能型 Nginx 架构与 SEO 无损迁移指南 - 蛋蛋之家 Typecho 迁移 Halo 完整教程:数据库视图解决表前缀 + 自动提取封面图 + 去重检测 - 蛋蛋之家 我的 CodeBuddy 装备库:218个技能全公开 + 使用心得 - 蛋蛋之家 为博客添加自定义评论表情 - 蛋蛋之家 - 一枚蛋蛋的自留地 Comment2Bark:Typecho评论推送到Bark插件 又拍云插件 UpyunFile v1.2 更新:修复 Typecho 1.3.0 兼容性问题 - 蛋蛋之家 手把手修复RSS 403:从PHP警告到Cloudflare拦截的全链路排查 - 蛋蛋之家 - 一枚蛋蛋的自留地 网站被镜像怎么办?2026年反镜像攻防实战指南 - 蛋蛋之家 - 一枚蛋蛋的自留地 VLESS + Reality + Vision:2026 极致隐蔽节点搭建全解析 - 蛋蛋之家 给OpenList和Alist加上液态玻璃效果,颜值直接拉满! - 蛋蛋之家 - 一枚蛋蛋的自留地 网页截图瞬间“赛博飞升”?手把手教你根治 Windows HDR 截图发白 - 蛋蛋之家 群晖 NAS 影音终极方案:Docker Rclone + OpenList 完美挂载 Plex 全攻略 一个简洁高效的SVG图标库 - 蛋蛋之家 - 一枚蛋蛋的自留地 告别重复劳动!一键填充评论的神奇书签脚本 - 蛋蛋之家 - 一枚蛋蛋的自留地 解锁思源笔记的隐藏力量:siyuan-patch 深度体验与技术实现 - 蛋蛋之家 - 一枚蛋蛋的自留地 Oracle Cloud 自救指南:旧手机丢失后如何绕过 MFA 重新登录【更新2026年最新政策】 - 蛋蛋之家 本站已加入“萌国ICP备案”联萌!😄 - 蛋蛋之家 - 一枚蛋蛋的自留地 Excel 月报自动化指南:告别重复劳动,让数据主动为你服务 - 蛋蛋之家 - 一枚蛋蛋的自留地 梦呓:为异地女友准备的2周年惊喜礼物 - 蛋蛋之家 - 一枚蛋蛋的自留地 Typecho 多吉云插件优化:更灵活的存储目录配置 - 蛋蛋之家 - 一枚蛋蛋的自留地 甲骨文云硬盘扩容指南:从50G到200G的“免费午餐”怎么吃? ⏰ 时间同步:当你的服务器开始“穿越”时,如何优雅地把它拉回现实 Typecho 又拍云文件管理插件修改版使用指南:新增自定义目录结构功能 华为鸿蒙6.0回退4.2记录 最近小公主情绪不佳 劳动节的家乡景色 烟台山公园一游 群晖搭建ChatGPT Web 推荐脚本:ChatGPT - 提示选择器 Activation Method for JetBrains Products (English version) JetBrains 系列产品激活方法 疫情三年后再进校园 修复searchEngineJump在谷歌搜索页面不生效问题 事情既然发生了,可以是遗憾,不要去后悔。 侄女的试唱会比赛 新一代图片编码格式测试 省科技馆之行 Typora Windows版破解补丁及主题推荐 在Typora中使用PicList上传图片 域名ICP备案之变更网站 Ubuntu 系统如何使用 root 用户登录 中国联通——让一切自由连不通 如何选购适合的口罩?
ハローブログの夜間停止トラブルの調査記録:『幽玄的な停止』から『真の原因が明らかになる』まで
吴蛋蛋 · 2026-05-22 · via 蛋蛋之家

サーバー:Oracle Cloud ARM | アプリケーション:Halo 2.x + PostgreSQL 15 + Nginx | レコード時間:2026.05.21–22


挺玄学的头图,阴间版
非常に神秘的なヘッド画像、地獄版

一、問題の奇妙な点

私のブログ(wuqishi.com)にはとても厄介な問題がある:毎日20:00から23:00まで正確にフリーズするが、同じサーバー上の他のウェブサイトには何の問題もない

サーバーはOracle CloudのARMインスタンスで、4コア24GBメモリ、Docker Composeで動かしている。Halo以外にもTypechoのサイトと純粋な静的ページがいくつかある。昼間はすべてのサイトが非常に速いが、Haloは夜になるとまるで魔術にかかれたかのように——ページがロードして回転し、バックエンドにアクセスできず、時折直接エラーが出るERR_CONNECTION_CLOSED

もっと奇妙なのは、が常にフリーズするのではなく、断続的にフリーズすることです。時には開けることができ、2回押すとフリーズする;時には完全に接続できず、数分後には直る。この「シュレーディンガーのフリーズ」は最も頭を悩ませるものです、なぜなら規則性が全く見つからないからです。

最初はHalo 2.x が Java(Spring Boot)に基づいているのではないかと疑いました。Javaはメモリを食うことで有名なため、TypechoはPHPで使ってすぐに解放されるので、Docker設定を見てJVMのヒープメモリが5G与えられており、G1ガーベッジコレクターも開いており、理論上は問題ないはずでした。

💡 Tip 1:選択的フリーズに遭遇したら、すぐにコードの調整や設定の追加をせずに。

最初のステップは常に問題の範囲を確認することです。。他サーバーの他のサイトは正常 → 全局リソース問題を除外;特定のアプリケーションが遅延 → 問題はおそらくそのアプリケーション自体、そのデータベース、またはそのドメイン専用のトラフィック上にある可能性が高い。


二、調査の最初のステップ:ログを確認し、段階的に掘り下げる

2.1 Nginx error.log の異常

まず Nginx の error.log を確認し、大量のこのようなエラーが表示されることを発見:

2026/05/21 22:14:57 [error] 1234#1234: *45678 recv() failed (104: Connection reset by peer)
2026/05/21 22:14:57 [error] 1234#1234: *45678 upstream prematurely closed connection

それらはすべて同じパスに指している:

/apis/online-user.zyx2012.cn/v1alpha1/online-ws

これは私が Halo にインストールした "オンラインユーザー数統計" プラグインだ。Nginx がリクエストを Halo にフォワードした後、Halo がレスポンスヘッダーを返す前に接続を切断したように見える。さらに恐ろしいことに、これらのエラーはスクリーンに大量表示されるレベルで、1秒間に10本以上発生する。

2.2 dmesg に示された二つの重要なヒント

を同時に確認した際、二つの現象が見られましたdmesg

第一に、Docker コンテナが頻繁に再起動しています

vetha1b2c3d4: entered disabled state
vetha1b2c3d4: left promiscuous mode
docker0: port 2(vetha1b2c3d4) entered disabled state

仮想ネットワークインターフェースが絶えず削除・再作成されています。これは Halo コンテナがクラッシュ → Docker による自動起動 → またクラッシュというサイクルに陥っていることを意味します

。第二に、UFW ファイアウォールが激しくスキャンを遮断しています

[UFW BLOCK] IN=eth0 OUT= MAC=... SRC=185.191.171.12 DST=... DPT=5432
[UFW BLOCK] IN=eth0 OUT= MAC=... SRC=194.165.16.73 DST=... DPT=5432

画面いっぱいに[UFW BLOCK]​が表示され、さまざまな海外 IP アドレスが私のポートをスキャンしています:5432(PostgreSQL)、5985(WinRM)、3389(リモートデスクトップ)…

など、5432?私のデータベースポートが外部に公開されていませんか

💡 Tip 2:dmesg はシステムレベルの問題を特定するための強力なツールです。

多くの人がアプリケーションログだけを見て、カーネルログを見落とします。Docker コンテナの頻繁な再起動、ネットワークインターフェースの状態の変化、OOM killer によるプロセスの殺害など、これらは dmesg​ で一望遠くに見えます。dmesg -T | grep -E "(UFW|veth|docker0)"​ を使って重要な情報を迅速にフィルタリングすることをお勧めします。


三、真の原因一:データベースが公网上で裸に立っています

私は docker-compose.yml を開いたら、驚きました:

services:
  halodb:
    image: postgres:15-alpine
    ports:
      - "5432:5432"

当時、DBeaver でデータベースに接続するのを簡単にするために、直接ホストマシンにマッピングしました。しかし、私は Linux 上の大きな落とし穴を忘れていました:Docker は UFW 防火墙を回避します

3.1 Docker がなぜ UFW を回避できるのか?

ここには多くの人が知らない詳細がある:

UFWは本質的にiptablesをラッピングしており、INPUTチェーンにルールを挿入する。しかし、Dockerはコンテナを起動すると、iptables​DOCKERチェーン(natテーブルとfilterテーブルに属する)に自分のルールを挿入し、の優先度はUFWのINPUTチェーンよりも高い。

具体的には、Dockerは次のようなDNATルールを作成する:

sudo iptables -t nat -L DOCKER -n --line-numbers | grep 5432

DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:5432 to:172.18.0.2:5432

このルールの意味は、すべてのホストマシンの5432ポートへのアクセストラフィックが、Dockerコンテナ内の172.18.0.2:5432にDNATされるということです。UFWのフィルタリングルールの前に起こりましただから UFW は全く役に立たない。

3.2 データベースログの強制解読

データベースのログを見るほど驚くべきことです:

2026-05-21 22:14:57.123 UTC [12345] FATAL: password authentication failed for user "postgres"
2026-05-21 22:14:57.123 UTC [12345] DETAIL: Role "postgres" does not exist.
2026-05-21 22:14:57.456 UTC [12346] FATAL: password authentication failed for user "postgres"
2026-05-21 22:14:57.789 UTC [12347] FATAL: password authentication failed for user "postgres"

ハッカーがデフォルトのユーザー名を使っていますpostgresデータベースを暴力破解しようとしている。Docker Composeでデータベースのユーザー名を変更したから、システムは全く認識していないpostgresこのユーザーなので、毎回解錠するのはFATALレベルエラー。

なぜ夜になると特に重いのですか?

夜間はグローバルスキャナーのピーク時(欧米の昼間の業務時間に相当)で、毎秒数十回の悪意のある接続が発生するため。PostgreSQLのデフォルト設定では、各接続ごとに以下の処理が必要です:

  1. バックエンドプロセスをフォークする(約5-10MBのメモリ使用)

  2. SSLハンドシェイクと認証計算を行う

  3. システムテーブルをクエリしてユーザー名を検証

  4. 失敗した場合にFATALログを記録

これらの不要なリクエストがCPUとI/Oを圧迫する。私のHaloブログがデータベースを検索したい?待ち行列に入ってください。

3.3 修正策

ポートをローカルループバックにバインドする:

services:
  halodb:
    image: postgres:15-alpine
    ports:
      - "127.0.0.1:5432:5432"
    environment:
      - POSTGRES_USER=halo
      - POSTGRES_PASSWORD=your_strong_password
      - POSTGRES_DB=halo

変更後リスタートすると、データベースログが瞬時にクリアになり、FATALエラーが完全に消滅する。

💡 Tip 3:すべての機密サービスのDockerポートマッピングは、永遠に以下のように記述する127.0.0.1:PORT:PORT

は怠って単にPORT:PORTを書かないで、それは公网上の裸で走るのと同じです。UFWはDockerの前では紙の虎です。もし本当に外部からデータベースに接続する必要があるなら、SSHトンネルやWireGuardを使って、直接ポートを公開しないでください。


四、真の犯人2:Nginxのバックエンド接続が長連接をオフにしていない

データベースは静まり返っていますが、ウェブサイトは時折遅延します。ログをさらに確認すると、Nginxのaccess.logに規則があります:ページの読み込みごとに、メインリクエスト以外にも多くの/apis/...​ のAPIリクエストが走っています。これらのリクエストはすべて

proxy_pass http://127.0.0.1:8090;

を通じて行われており、upstreamを経由していません。

問題はここにあります:私はupstream keepaliveを設定していません

4.1 なぜショートコネクションがシステムを弱体化させるのか?

Nginxは毎回リバースプロキシを行うたびに新しいTCPコネクションを新規作成し、使用後は破棄します。夜間に並行処理が高くなると、システム内に数千個のTIME_WAIT状態のコネクションが積み重なります。

ssコマンドで確認できます:

ss -tan state time-wait | wc -l

当時、ピーク時にはこの数値が8000+に達しました。

TIME_WAITはTCP四次ハンドシェイクの正常な状態(主動終了側が2MSL待機し、最後のACKが相手側に受信されることを確実にする)ですが、数が多すぎると:

  • 一時ポートが枯渇します:Linux のデフォルトの一時ポート範囲は 32768–60999 で、約 28000 個です。8000+ の TIME_WAIT は利用可能なポートが急減し、新しいリクエストが接続できなくなり、「回転中」「白い画面」のように現れます。

  • が使用するメモリ:各 TIME_WAIT 接続はカーネル内で約 1–2KB の TCP 制御ブロックを占有し、積み重なることで増加します。

4.2 修正策

:Nginx の設定の上部で upstream を定義します:

upstream halo_backend {
    server 127.0.0.1:8090;
    keepalive 64;
}

その後、すべての proxy_pass http://127.0.0.1:8090proxy_pass http://halo_backend に変更し、対応する location に追加します:

proxy_http_version 1.1;
proxy_set_header Connection "";

これにより、Nginx と Halo の間で 64 つの長い接続が再利用され、頻繁な 3-way handshake が不要になります。

2つの詳細に注意してください:

  1. proxy_http_version 1.1 は必須です、なぜなら HTTP/1.0 はデフォルトで keepalive をサポートしないからです。

  2. proxy_set_header Connection ""​ は、Nginx がクライアントの Connection: close をバックエンドに透過させず、バックエンドが長い接続を主動的に切断するのを防ぐためにあります。

を変更した後、すぐに ss で検証できます:

ss -tan state established '( dport = :8090 or sport = :8090 )' | wc -l

接続数は以前の数百個から安定した約64個に急上昇し、それ以上増加しません。TIME_WAIT の数も正常レベル(数十から百個)に低下しました。

💡 Tip 4:ssnetstat を置き換え、より速く正確になります。

特定ポートの接続状態の分布を確認する:

ss -tan state time-wait '( dport = :8090 )' | wc -l
ss -tan state established '( dport = :8090 )' | wc -l

もし TIME_WAIT が5000を超える場合、注意が必要です。


五、真の原因三:WebSocketプラグインの再接続嵐

今度は「オンライン人数統計」プラグインについて話しましょう。その原理は、フロントエンドがWebSocketの長接続を通じてオンライン状態をリアルタイムで報告することです。

5.1 WebSocketハンドシェイクが失敗する理由?

WebSocketは通常のHTTPリクエストではありません。その確立プロセスは以下のようになります:

  1. クライアントはUpgrade: websocket​Connection: Upgradeヘッダーを含むHTTPリクエストを送信します

  2. サーバーは応答101 Switching Protocols​、その後HTTPからWebSocketの全二重通信チャネルにアップグレード

  3. 、以降のデータはフレーム(frame)で転送され、HTTPのリクエスト/レスポンスモデルを走らなくなります

。問題は第2ステップにあります:、私のNginxがUpgradeヘッダーを正しく転送できていません

、NginxはデフォルトでUpgradeのような非標準ヘッダーをフィルタリングする(または通常のHTTPリクエストとして扱う)ため、後端のHaloは通常のGETリクエストではなくWebSocketハンドシェイクリクエストを受信します。後端はこれを通常のAPI呼び出しと考えて処理し、接続を閉じます

。しかし、フロントエンドのJSがWebSocketが切断されたと検知すると、自動的に再接続します。、それによって「接続 → 切断 → 再接続」の無限ループが形成されました。

5.2 ログの「セットアップ現場」

access.log​からこのセットアッププロセスがはっきりと見られます:

22:14:57 "GET /apis/online-user.../online-ws HTTP/1.1" 101 197
22:14:59 "GET /apis/online-user.../online-ws HTTP/1.1" 101 169  ← 同一个 IP,2 秒后重连
22:16:04 "GET /apis/online-user.../online-ws HTTP/1.1" 101 0    ← 传输 0 字节,空连接
22:16:48 "GET /apis/online-user.../online-ws HTTP/1.1" 101 143
22:16:56 "GET /apis/online-user.../online-ws HTTP/1.1" 101 143  ← 8 秒后又重连

一つの訪問者でも短時間で3~4回のWebSocketハンドシェイクを発行できます。夜になると数十人の訪問者がいて、様々なスクレイピングツールが加わり、Haloのスレッドプールがこれらの無効なハンドシェイクでいっぱいになり、通常のページリクエストは待ち行列に並び、タイムアウトするまで待たなければなりません。

Nginx error.log​ の中の Connection reset by peerprematurely closed connection はこんな風に生まれました——Haloが圧倒され、直接粗暴に接続を切断して自衛しました。

5.3 修正策

このプラグインをアンインストールしていない理由は、長いリンクを追加した後、現在は問題がないため、ここに記録する:NginxでWebSocketを正しくサポートできる。対応するlocationに追加する:

location /apis/online-user.zyx2012.cn/ {
    proxy_pass http://halo_backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;
}

検証:

curl -i -N   -H "Connection: Upgrade"   -H "Upgrade: websocket"   -H "Host: wuqishi.com"   -H "Origin: https://wuqishi.com"   https://wuqishi.com/apis/online-user.zyx2012.cn/v1alpha1/online-ws

返信:

HTTP/2 101 Switching Protocols
upgrade: websocket
connection: upgrade

101 Switching Protocolsこれでハンドシェイクが成功し、長い接続が確立されたことを示す。ブラウザのF12のNetworkタブでも、WebSocket接続状態が(pending)から101に変わって、その後緑色の小さな点に変わり、頻繁に切断と再接続されなくなった。

現在このプラグインは非常に安定しており、オンライン人数の統計機能が完全に正常で、以前の再接続の嵐が再び発生していない。

💡 Tip 5:WebSocket の問題を調査する際、ブラウザコンソールのエラーメッセージだけを見ないでください。

curl を使って直接ハンドシェイクリクエストをシミュレートするのが最も速い方法です。もし返答が 200 OK​ ではなく 101 であれば、Nginx またはバックエンドが Upgrade ヘッダを正しく処理していないことを意味します。また、HTTP/2 が WebSocket に対応する方法は特別なため、Nginx は 1.25.1 より前のバージョンでは HTTP/2 の下での WebSocket をサポートしていません。もしあなたの Nginx のバージョンが古い場合、HTTP/1.1 でテストすることをお勧めします。


六、第四の隐患:悪意のあるスパイラルの妨害

調査中、Nginx access.log には、私を笑わせるような別のリクエストの種類もあります:

185.191.171.12 "GET /wp-content/themes/begin/inc/go.php?url=..." 404
85.208.96.196 "GET /wp-content/themes/begin/inc/go.php?url=..." 404

SemrushBot(悪名高いSEOスパイダー)が私のウェブサイトの根本不存在的 WordPress 路径を乱暴にリクエストしている。私はもうTypechoを転向させたが、今はHaloに転向している。彼はまだ私がWordPressサイトだと思っていて、Beginテーマのリダイレクトバグ(過去の脆弱性)を探っており、私のサーバーを使って黒帽SEOの踏み台にしようとしている。

これらのリクエストはすべて404で返されるが、問題は:それらはすべてHaloのバックエンドに入っていること。

Nginxが物理ファイルをマッチングできなかったので、@halo_proxy​に渡され、Haloは404リクエストごとに完全なページレンダリングを初期化し、404ページを出力しなければならない。1秒に十数件のリクエストがあり、スレッドプールが無駄に使われている。

6.1 対処方

私はNginxレベルでこれらのスパムボットをブロックしていません。代わりにCloudflareで処理しています。CloudflareのファイアウォールルールはUser-Agentに基づいてSemrushBot、DotBotなどのスパムボットを直接ブロックできるため、サーバーのリソースを消費する必要がありません。

この方法の利点は:

  • スパムトラフィックはエッジノードでブロックされるため、サーバーに到達する前に阻止されます

  • Nginx内のスパムボットのブラックリストをメンテナンスする必要がなく、Cloudflareの脅威インテリジェンスデータベースが自動的に更新されます

  • Nginxのログを汚染せず、access.logには本物の訪問者のみが残ります

💡 Tip 6:Cloudflareを使用している場合は、優先的にエッジノードでスパムトラフィックをブロックしてください。

Cloudflare端自定义规则
Cloudflare側のカスタムルール

式コード:

(http.request.uri.path contains "/.env") or 
(http.request.uri.path contains "/.git") or 
(http.request.uri.path contains "/wp-") or 
(http.request.uri.path contains "/xmlrpc.php")

Cloudflareのファイアウォールルール、Bot Fight Mode、User-Agentのブロックは、オリジンサーバーのNginxで処理するよりも効率的です。オリジンサーバーのリソースは本物のユーザーに割り当てるべきで、クローラーへの応答に無駄に使うべきではありません。もしあなたのサイトがWordPressでない場合、Cloudflareでwp-contentwp-adminを含むすべてのリクエストをブロックできます。404を返す必要すらありません。


七、その他の雑事と未完了

/rss.xmlは毎回115KBのXMLを動的に生成し、FreshRSS、Inoreader、AstraHubなどの複数のRSSアグリゲーターが数分ごとにクロールしてきます。現在、静的キャッシュは作成していません。暫くおきます。

💡 Tip 7:RSSアグリゲーターのクロール間隔は異なります。

5分ごとにクロールするものもあれば、30分ごとにクロールするものもあります。あなたはaccess.log ユーザーエージェントを分析し、最も頻繁にアクセスするアグリゲータを特定して、ターゲットに合わせたキャッシュ時間を設定します。また、HaloのRSS生成は全量の記事リストであり、記事が多く(数百件)の場合、毎回生成する際にO(n)の反復処理が必要で、CPUを非常に消費します。ページ分割されたRSSや出力数を制限することを検討することをお勧めします。

7.2 サムネイルプロキシの302ストーム

ログに大量のこのようなリクエストが見つかりました:

GET /apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https://cdn.ssslove.com/...&size=m HTTP/2.0" 302 0

Haloのサムネイルサービスは外部リンクの画像に対して302リダイレクトを行います。ページ内の画像が多い場合、この往復で多くの無駄なリクエストが増加します。今後は、画像リンクをCDNパラメータ付きの直リンクに置き換え、Haloのプロキシ層をスキップすることを検討します。

💡 Tip 8:Haloのサムネイルプロキシは、ローカルにアップロードされた画像を処理するのに適しています。

外部リンクの画像(例えば又拍云、OSS)の場合は、フロントエンドでCDNのサムネイルパラメータ(例えば !m )を使用することをお勧めします。?x-oss-process=image/resize​),Halo の代理層をスキップし、302 ルーティングを1回減らす。

7.3 HALO_EXTERNAL_URL の罠

Docker ComposeでHALO_EXTERNAL_URLがhttp://localhost:8090/,这会导致某些插件在生成回调地址、RSSリンクで誤ったドメイン名を使用しています。

実際のドメイン名に変更すべきです:

environment:
  - HALO_EXTERNAL_URL=https://wuqishi.com/

💡 ヒント9:HALO_EXTERNAL_URLは、ページリンクだけに影響するわけではありません。

それも影響します:

  • Open Graph ラベル(WeChatやTwitterで共有する際のプレビューカード)

  • RSS で<リンク> と [atom:link](atom:link) 要素

  • 一部のプラグインのOAuthコールバックアドレス

  • メール通知内のリンク

間違えていたことで、共有された記事のリンクが http://localhost:8090/...,别人根本打不开。現在、私はまだ修正していない。


八、調査の考え方のまとめ:私の独自の方法論

今回の調査では少し道を外れたが、効果的な方法論を検証することができた:

調査の順序

何をした

何を発見した

重要なコマンド / ツール

範囲の確認

他のサーバー上の他のサイトと比較

Halo カードのみで、グローバルリソースの問題を排除

htopfree -hdf -h

2. Nginx の error.log を確認

フロントエンドとバックエンドの切断原因を特定

多数の Connection reset by peer​

​tail -f /var/log/nginx/error.log

3. dmesg を確認

システムレベルの例外をチェック

Docker の頻繁な再起動 + UFW の大量の遮断

​dmesg -T | grep -E "(UFW|veth|docker0)"​

データベースログを確認

DBの負荷をチェック

公网での暴力攻撃、FATALメッセージで画面が乱れる

​docker logs halodb​

5. Nginx access.logを確認

リクエストの分布を分析

悪意のあるスクレイピング + WebSocketの高頻度再接続

awk '{print $6, $7}' access.log | sort | uniq -c | sort -rn​

6. ブラウザのF12を開く

フロントエンド/バックエンドの遅延を区別

TTFBが高く、バックエンドの応答が遅いことを確認

Chromeデベロッパーツール → ネットワーク → 時間

7. 接続状態を確認

TCPリソース枯渇を確認

TIME_WAIT 8000+

​ss -tan state time-wait | wc -l​

8. WebSocketハンドシェイクのシミュレーション

Upgradeヘッダ転送の検証

200を返す代わりに101を返す

curl -H "Upgrade: websocket" ...​

核心原則:外から内へ、まず不要なトラフィックを遮断し、次に内部設定を修正する。

最初からJVMパラメータを調整したり、データベースインデックスを追加したりすると、真の原因を見つけることが永远にできないかもしれない。根本的な問題はパフォーマンス不足ではなく、不要なリクエストによってリソースが無駄に消費されている


九、現在の状態

設定を変更し、サービスを再起動した後、今日までログを確認して30分間過ごした:

  • データベースログ:静まり返り、時折通常のクエリインタラクション

  • Nginx error.log:ゼロのエラー報告

  • Nginx access.log:多くがクリーンになり、Cloudflare が大部分のスパムクローラーをブロックしている

  • WebSocket プラグイン:ハンドシェイクで 101 Switching Protocols 返り、長期接続は安定し、頻繁な再接続がなくなった。オンライン人数統計機能は正常に作動している

  • ページの読み込み:iPad、Mac、スマートフォン 4G テストで、通常通りに開ける

夜間 22:30 から 23:00 という本来最も混雑する時間帯が、今では昼間と同じくらいスムーズです。(今夜も引き続き確認してみます


十、反省:6つの見落としやすい落とし穴

穴 1:Docker ポートマッピングはデフォルトで公開ネットワークに公開されるため、UFW ではブロックできません

データベース、Redis、MQ などの機密サービスは必ずバインド127.0.0.1してください。UFW を信じてはいけません。信じるべきものはiptables -t nat -L DOCKERです。

穴 2:Nginx のリバースプロキシはデフォルトで短い接続です

高負荷時には必ずkeepaliveを有効にしてください。そうしないとTIME_WAITがあなたを壊します。ss -tan state time-wait | wc -l​ 5000を超えると注意が必要です。

落とし穴3:WebSocketは「設定して終わり」ではありません

Upgrade​Connectionヘッダーはどちらも不可欠です。curl​を使ってハンドシェイクをシミュレートして迅速に検証し、101が返ってこないと成功とは言えません。また、Nginxのバージョンにも注意が必要です。HTTP/2でのWebSocketにはNginx ≥ 1.25.1が必要です。

落とし穴4:サードパーティプラグインは慎重に選ぶ必要がありますが、すべてを拒否する必要はありません。

今回のオンライン人数プラグインの問題の根源はプラグイン自体ではなく、Nginxの設定不足にある。修正後、プラグインは非常に安定して動作し、機能も完全に正常である。したがって、プラグイン関連の問題が発生した場合は、まずインフラストラクチャ(Nginx、リバースプロキシ、ファイアウォール)を確認し、急いでプラグインをアンインストールしないようにする。

落とし穴5:ログは最良のデバッガ

今回の調査ではJavaコードを一行も変更せず、すべての問題はログから特定した。ログを確認する習慣をつけることは、無目的なパラメータ調整よりも重要である。

Nginxのログにこれらのフィールドを追加することを推奨する。これにより、前段が遅いか後段が遅いかをすぐに判断できる:

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" '
                '"$http_user_agent" '
                'rt=$request_time urt=$upstream_response_time';
  • ​rt(request_time):クライアント視点での総時間

  • ​urt​(upstream_response_time):後段の処理時間

もし rt が大きい場合はurt が小さい → 問題はネットワークまたは Nginx にあります;もし urt が大きい → 問題はバックエンドです。

落とし穴 6:クローラの影響を無視しないで

SemrushBot、DotBot などの商用クローラは非常に aggressive で、あなたの robots.txt を遵守しません。また、WordPress の脆弱性のパスを狙ってスキャンします。もしあなたのサイトが WP でない場合、Cloudflare または Nginx レイヤーで直接ブロックし、バックエンドのリソースを無駄にしないでください。


もし Halo や似たような Java ブログシステムを使っていても夜間にパフォーマンスが低下する場合、この記録があなたにいくつかの調査のアプローチを与えることを願っています。時には問題はコードの複雑さではなく、ある設定の詳細、あるプラグイン、または公開されているポートにあることがあります。

最後に一言:SemrushBot は本当に執着しており、21:00 から 23:00 まで無停電で2時間もスキャンし続け、Cloudflare に何百回もブロックされた後も試し続けた。このような職務熱心さは、正しい道に使ってあればもうアーキテクトになっていただろう。


2026年5月22日未明に記載、一部の内容はAIによる検索後に整理されたものです。