<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>运维 | 个人主页</title><link>https://page.syscall.cn/tags/%E8%BF%90%E7%BB%B4/</link><atom:link href="https://page.syscall.cn/tags/%E8%BF%90%E7%BB%B4/index.xml" rel="self" type="application/rss+xml"/><description>运维</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>zh-Hans</language><lastBuildDate>Fri, 18 Sep 2026 00:00:00 +0000</lastBuildDate><image><url>https://page.syscall.cn/media/icon_hu7729264130191091259.png</url><title>运维</title><link>https://page.syscall.cn/tags/%E8%BF%90%E7%BB%B4/</link></image><item><title>🛡 家庭服务器的安全加固：从端口映射到 CrowdSec 联动封禁</title><link>https://page.syscall.cn/post/homelab-security-hardening/</link><pubDate>Fri, 18 Sep 2026 00:00:00 +0000</pubDate><guid>https://page.syscall.cn/post/homelab-security-hardening/</guid><description>&lt;blockquote>
&lt;p>自托管服务的第一个念头是&amp;quot;怎么把它跑起来&amp;quot;，第二个念头应该是&amp;quot;怎么别让它被人玩坏&amp;quot;。这篇是第二个念头的完整记录。&lt;/p>
&lt;/blockquote>
&lt;h2 id="一先收敛暴露面">一、先收敛暴露面&lt;/h2>
&lt;p>在装任何防护软件之前，先做减法。我的检查清单：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 哪些端口真的在监听&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ss -tlnp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 容器端口是否误暴露到公网&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker ps --format &lt;span class="s1">&amp;#39;{{.Names}}\t{{.Ports}}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>三条原则：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>容器端口只监听内网&lt;/strong>。宁可 &lt;code>127.0.0.1:2283&lt;/code> 也不要 &lt;code>0.0.0.0:2283&lt;/code>。&lt;/li>
&lt;li>&lt;strong>对外只保留必要的入口&lt;/strong>。一个 nginx 做统一入口，比十个服务各自开端口好管一百倍。&lt;/li>
&lt;li>&lt;strong>默认端口不是安全边界&lt;/strong>，但也别把管理面板挂在 80 上 —— SSH、面板、数据库都不该出现在公网扫描的常见命中列表里。&lt;/li>
&lt;/ol>
&lt;h2 id="二crowdsec用社区情报做本地防护">二、CrowdSec：用社区情报做本地防护&lt;/h2>
&lt;p>CrowdSec 的架构比 fail2ban 更值得用：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">日志解析 → 场景判定 → 决策（decision）→ LAPI 存储 → bouncer 执行封禁
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>我用的部署是&lt;strong>两级&lt;/strong>的：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>应用层节点&lt;/strong>：跑 LAPI（决策中心）+ 日志解析器，:8080 提供决策 API&lt;/li>
&lt;li>&lt;strong>边缘节点&lt;/strong>：跑 bouncer，从远程 LAPI 拉决策，在 nginx 层拒绝请求&lt;/li>
&lt;/ul>
&lt;p>这样的好处是&lt;strong>决策与执行解耦&lt;/strong>：多个入口（nginx / 防火墙 / 其他主机）可以共享同一份黑名单。&lt;/p>
&lt;p>nginx bouncer 的最小配置：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nginx" data-lang="nginx">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># http{} 内
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">server&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">listen&lt;/span> &lt;span class="mi">8888&lt;/span> &lt;span class="s">ssl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">location&lt;/span> &lt;span class="s">/&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># bouncer 通过 auth_request 或 lua 拦截
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1"># 命中黑名单 → 403
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="三坑一限流区定义位置">三、坑一：限流区定义位置&lt;/h2>
&lt;p>给防护页面加限流防刷，我第一次这么写：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nginx" data-lang="nginx">&lt;span class="line">&lt;span class="cl">&lt;span class="k">server&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">listen&lt;/span> &lt;span class="mi">8888&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># ❌ 这里不行
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kn">limit_req_zone&lt;/span> &lt;span class="nv">$binary_remote_addr&lt;/span> &lt;span class="s">zone=api:10m&lt;/span> &lt;span class="s">rate=10r/s&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>nginx 直接起不来。原因：&lt;strong>&lt;code>limit_req_zone&lt;/code> 是 &lt;code>http{}&lt;/code> 级别的指令&lt;/strong>，必须写在 &lt;code>http{}&lt;/code> 内、&lt;code>server{}&lt;/code> 之外。&lt;/p>
&lt;p>正确写法：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nginx" data-lang="nginx">&lt;span class="line">&lt;span class="cl">&lt;span class="k">http&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">limit_req_zone&lt;/span> &lt;span class="nv">$binary_remote_addr&lt;/span> &lt;span class="s">zone=api:10m&lt;/span> &lt;span class="s">rate=30r/s&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">server&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">listen&lt;/span> &lt;span class="mi">8888&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">location&lt;/span> &lt;span class="s">/api/&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">limit_req&lt;/span> &lt;span class="s">zone=api&lt;/span> &lt;span class="s">burst=20&lt;/span> &lt;span class="s">nodelay&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">limit_req_status&lt;/span> &lt;span class="mi">429&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>burst&lt;/code> 是允许的突发额度，&lt;code>nodelay&lt;/code> 表示突发部分立即放行（不排队）。防护类页面上线时这两个参数很关键 —— 小主机上排队比拒绝更危险。&lt;/p>
&lt;h2 id="四坑二防护机制把自己封了">四、坑二：防护机制把自己封了&lt;/h2>
&lt;p>这是最值得记录的一个坑，因为它属于&amp;quot;机制反噬&amp;quot;：&lt;/p>
&lt;blockquote>
&lt;p>我做压测时，防护组件判定&lt;strong>来源 IP 有攻击行为&lt;/strong>，于是把发起压测的 IP 封禁了。而这个 IP 恰好是我自己 SSH 用的出口 IP —— 结果表现为 &lt;strong>SSH 假死&lt;/strong>，看起来像网络断了，实际是被自己的防护墙拒绝。&lt;/p>
&lt;/blockquote>
&lt;p>排查过程很有代表性：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 首先怀疑网络&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ping &amp;lt;host&amp;gt; &lt;span class="c1"># 通？&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ssh &amp;lt;host&amp;gt; &lt;span class="c1"># 挂？&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. 从另一个网络位置尝试&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># → 能连上 → 说明服务活着，是&amp;#34;某个来源&amp;#34;被拒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. 直接查决策表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cscli decisions list
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. 找到自己的 IP，删除&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cscli decisions delete --ip &amp;lt;your-ip&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>结论与对策：&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>压测/扫描&lt;strong>永远从第三方网络位置发起&lt;/strong>，不要用管理出口 IP&lt;/li>
&lt;li>给管理 IP 加&lt;strong>白名单&lt;/strong>（trusted IPs），比事后救火靠谱&lt;/li>
&lt;li>记住 &lt;code>cscli decisions delete --ip &amp;lt;x&amp;gt;&lt;/code> 这条救命命令&lt;/li>
&lt;li>反过来这证明防护是&lt;strong>真的在工作&lt;/strong> —— 一个从不误封的 IDS 通常也没在工作&lt;/li>
&lt;/ul>
&lt;h2 id="五坑三光猫映射只能增量改">五、坑三：光猫映射只能增量改&lt;/h2>
&lt;p>运营商光猫的端口映射接口有个危险特性：&lt;strong>某些操作是整体替换而非单条修改&lt;/strong>。我踩过一次映射表被覆盖，服务全断。&lt;/p>
&lt;p>现在我的流程固定为：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">1. 先导出/记录当前完整映射表
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 只对目标条目做 add-only 操作（新增，绝不批量提交）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 立即读回校验：新增条目在？旧条目还在？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. 任何异常 → 用步骤 1 的记录回滚
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>同样适用于任何&amp;quot;配置整体提交&amp;quot;的管理面板 —— &lt;strong>先备份，再增量，后校验。&lt;/strong>&lt;/p>
&lt;h2 id="六坑四cgi-的-fastcgi_params-陷阱">六、坑四：CGI 的 &lt;code>fastcgi_params&lt;/code> 陷阱&lt;/h2>
&lt;p>我想用 nginx + fcgiwrap 提供一个输出网卡状态的探针。配置写完，所有请求 404：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nginx" data-lang="nginx">&lt;span class="line">&lt;span class="cl">&lt;span class="k">location&lt;/span> &lt;span class="s">/eth0&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">include&lt;/span> &lt;span class="s">fastcgi_params&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1"># ❌ 元凶
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kn">fastcgi_pass&lt;/span> &lt;span class="s">unix:/run/fcgiwrap.socket&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>原因：&lt;code>fastcgi_params&lt;/code> 里有一行&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>它会&lt;strong>覆盖&lt;/strong>你设定的 &lt;code>SCRIPT_FILENAME&lt;/code>，指向一个不存在的路径，于是 CGI 找不到脚本 → 404。&lt;/p>
&lt;p>正确做法是&lt;strong>手工列出需要的参数&lt;/strong>，不要 include 这个文件：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nginx" data-lang="nginx">&lt;span class="line">&lt;span class="cl">&lt;span class="k">location&lt;/span> &lt;span class="s">/eth0&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">fastcgi_pass&lt;/span> &lt;span class="s">unix:/run/fcgiwrap.socket&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">fastcgi_param&lt;/span> &lt;span class="s">SCRIPT_FILENAME&lt;/span> &lt;span class="s">/path/to/script.sh&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">fastcgi_param&lt;/span> &lt;span class="s">QUERY_STRING&lt;/span> &lt;span class="nv">$query_string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">fastcgi_param&lt;/span> &lt;span class="s">REQUEST_METHOD&lt;/span> &lt;span class="nv">$request_method&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">fastcgi_param&lt;/span> &lt;span class="s">CONTENT_TYPE&lt;/span> &lt;span class="nv">$content_type&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">fastcgi_param&lt;/span> &lt;span class="s">SERVER_PROTOCOL&lt;/span> &lt;span class="nv">$server_protocol&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="七坑五域名到期--安全事件">七、坑五：域名到期 = 安全事件&lt;/h2>
&lt;p>最后这个坑最容易被忽视，但后果最严重：&lt;/p>
&lt;p>站点配置里留着一个旧域名的引用。那个域名&lt;strong>我没有续费&lt;/strong>。结果：&lt;/p>
&lt;ol>
&lt;li>域名掉落后被&lt;strong>抢注&lt;/strong>&lt;/li>
&lt;li>抢注者配置了&lt;strong>通配解析&lt;/strong>，所有子域指向一个诈骗页面&lt;/li>
&lt;li>我的站点 canonical / sitemap / RSS &lt;strong>全部仍然指向旧域名&lt;/strong>&lt;/li>
&lt;li>搜索引擎和社交分享卡片全部导流到诈骗站&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>技术上站点是正常的，但对外身份被劫持了。&lt;/strong>&lt;/p>
&lt;p>对策清单：&lt;/p>
&lt;ul>
&lt;li>定期检查所有在用的域名到期时间（whois / RDAP）&lt;/li>
&lt;li>域名一旦不打算再用，&lt;strong>立刻从所有配置中清除&lt;/strong>，包括 canonical、sitemap、RSS、&lt;code>server_name&lt;/code>、&lt;code>/etc/hosts&lt;/code>&lt;/li>
&lt;li>迁移域名时&lt;strong>检查 baseURL 这类全局变量&lt;/strong> —— 只改 DNS 和证书是不够的&lt;/li>
&lt;li>搜索引擎收录是滞后的，修复后要等重新抓取&lt;/li>
&lt;/ul>
&lt;h2 id="八小结">八、小结&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>层次&lt;/th>
&lt;th>措施&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>暴露面&lt;/td>
&lt;td>端口收敛 + 容器端口内网化 + 统一入口&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>检测&lt;/td>
&lt;td>CrowdSec 日志解析 + 场景判定&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>阻断&lt;/td>
&lt;td>bouncer 在边缘执行，决策集中管理&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>抗压&lt;/td>
&lt;td>&lt;code>limit_req&lt;/code> + 热缓存读写分离&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>变更安全&lt;/td>
&lt;td>备份 → 增量 → 校验 → 可回滚&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>身份安全&lt;/td>
&lt;td>域名到期监控 + 全局变量一致性检查&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>安全加固不是装一个软件，而是让每一次变更都可回滚、每一份对外身份都可追溯。&lt;/strong>&lt;/p></description></item><item><title>🏠 家庭实验室的四层架构：边缘 / 应用 / 云端 / 接入</title><link>https://page.syscall.cn/post/homelab-architecture/</link><pubDate>Mon, 14 Sep 2026 00:00:00 +0000</pubDate><guid>https://page.syscall.cn/post/homelab-architecture/</guid><description>&lt;blockquote>
&lt;p>这篇是我这套家庭自托管集群的总览。它不是&amp;quot;买台服务器装个 Docker&amp;quot;的入门贴，而是&lt;strong>为什么这样分层&lt;/strong>的复盘 —— 每一层都在解决上一层解决不了的问题。&lt;/p>
&lt;/blockquote>
&lt;h2 id="一动机东西越堆越多然后失控">一、动机：东西越堆越多，然后失控&lt;/h2>
&lt;p>一开始只是想把照片从手机里挪出来。于是有了一台小主机跑 Immich。后来想有个自己的网盘，又加了 OpenList。再后来要远程访问、要监控、要备份、要证书、要域名……&lt;/p>
&lt;p>半年之后我发现：&lt;strong>我不知道哪个服务跑在哪台机器上，也不知道某个端口是谁开的。&lt;/strong>&lt;/p>
&lt;p>真正的问题不是&amp;quot;服务太少&amp;quot;，而是&lt;strong>缺少架构&lt;/strong>。于是我按职责把设备重新划成四层。&lt;/p>
&lt;h2 id="二四层架构">二、四层架构&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"> ┌──────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 公网 ────────────▶│ 接入层 光猫 / SD-WAN 组网 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────┬───────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 端口映射（增量式）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────▼───────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 边缘层 1.50 Edge Box │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ nginx / 门户 / 防护 bouncer │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 代理客户端 / 流量统计 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────┬───────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 内网 TLS 反代
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────▼───────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 应用层 1.40 App Node │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ Docker: 照片库 / 网盘 / DNS │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 云端层 VPS（公网入口 / 中继） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 反向代理 / 穿透服务端 / 数据库 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────────────────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="21-边缘层所有入站流量的唯一入口">2.1 边缘层：所有入站流量的唯一入口&lt;/h3>
&lt;p>边缘盒是一台 ARM 小主机（4 核 / 1GB 级），它承担：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>职责&lt;/th>
&lt;th>实现&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Web 入口&lt;/td>
&lt;td>nginx，静态托管门户站与内部工具页&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>TLS 终止&lt;/td>
&lt;td>TrustAsia DV 证书，统一挂 &lt;code>home.***.cn&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>入侵防护&lt;/td>
&lt;td>CrowdSec bouncer（决策来自应用层的 LAPI）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>出站代理&lt;/td>
&lt;td>本地 SOCKS5 + HTTP 代理，供内网其他主机借用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>流量审计&lt;/td>
&lt;td>vnStat 按日/月统计双网卡&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>状态探针&lt;/td>
&lt;td>fcgiwrap + CGI，输出网卡地址等实时信息&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>为什么把入口单独拎出来？&lt;/strong> 因为它必须是&lt;strong>唯一&lt;/strong>的。一旦有两个入口，你就有了两套证书、两套防火墙规则、两套日志，然后开始不一致。&lt;/p>
&lt;h3 id="22-应用层只跑应用不碰边界">2.2 应用层：只跑应用，不碰边界&lt;/h3>
&lt;p>应用节点的职责非常窄：跑容器。上面是 Docker 全家桶 —— 照片库（含 PostgreSQL + Redis）、网盘，以及 DDNS 客户端和 CrowdSec LAPI。&lt;/p>
&lt;p>它的网络暴露策略是&lt;strong>最小化&lt;/strong>：容器端口只监听内网，对外的 TLS 反代由边缘层做。这样即使某个应用出了 RCE，攻击者也只拿到一个没有公网身份的内网容器。&lt;/p>
&lt;h3 id="23-云端层只有公网身份不存隐私数据">2.3 云端层：只有公网身份，不存隐私数据&lt;/h3>
&lt;p>云端那台 VPS 承担两类工作：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>公网入口&lt;/strong>：反向代理 + 内网穿透服务端，把外部请求送回家里。&lt;/li>
&lt;li>&lt;strong>纯服务&lt;/strong>：递归 DNS、关系库、缓存、远程桌面中继 —— 这些&amp;quot;要么需要固定公网 IP，要么需要独立可用性&amp;quot;的东西。&lt;/li>
&lt;/ol>
&lt;p>一条硬规则：&lt;strong>隐私数据（尤其照片）绝不上云。&lt;/strong> 云主机只放可重建的数据。&lt;/p>
&lt;h3 id="24-接入层最不该被自动化碰的地方">2.4 接入层：最不该被自动化碰的地方&lt;/h3>
&lt;p>光猫和 SD-WAN 盒子属于运营商/厂商资产，配置有&lt;strong>整体替换&lt;/strong>的坑（改一条规则可能把整个映射表覆盖掉）。&lt;/p>
&lt;p>所以这一层我只做两件事：&lt;strong>只增不改&lt;/strong>（新增映射时严格走 add-only，改前导出、改后校验、失败回滚），以及&lt;strong>记录下来&lt;/strong>（哪条映射对应哪个服务，写进文档而不是记在脑中）。&lt;/p>
&lt;h2 id="三三条关键设计决策">三、三条关键设计决策&lt;/h2>
&lt;h3 id="31-域名必须统一">3.1 域名必须统一&lt;/h3>
&lt;p>站点早期用过 &lt;code>aaa.site&lt;/code> 和 &lt;code>bbb.site&lt;/code> 两套域名，结果：&lt;/p>
&lt;ul>
&lt;li>证书要签两遍&lt;/li>
&lt;li>反代 &lt;code>server_name&lt;/code> 要写两组&lt;/li>
&lt;li>文档要维护两份映射表&lt;/li>
&lt;li>&lt;strong>其中一个域名忘了续费，被抢注后全站 canonical 指向了诈骗页&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>统一到一套域名后，证书、反代、DNS 记录全部收敛成一条线。&lt;/p>
&lt;h3 id="32-公网暴露面用高位端口--域名代替ip--默认端口">3.2 公网暴露面用&amp;quot;高位端口 + 域名&amp;quot;代替&amp;quot;IP + 默认端口&amp;quot;&lt;/h3>
&lt;p>有备案要求的网络环境下，80/443 上的未备案域名会被边缘劫持到一个阻断页。应对方式不是放弃，而是&lt;strong>把 HTTPS 挪到高位端口&lt;/strong>，靠域名 + TLS 提供服务。代价是 URL 难看，收益是链路可控。&lt;/p>
&lt;h3 id="33-服务与数据分离">3.3 服务与数据分离&lt;/h3>
&lt;p>备份策略按层设计：&lt;/p>
&lt;ul>
&lt;li>边缘层：配置 + 证书，改动前必 &lt;code>cp -p&lt;/code> 备份，带回滚路径&lt;/li>
&lt;li>应用层：数据库卷 + 对象存储目录，定期快照&lt;/li>
&lt;li>云端层：可重建，所以只需要配置即代码&lt;/li>
&lt;/ul>
&lt;h2 id="四可观测性先能看见才能优化">四、可观测性：先能看见，才能优化&lt;/h2>
&lt;p>早期我靠&amp;quot;感觉&amp;quot;判断负载高低，后来改成本地采集 + 静态 JSON 热缓存：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">采集进程（应用身份） → 写 /run 下的热缓存 → nginx 读文件返回
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>关键在于&lt;strong>读写分离&lt;/strong>：HTTP 侧只读缓存，绝不触发采集。这样即使页面被脚本高频轮询，也不会把负载打上去 —— 再叠一层 &lt;code>limit_req&lt;/code> 限流，正常用户完全无感。&lt;/p>
&lt;h2 id="五踩过的坑">五、踩过的坑&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>坑&lt;/th>
&lt;th>现象&lt;/th>
&lt;th>结论&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>限流区定义位置&lt;/td>
&lt;td>nginx 起不来&lt;/td>
&lt;td>&lt;code>limit_req_zone&lt;/code> 只能在 &lt;code>http{}&lt;/code> 内&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>include fastcgi_params&lt;/code>&lt;/td>
&lt;td>所有 CGI 请求 404&lt;/td>
&lt;td>它会用 &lt;code>$document_root$fastcgi_script_name&lt;/code> 覆盖 &lt;code>SCRIPT_FILENAME&lt;/code>，必须手工列参数&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>光猫映射批量改&lt;/td>
&lt;td>映射表被整体替换&lt;/td>
&lt;td>必须 add-only 增量 + 校验 + 回滚&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>域名续费&lt;/td>
&lt;td>canonical 全指向诈骗站&lt;/td>
&lt;td>定期检查 whois 到期时间&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>容器端口直接对公网&lt;/td>
&lt;td>暴露面失控&lt;/td>
&lt;td>应用层端口只在内网可达&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="六小结">六、小结&lt;/h2>
&lt;p>这套架构的核心不是设备数量，而是&lt;strong>分层带来的可推理性质&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>想改证书 → 只动边缘层&lt;/li>
&lt;li>想加服务 → 只动应用层&lt;/li>
&lt;li>想改公网路由 → 只动云端层&lt;/li>
&lt;li>想调接入 → 只动光猫，且只增不改&lt;/li>
&lt;/ul>
&lt;p>当你需要同时在心里维护超过三台机器的状态时，架构就不是奢侈品，而是必需品。&lt;/p></description></item></channel></rss>