<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://cryozerolabs.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://cryozerolabs.github.io/" rel="alternate" type="text/html" /><updated>2025-09-22T21:20:16+00:00</updated><id>https://cryozerolabs.github.io/feed.xml</id><title type="html">冰零实验室 | CryoZero Labs ｜ AI &amp;amp; Dev</title><subtitle>Practical AI &amp; Dev — tools, workflows, and real-world engineering</subtitle><author><name>CryoZeroLabs</name></author><entry><title type="html">n8n 入门系列（一）：用 Docker Compose 快速搭建可用的 n8n 工作流环境</title><link href="https://cryozerolabs.github.io/n8n/n8n-quickstart-with-docker-compose/" rel="alternate" type="text/html" title="n8n 入门系列（一）：用 Docker Compose 快速搭建可用的 n8n 工作流环境" /><published>2025-09-08T00:00:00+00:00</published><updated>2025-09-08T00:00:00+00:00</updated><id>https://cryozerolabs.github.io/n8n/n8n-quickstart-with-docker-compose</id><content type="html" xml:base="https://cryozerolabs.github.io/n8n/n8n-quickstart-with-docker-compose/"><![CDATA[<blockquote class="notice">
  <p>面向小白的 n8n 入门：用 Docker Compose 10 分钟起一个可用的本地工作流环境。</p>
</blockquote>
<!--more-->

<h1 id="1-difycozen8n-的定位差异">1. Dify、Coze、n8n 的定位差异</h1>
<ul>
  <li><strong>n8n</strong>：通用的工作流自动化平台，可自部署，长项是“连接万物 + 可视化编排 + 适度写代码（JS/表达式）”。近年加入了 AI/LLM 节点，但整体仍以系统/业务自动化为核心。</li>
  <li><strong>Coze</strong>：字节旗下的 在线 Agent/Bot 平台，上手快、分发渠道全（各类社交/企业应用），同时提供了开源版（Coze Studio）便于企业私有化或二次开发。</li>
  <li><strong>Dify</strong>：面向 AI 应用/Agent 的可视化编排平台，把模型管理、RAG Pipeline、观测与评估做在一起；自带知识库，但也能接外部 RAG（如 RAGFlow）。</li>
</ul>

<h1 id="2-开始之前准备好-docker">2. 开始之前：准备好 Docker</h1>
<p>若未安装，请先按我们的图文教程完成：
《Docker 安装与配置教程》：https://cryozerolabs.github.io/devops/install-and-configure-docker/</p>

<p>已安装的同学，打开 Docker Desktop，右上角鲸鱼图标为 Running 即可继续。
（可选自检：菜单 → Preferences/Settings → About 里能看到版本号；或终端运行 docker –version、docker compose version。）</p>

<blockquote>
  <p>Windows 小贴士：建议启用 WSL2（Docker Desktop → Settings → Resources → WSL Integration 勾选你的发行版）。
{. :notice–primary}</p>
</blockquote>

<h1 id="3-新建项目n8n_compose">3. 新建项目：n8n_compose</h1>

<blockquote class="notice--primary">
  <p>目标：你将得到一个项目文件夹（里面只有一个 docker-compose.yml）</p>
</blockquote>

<h2 id="31-windows">3.1 Windows</h2>
<ol>
  <li>打开 文件资源管理器 → 进入 文档 (Documents)。</li>
  <li>建项目文件夹：右键空白处 → 新建 &gt; 文件夹 → 命名为 n8n_compose。</li>
  <li><a href="/assets/2025/09/08/n8n-quickstart-with-docker-compose/docker-compose.yml" download="">⬇️ 下载 docker-compose.yml</a> 并放在 <code class="language-plaintext highlighter-rouge">n8n_compose</code>目录中</li>
  <li>在 n8n_compose 文件夹空白处 Shift+右键 → 选择 在此处打开 PowerShell 窗口
<img src="/assets/2025/09/08/n8n-quickstart-with-docker-compose/windows-open-terminal.png" alt="windows-open-termianl" /></li>
  <li>在终端里输入这唯一一条命令（回车）：
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker compose up -d
</code></pre></div>    </div>
  </li>
</ol>

<h2 id="32-macos">3.2 macOs</h2>
<ol>
  <li>打开 Finder → 进入 文稿 (Documents)。</li>
  <li>新建项目文件夹：<code class="language-plaintext highlighter-rouge">⌘+Shift+N</code> 新建文件夹，命名 <code class="language-plaintext highlighter-rouge">n8n_compose</code>。</li>
  <li><a href="/assets/2025/09/08/n8n-quickstart-with-docker-compose/docker-compose.yml" download="">⬇️ 下载 docker-compose.yml</a> 并放在 <code class="language-plaintext highlighter-rouge">n8n_compose</code>目录中</li>
  <li>Finder 里选中 n8n_compose → 右键 → 在终端中打开
<img src="/assets/2025/09/08/n8n-quickstart-with-docker-compose/macos-open-terminal.png" alt="macos-open-termianl" /></li>
  <li>在终端里输入这唯一一条命令（回车）：
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker compose up -d
</code></pre></div>    </div>
  </li>
</ol>

<h2 id="33-完整的docker-composeyml">3.3 完整的docker-compose.yml</h2>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">services</span><span class="pi">:</span>
  <span class="na">n8n</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">n8nio/n8n:1.111.0</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">5678:5678"</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="na">N8N_HOST</span><span class="pi">:</span> <span class="s">localhost</span>
      <span class="na">N8N_PORT</span><span class="pi">:</span> <span class="m">5678</span>
      <span class="na">N8N_PROTOCOL</span><span class="pi">:</span> <span class="s">http</span>
      <span class="na">WEBHOOK_URL</span><span class="pi">:</span> <span class="s">http://localhost:5678/</span>
      <span class="na">DB_TYPE</span><span class="pi">:</span> <span class="s">sqlite</span>
      <span class="na">DB_SQLITE_FILE</span><span class="pi">:</span> <span class="s">/home/node/.n8n/database.sqlite</span>
      <span class="na">DB_SQLITE_POOL_SIZE</span><span class="pi">:</span> <span class="m">1</span>
      <span class="na">N8N_RUNNERS_ENABLED</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
      <span class="na">N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
      <span class="na">N8N_SECURE_COOKIE</span><span class="pi">:</span> <span class="s2">"</span><span class="s">false"</span>
      <span class="na">N8N_BLOCK_ENV_ACCESS_IN_NODE</span><span class="pi">:</span> <span class="s2">"</span><span class="s">false"</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">n8n_data:/home/node/.n8n</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
<span class="na">volumes</span><span class="pi">:</span>
  <span class="na">n8n_data</span><span class="pi">:</span>
</code></pre></div></div>

<h1 id="4-查看n8n运行状态并访问n8n">4. 查看n8n运行状态并访问n8n</h1>
<p>前面执行完<code class="language-plaintext highlighter-rouge">docker compose up -d</code>之后，耐心等待下载。
<img src="/assets/2025/09/08/n8n-quickstart-with-docker-compose/docker-compose-up-n8n.png" alt="docker-compose-up-n8" /></p>

<p>直到看见命令跑完</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>+] Running 3/3
• Network n8n-compose_default   Created
• Volume <span class="s2">"n8n-compose_nen_data"</span> Created
• Container n8n-compose-n8n-1   Created
</code></pre></div></div>

<p>此时我们就可以在Docker Desktop中看到n8n服务的状态了。
<img src="/assets/2025/09/08/n8n-quickstart-with-docker-compose/docker-compose-n8n-stack.png" alt="docker-compose-n8n-stack" /></p>

<p>接下来，只需要点击 “5678” 就会在浏览器中打开n8n。</p>

<p>你也可以直接点击这里访问：<a href="http://localhost:5678">http://localhost:5678</a></p>

<h1 id="5-n8n初始化向导">5. n8n初始化向导</h1>

<p>完成安装后，第一次访问 <code class="language-plaintext highlighter-rouge">http://localhost:5678</code> 会要求我们设置主账号信息，按照提示填写<code class="language-plaintext highlighter-rouge">邮箱</code>、<code class="language-plaintext highlighter-rouge">姓氏</code>、<code class="language-plaintext highlighter-rouge">名字</code>、<code class="language-plaintext highlighter-rouge">密码</code>。</p>
<blockquote class="notice--warning">
  <p>注意, 密码长度要求 <code class="language-plaintext highlighter-rouge">8位</code> 及以上，并且至少包含<code class="language-plaintext highlighter-rouge">一个数字</code>、<code class="language-plaintext highlighter-rouge">一个大写字母</code></p>
</blockquote>

<p><img src="/assets/2025/09/08/n8n-quickstart-with-docker-compose/n8n-setup-step1.png" alt="n8n-setup-step1" /></p>

<p>接下来，按照实际情况填写一下问卷调查即可。
<img src="/assets/2025/09/08/n8n-quickstart-with-docker-compose/n8n-setup-step2.png" alt="n8n-setup-step2" /></p>

<p>最后，你成功在本地完成了n8n的部署。
<img src="/assets/2025/09/08/n8n-quickstart-with-docker-compose/n8n-home.png" alt="n8n-home" /></p>

<h1 id="6-后续访问">6. 后续访问</h1>
<p>后续的访问，你只需要确保Docker Desktop启动。
接着在Docker Desktop中，查看n8n_compose的状态，<code class="language-plaintext highlighter-rouge">绿色</code>则是运行中。</p>

<p>点击后面的”5678”即可使用默认浏览器打开n8n的web页面。</p>

<p>或者直接访问：<a href="http://localhost:5678">http://localhost:5678</a>
<img src="/assets/2025/09/08/n8n-quickstart-with-docker-compose/docker-compose-n8n-stack.png" alt="docker-compose-n8n-stack" /></p>

<p>如果你发现状态图标是<code class="language-plaintext highlighter-rouge">灰色</code>的，那么点击后面的小三角，稍等片刻，直到上图的状态，即可按照上面的步骤访问n8n了。</p>

<p><img src="/assets/2025/09/08/n8n-quickstart-with-docker-compose/docker-compose-start-n8n.png" alt="docker-compose-start-n8n" /></p>]]></content><author><name>CryoZeroLabs</name></author><category term="N8N" /><category term="n8n" /><category term="AI工作流" /><category term="低代码" /><category term="docker" /><category term="docker-desktop" /><category term="docker-compose" /><summary type="html"><![CDATA[面向小白的 n8n 入门：用 Docker Compose 10 分钟起一个可用的本地工作流环境，]]></summary></entry><entry><title type="html">一次搞定 Docker 安装与配置（含代理与镜像加速）</title><link href="https://cryozerolabs.github.io/devops/install-and-configure-docker/" rel="alternate" type="text/html" title="一次搞定 Docker 安装与配置（含代理与镜像加速）" /><published>2025-09-04T00:00:00+00:00</published><updated>2025-09-04T00:00:00+00:00</updated><id>https://cryozerolabs.github.io/devops/install-and-configure-docker</id><content type="html" xml:base="https://cryozerolabs.github.io/devops/install-and-configure-docker/"><![CDATA[<p class="notice">这篇指南带你在 macOS、Windows、Ubuntu 上安装 Docker，并手把手配置镜像加速与网络代理，最后用 hello-world 验证一切正常。</p>
<!--more-->

<h1 id="1-macos--windows-安装-docker-desktop">1. macOS / Windows: 安装 Docker Desktop</h1>
<h2 id="11-下载安装-docker-desktop">1.1 下载安装 Docker Desktop</h2>
<ol>
  <li>访问<a href="https://www.docker.com/">Docker官网</a>下载安装包，按照向导安装。</li>
  <li>Windows 建议启用 WSL 2 后端（安装向导会引导完成）。</li>
</ol>

<h2 id="12-快速自检">1.2 快速自检</h2>
<p>在Docker Desktop中，打开Terminal，执行以下命令：</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">--rm</span> hello-world
</code></pre></div></div>
<p><img src="/assets//2025/09/04/install-and-configure-docker/docker-open-termianl.png" alt="docker-open-termianl" /></p>

<p>若 <code class="language-plaintext highlighter-rouge">hello-world</code> 成功输出欢迎文案，说明安装 OK。若失败，继续阅读第 <a href="#3配置镜像加速registry-mirrors">3.配置镜像加速registry-mirror</a> 后再试。</p>

<h1 id="2ubuntu-官方仓库安装">2.Ubuntu: 官方仓库安装</h1>
<h1 id="21-添加-docker-官方源并安装">2.1 添加 Docker 官方源并安装</h1>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get update
<span class="nb">sudo </span>apt-get <span class="nb">install</span> <span class="nt">-y</span> ca-certificates curl gnupg

<span class="c"># 放置 keyring</span>
<span class="nb">sudo install</span> <span class="nt">-m</span> 0755 <span class="nt">-d</span> /etc/apt/keyrings
curl <span class="nt">-fsSL</span> https://download.docker.com/linux/ubuntu/gpg <span class="se">\</span>
  | <span class="nb">sudo </span>gpg <span class="nt">--dearmor</span> <span class="nt">-o</span> /etc/apt/keyrings/docker.gpg
<span class="nb">sudo chmod </span>a+r /etc/apt/keyrings/docker.gpg

<span class="c"># 添加 repo（注意使用你当前系统的代号）</span>
<span class="nb">echo</span> <span class="s2">"deb [arch=</span><span class="si">$(</span>dpkg <span class="nt">--print-architecture</span><span class="si">)</span><span class="s2"> signed-by=/etc/apt/keyrings/docker.gpg] </span><span class="se">\</span><span class="s2">
https://download.docker.com/linux/ubuntu </span><span class="se">\</span><span class="s2">
</span><span class="si">$(</span><span class="nb">.</span> /etc/os-release <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">UBUNTU_CODENAME</span><span class="k">:-</span><span class="nv">$VERSION_CODENAME</span><span class="k">}</span><span class="s2">"</span><span class="si">)</span><span class="s2"> stable"</span> <span class="se">\</span>
| <span class="nb">sudo tee</span> /etc/apt/sources.list.d/docker.list <span class="o">&gt;</span> /dev/null

<span class="nb">sudo </span>apt-get update
<span class="nb">sudo </span>apt-get <span class="nb">install</span> <span class="nt">-y</span> docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
</code></pre></div></div>

<blockquote class="notice--primary">
  <p>可选：把当前用户加入 <code class="language-plaintext highlighter-rouge">docker</code> 组（避免每次 <code class="language-plaintext highlighter-rouge">sudo</code>）</p>
</blockquote>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>usermod <span class="nt">-aG</span> docker <span class="nv">$USER</span>
<span class="c"># 重新登录终端后生效</span>
</code></pre></div></div>

<h1 id="22-快速自检">2.2 快速自检</h1>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker version
docker info
docker run --rm hello-world
</code></pre></div></div>
<p>若 <code class="language-plaintext highlighter-rouge">hello-world</code> 成功输出欢迎文案（参考 <a href="#12-快速自检">1.2-快速自检</a> )，说明安装 OK。若失败，继续阅读 <a href="#3配置镜像加速registry-mirrors">3.配置镜像加速registry-mirror</a> 再试。</p>

<h1 id="3配置镜像加速registry-mirrors">3.配置镜像加速（registry-mirrors）</h1>
<h2 id="31-为什么要配-registry-mirrors">3.1 为什么要配 registry-mirrors？</h2>
<p>网络连通与跨境链路：直连 registry-1.docker.io 常遇到链路拥塞、解析与互联互通质量不佳，导致 docker pull 慢或超时。</p>

<p>但历经多轮整改，若干高校/公共镜像已停服或限流，云厂商的“官方加速器”也有策略调整（例如阿里云 ACR 公共加速器停止同步最新镜像，需改用订阅/全球加速等方案）。所以地址的可用性具有时效性，建议准备多个备选，或考虑自建代理。</p>
<ul>
  <li><a href="https://help.aliyun.com/zh/acr/user-guide/accelerate-the-pulls-of-docker-official-images">阿里云官方镜像加速: ACR镜像加速目前已停止同步最新镜像</a></li>
  <li><a href="https://mirrors.ustc.edu.cn/help/dockerhub.html">中科大镜像: 所有镜像缓存已暂停服务</a></li>
</ul>

<p>官方也支持通过 –registry-mirror 启动参数设置，但落盘到 daemon.json 可持久化管理。</p>

<blockquote class="notice--danger">
  <p>以下镜像加速地址更新时间为 <code class="language-plaintext highlighter-rouge">2025-09-04</code> 如需测试是否可用，可以直接运行命令 <code class="language-plaintext highlighter-rouge">docker pull run-docker.cn/hello-world</code> 进行测试，其中<code class="language-plaintext highlighter-rouge">run-docker.cn</code>换成需要测试的镜像加速器的域名</p>
</blockquote>

<h2 id="31-docker-desktopmacos-和-windows-两步就够">3.1 Docker Desktop(macOS 和 Windows): 两步就够</h2>
<p>打开 Docker Desktop → Settings → Docker Engine，在 JSON 中添加/合并：</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"registry-mirrors"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"https://docker.m.daocloud.io"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"https://docker.1ms.run"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"https://run-docker.cn"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"https://docker.hlmirror.com"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"https://docker.tbedu.top"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"https://dhub.kubesre.xyz"</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p><img src="/assets/2025/09/04/install-and-configure-docker/docker-engine-set-registry-mirrors.jpg" alt="docker-open-termianl" />
点击 <code class="language-plaintext highlighter-rouge">Apply &amp; restart</code> 使其生效（不同版本也可在 Settings 总页签里调整，或直接改 settings-store.json）。</p>

<h2 id="32-适用于ubuntulinux-编辑-daemonjson">3.2 适用于Ubuntu(Linux): 编辑 daemon.json</h2>
<h3 id="321-新建或编辑-etcdockerdaemonjson">3.2.1 新建或编辑 /etc/docker/daemon.json：</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json &gt;/dev/null &lt;&lt;'EOF'
{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://docker.1ms.run",
    "https://run-docker.cn",
    "https://docker.hlmirror.com",
    "https://docker.tbedu.top",
    "https://dhub.kubesre.xyz"
  ]
}
EOF
</code></pre></div></div>
<h3 id="322-重载并重启">3.2.2 重载并重启</h3>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl daemon-reload
<span class="nb">sudo </span>systemctl restart docker
</code></pre></div></div>

<h2 id="33-使用-hello-world-验证安装与网络">3.3 使用 hello-world 验证安装与网络</h2>
<p>打开终端（或 PowerShell）执行：</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 1) 先看镜像加速是否生效</span>
docker info | <span class="nb">grep</span> <span class="nt">-A3</span> <span class="s1">'Registry Mirrors'</span>
<span class="c"># 2) 拉取镜像测试</span>
docker run <span class="nt">--rm</span> hello-world
</code></pre></div></div>

<blockquote class="notice--primary">
  <p>到这里，你已经完成了 Docker 的安装、镜像加速与网络代理配置，并用 hello-world 验证通过</p>
</blockquote>]]></content><author><name>CryoZeroLabs</name></author><category term="devops" /><category term="docker" /><category term="docker-desktop" /><category term="ubuntu" /><category term="registry-mirrors" /><category term="proxy" /><summary type="html"><![CDATA[这篇指南带你在 macOS、Windows、Ubuntu 上安装 Docker，并手把手配置镜像加速与网络代理，最后用 hello-world 验证一切正常。]]></summary></entry><entry><title type="html">用 Nginx 通过自种 Cookie 做会话保持，像云厂商 SLB/CLB 的“植入 Cookie”那样复刻到自建 Nginx</title><link href="https://cryozerolabs.github.io/devops/nginx-sticky-cookie-websocket-and-least-conn/" rel="alternate" type="text/html" title="用 Nginx 通过自种 Cookie 做会话保持，像云厂商 SLB/CLB 的“植入 Cookie”那样复刻到自建 Nginx" /><published>2025-09-04T00:00:00+00:00</published><updated>2025-09-04T00:00:00+00:00</updated><id>https://cryozerolabs.github.io/devops/nginx-sticky-cookie-websocket-and-least-conn</id><content type="html" xml:base="https://cryozerolabs.github.io/devops/nginx-sticky-cookie-websocket-and-least-conn/"><![CDATA[<p class="notice">在 Ubuntu 上用 Nginx 通过自种 Cookie 做会话保持：WebSocket 路由用一致性哈希粘住，其他请求继续 least_conn。本文也会解释为什么不用 ip_hash，以及如何像云厂商 SLB/CLB 的“植入 Cookie”那样复刻到自建 Nginx</p>

<!--more-->

<h1 id="1场景与思路">1.场景与思路</h1>

<ul>
  <li>诉求
    <ul>
      <li>WebSocket（或长连接、SignalR、SockJS 等）需要“粘”到同一后端。</li>
      <li>普通 HTTP 请求仍追求最少连接的公平分配（least_conn）。</li>
    </ul>
  </li>
  <li>做法
    <ul>
      <li>只在需要“粘”的路由（如 /ws/）种一个黏性 Cookie（例如 SRV_STICKY），上游用 hash $cookie_SRV_STICKY consistent 做一致性哈希；</li>
      <li>其他路径继续用 least_conn。</li>
    </ul>
  </li>
  <li>为什么不用 ip_hash
    <ul>
      <li>IP 可能由 NAT/CDN/代理 共用，整团用户被“粘”到同一台，极易倾斜。</li>
      <li>移动网络 IP 频繁变化，粘性不稳定。</li>
      <li>你往往只想在部分路由粘住（如 /ws/），ip_hash 是上游级别策略，不够精细。</li>
      <li>Cookie 粘性可 设置过期时间、按站点/路径生效、配合 SameSite/HttpOnly/Secure 更可控。</li>
    </ul>
  </li>
</ul>

<h1 id="2目录结构与约定">2.目录结构与约定</h1>
<p>本文以 Ubuntu/Debian 常见布局为例：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/nginx/nginx.conf
/etc/nginx/snippets/proxy_defaults.conf
/etc/nginx/snippets/ssl_params.conf
/etc/nginx/upstreams.d/app.conf
/etc/nginx/sites-available/example.conf
/etc/nginx/sites-enabled/example.conf -&gt; ../sites-available/example.conf (软链)
</code></pre></div></div>
<blockquote class="notice--primary">
  <p>文中所有 IP、域名、证书路径 均使用通用占位：
内网 IP：10.0.0.x，域名：www.example.com，证书：/etc/nginx/certs/www.example.com.{pem,key}</p>
</blockquote>

<h1 id="3全局-nginxconf含是否需要种-cookie的开关">3.全局 nginx.conf（含“是否需要种 Cookie”的开关）</h1>
<p>修改nginx.conf，增加 <code class="language-plaintext highlighter-rouge">$cookie_SRV_STICKY $need_sticky</code>, <code class="language-plaintext highlighter-rouge">$request_id $sticky_value</code> map组合。</p>
<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">worker_processes</span>  <span class="s">auto</span><span class="p">;</span>

<span class="k">events</span> <span class="p">{</span>
    <span class="kn">worker_connections</span>  <span class="mi">4096</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">http</span> <span class="p">{</span>
    <span class="kn">include</span>       <span class="s">mime.types</span><span class="p">;</span>
    <span class="kn">default_type</span>  <span class="nc">application/octet-stream</span><span class="p">;</span>

    <span class="kn">sendfile</span>        <span class="no">on</span><span class="p">;</span>
    <span class="kn">keepalive_timeout</span>  <span class="mi">65</span><span class="p">;</span>
    <span class="kn">server_tokens</span>     <span class="no">off</span><span class="p">;</span>

    <span class="c1"># 统一日志</span>
    <span class="kn">log_format</span> <span class="s">main</span> <span class="s">'</span><span class="nv">$remote_addr</span> <span class="s">-</span> <span class="nv">$remote_user</span> <span class="s">[</span><span class="nv">$time_local</span><span class="s">]</span> <span class="s">"</span><span class="nv">$request</span><span class="s">"</span> <span class="s">'</span>
                    <span class="s">'</span><span class="nv">$status</span> <span class="nv">$body_bytes_sent</span> <span class="s">"</span><span class="nv">$http_referer</span><span class="s">"</span> <span class="s">'</span>
                    <span class="s">'"</span><span class="nv">$http_user_agent</span><span class="s">"</span> <span class="s">"</span><span class="nv">$http_x_forwarded_for</span><span class="s">"'</span><span class="p">;</span>
    <span class="kn">access_log</span>  <span class="n">/var/log/nginx/access.log</span>  <span class="s">main</span><span class="p">;</span>
    <span class="kn">error_log</span>   <span class="n">/var/log/nginx/error.log</span>   <span class="s">warn</span><span class="p">;</span>

    <span class="c1"># WebSocket 连接升级的辅助变量</span>
    <span class="kn">map</span> <span class="nv">$http_upgrade</span> <span class="nv">$connection_upgrade</span> <span class="p">{</span>
        <span class="kn">default</span> <span class="s">upgrade</span><span class="p">;</span>
        <span class="kn">''</span>      <span class="s">close</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1"># 是否需要种黏性 Cookie（无 SRV_STICKY 时返回 1）</span>
    <span class="kn">map</span> <span class="nv">$cookie_SRV_STICKY</span> <span class="nv">$need_sticky</span> <span class="p">{</span>
        <span class="kn">""</span>      <span class="mi">1</span><span class="p">;</span>   <span class="c1"># 没有 -&gt; 需要下发</span>
        <span class="kn">default</span> <span class="mi">0</span><span class="p">;</span>   <span class="c1"># 已有 -&gt; 不再下发</span>
    <span class="p">}</span>

    <span class="c1"># 生成要下发的 Cookie 值（示例用 $request_id）</span>
    <span class="kn">map</span> <span class="nv">$request_id</span> <span class="nv">$sticky_value</span> <span class="p">{</span>
        <span class="kn">default</span> <span class="nv">$request_id</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1"># 通用片段（代理默认头、强 TLS 参数等）</span>
    <span class="kn">include</span> <span class="n">/etc/nginx/snippets/proxy_defaults.conf</span><span class="p">;</span>
    <span class="kn">include</span> <span class="n">/etc/nginx/snippets/ssl_params.conf</span><span class="p">;</span>

    <span class="c1"># 上游池</span>
    <span class="kn">include</span> <span class="n">/etc/nginx/upstreams.d/*.conf</span><span class="p">;</span>

    <span class="c1"># 站点</span>
    <span class="kn">include</span> <span class="n">/etc/nginx/sites-enabled/*.conf</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote class="notice--primary">
  <p>这里的重点就在于map组合[<code class="language-plaintext highlighter-rouge">$cookie_SRV_STICKY $need_sticky</code>, <code class="language-plaintext highlighter-rouge">$request_id $sticky_value</code>], 让我们可以“只在需要的 location”里发 Cookie，其他位置不受影响。</p>
</blockquote>

<h1 id="4upstream普通请求-least_connws-用-cookie-一致性哈希">4.upstream：普通请求 least_conn，WS 用 Cookie 一致性哈希</h1>
<p><code class="language-plaintext highlighter-rouge">/etc/nginx/upstreams.d/app.conf</code>：</p>
<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 普通 HTTP 请求：最少连接</span>
<span class="k">upstream</span> <span class="s">app_http</span> <span class="p">{</span>
    <span class="kn">least_conn</span><span class="p">;</span>
    <span class="kn">server</span> <span class="nf">10.0.0.10</span><span class="p">:</span><span class="mi">80</span> <span class="s">weight=2</span>  <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>
    <span class="kn">server</span> <span class="nf">10.0.0.11</span><span class="p">:</span><span class="mi">80</span> <span class="s">weight=1</span>  <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>
    <span class="kn">server</span> <span class="nf">10.0.0.12</span><span class="p">:</span><span class="mi">80</span> <span class="s">weight=1</span>  <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>
    <span class="kn">keepalive</span> <span class="mi">64</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1"># WebSocket/长连接：按我们种的 Cookie 黏性（一致性哈希）</span>
<span class="k">upstream</span> <span class="s">app_ws_cookie</span> <span class="p">{</span>
    <span class="kn">hash</span> <span class="nv">$cookie_SRV_STICKY</span> <span class="s">consistent</span><span class="p">;</span>
    <span class="kn">server</span> <span class="nf">10.0.0.10</span><span class="p">:</span><span class="mi">80</span> <span class="s">weight=2</span> <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>
    <span class="kn">server</span> <span class="nf">10.0.0.11</span><span class="p">:</span><span class="mi">80</span> <span class="s">weight=1</span> <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>
    <span class="kn">server</span> <span class="nf">10.0.0.12</span><span class="p">:</span><span class="mi">80</span> <span class="s">weight=1</span> <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>
    <span class="kn">keepalive</span> <span class="mi">64</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote class="notice--primary">
  <p>consistent 能减少后端节点增删时的“重映射”范围，连接更平滑。</p>
</blockquote>

<h1 id="5站点-server只在-ws-下发-cookie-并走黏性池">5.站点 server：只在 /ws/ 下发 Cookie 并走黏性池</h1>
<p><code class="language-plaintext highlighter-rouge">/etc/nginx/sites-available/example.conf</code>:</p>
<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># HTTP -&gt; HTTPS 跳转</span>
<span class="k">server</span> <span class="p">{</span>
    <span class="kn">listen</span>      <span class="mi">80</span><span class="p">;</span>
    <span class="kn">server_name</span> <span class="s">www.example.com</span><span class="p">;</span>
    <span class="kn">return</span> <span class="mi">301</span> <span class="s">https://</span><span class="nv">$host$request_uri</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1"># HTTPS 主站点</span>
<span class="k">server</span> <span class="p">{</span>
    <span class="kn">listen</span>      <span class="mi">443</span> <span class="s">ssl</span><span class="p">;</span>
     <span class="kn">http2</span> <span class="no">on</span><span class="p">;</span>
    <span class="kn">server_name</span> <span class="s">www.example.com</span><span class="p">;</span>

    <span class="c1"># 证书（fullchain）</span>
    <span class="kn">ssl_certificate</span>     <span class="n">/etc/nginx/certs/www.example.com.pem</span><span class="p">;</span>
    <span class="kn">ssl_certificate_key</span> <span class="n">/etc/nginx/certs/www.example.com.key</span><span class="p">;</span>

    <span class="c1"># 安全参数 &amp; 通用反代头/WS</span>
    <span class="kn">include</span> <span class="n">/etc/nginx/snippets/ssl_params.conf</span><span class="p">;</span>
    <span class="kn">include</span> <span class="n">/etc/nginx/snippets/proxy_defaults.conf</span><span class="p">;</span>

    <span class="c1"># 普通请求：最少连接</span>
    <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
        <span class="kn">proxy_pass</span> <span class="s">http://app_http</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1"># WebSocket/SignalR 等：只在这里种 cookie，然后走 cookie 哈希上游</span>
    <span class="kn">location</span> <span class="s">^~</span> <span class="n">/ws/</span> <span class="p">{</span>
        <span class="c1"># 首次没 Cookie 才下发</span>
        <span class="kn">if</span> <span class="s">(</span><span class="nv">$need_sticky</span><span class="s">)</span> <span class="p">{</span>
            <span class="kn">add_header</span> <span class="s">Set-Cookie</span> <span class="s">"SRV_STICKY=</span><span class="nv">$sticky_value</span><span class="p">;</span> <span class="kn">Path=/</span><span class="p">;</span> <span class="kn">Max-Age=3600</span><span class="p">;</span> <span class="kn">HttpOnly</span><span class="p">;</span> <span class="kn">Secure</span><span class="p">;</span> <span class="kn">SameSite=Lax"</span> <span class="s">always</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="kn">proxy_pass</span> <span class="s">http://app_ws_cookie</span><span class="p">;</span>

        <span class="c1"># WS 关键选项</span>
        <span class="kn">proxy_buffering</span>   <span class="no">off</span><span class="p">;</span>
        <span class="kn">proxy_read_timeout</span>  <span class="s">3600s</span><span class="p">;</span>
        <span class="kn">proxy_send_timeout</span>  <span class="s">3600s</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">/etc/nginx/snippets/proxy_defaults.conf</code>（示例）：</p>
<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">proxy_set_header</span> <span class="s">Host</span>              <span class="nv">$host</span><span class="p">;</span>
<span class="k">proxy_set_header</span> <span class="s">X-Real-IP</span>         <span class="nv">$remote_addr</span><span class="p">;</span>
<span class="k">proxy_set_header</span> <span class="s">X-Forwarded-For</span>   <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
<span class="k">proxy_set_header</span> <span class="s">X-Forwarded-Proto</span> <span class="nv">$scheme</span><span class="p">;</span>

<span class="c1"># WebSocket 升级头</span>
<span class="k">proxy_set_header</span> <span class="s">Upgrade</span>    <span class="nv">$http_upgrade</span><span class="p">;</span>
<span class="k">proxy_set_header</span> <span class="s">Connection</span> <span class="nv">$connection_upgrade</span><span class="p">;</span>

<span class="k">proxy_http_version</span> <span class="mi">1</span><span class="s">.1</span><span class="p">;</span>
</code></pre></div></div>

<blockquote class="notice--primary">
  <p>为什么把“是否种 Cookie”的判断放在 location：
我们只在 /ws/ 下发 SRV_STICKY，保证普通请求不受影响，达到“按需粘住”。</p>
</blockquote>

<h1 id="6-与-ip_hash-的对照">6. 与 ip_hash 的对照</h1>

<table>
  <thead>
    <tr>
      <th>维度</th>
      <th><code class="language-plaintext highlighter-rouge">ip_hash</code></th>
      <th>Cookie 一致性哈希</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>精细度</td>
      <td>只能对一个 upstream 生效，难以<strong>按 location 控制</strong></td>
      <td>只在需要的路由种 Cookie，其它照常</td>
    </tr>
    <tr>
      <td>NAT/CDN 影响</td>
      <td>多用户共享一个外网 IP 时会<strong>挤到一台</strong></td>
      <td>粘性以浏览器 Cookie 为准，更均衡</td>
    </tr>
    <tr>
      <td>移动网络</td>
      <td>IP 频繁变化，粘性不稳定</td>
      <td>Cookie 按过期时间可控</td>
    </tr>
    <tr>
      <td>安全/治理</td>
      <td>无法设置过期、作用域</td>
      <td>支持 <code class="language-plaintext highlighter-rouge">Max-Age/Path/SameSite/HttpOnly/Secure</code></td>
    </tr>
    <tr>
      <td>迁移/扩缩容</td>
      <td>增删节点重映射不可控</td>
      <td><code class="language-plaintext highlighter-rouge">consistent</code> 降低抖动</td>
    </tr>
  </tbody>
</table>

<h1 id="7验证与排错">7.验证与排错</h1>
<h2 id="71-用-curl-看是否下发-cookie">7.1 用 curl 看是否下发 Cookie</h2>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 首次访问 WS 路由，应该看到 Set-Cookie: SRV_STICKY=...</span>
curl <span class="nt">-I</span> https://www.example.com/ws/any
</code></pre></div></div>

<h2 id="72-用浏览器开发者工具确认-srv_sticky">7.2 用浏览器开发者工具确认 SRV_STICKY</h2>
<ul>
  <li>打开页面后按 F12（或右键→检查）。
    <ul>
      <li>Chrome/Edge：切到 Application → Storage → Cookies → 选择 https://www.example.com</li>
      <li>Firefox：切到 Storage → Cookies → 选择对应站点</li>
      <li>Safari：Web Inspector → Storage → Cookies</li>
    </ul>
  </li>
  <li>确认存在名为 SRV_STICKY 的条目</li>
  <li>若没有看到：
    <ul>
      <li>先清理站点数据或开无痕窗口重试；</li>
      <li>确认访问的 URL 命中了 /ws/ 这个会下发 Cookie 的 location；</li>
      <li>看响应头里是否有 Set-Cookie: SRV_STICKY=…。</li>
    </ul>
  </li>
</ul>

<h2 id="73-验证粘性是否生效">7.3 验证“粘性”是否生效</h2>
<ul>
  <li>记下 SRV_STICKY 的值，多次访问 /ws/，观察是否命中同一后端
（可让后端在响应里加 X-Node: app-10.0.0.10 之类的标识，或查后端/ELB 日志）。</li>
  <li>修改/删除浏览器里的 SRV_STICKY 值，再次连接应切到对应新节点。</li>
</ul>

<h2 id="74-在-nginx-日志打印-cookie可选">7.4 在 Nginx 日志打印 Cookie（可选）</h2>
<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">log_format</span> <span class="s">main</span> <span class="s">'</span><span class="nv">$remote_addr</span> <span class="s">-</span> <span class="nv">$remote_user</span> <span class="s">[</span><span class="nv">$time_local</span><span class="s">]</span> <span class="s">"</span><span class="nv">$request</span><span class="s">"</span> <span class="s">'</span>
                <span class="s">'</span><span class="nv">$status</span> <span class="nv">$body_bytes_sent</span> <span class="s">"</span><span class="nv">$http_referer</span><span class="s">"</span> <span class="s">'</span>
                <span class="s">'"</span><span class="nv">$http_user_agent</span><span class="s">"</span> <span class="s">"</span><span class="nv">$http_x_forwarded_for</span><span class="s">"</span> <span class="s">'</span>
                <span class="s">'cookie=</span><span class="nv">$cookie_SRV_STICKY</span><span class="s">'</span><span class="p">;</span>
</code></pre></div></div>

<h1 id="8生产化建议">8.生产化建议</h1>
<ul>
  <li>Cookie 设计：默认用 $request_id 简洁够用；也可改为用户 ID 的哈希（注意隐私与安全）。</li>
  <li>过期时间：根据会话特性调整 Max-Age（如 30–120 分钟）。</li>
  <li>灰度/回滚：将 /ws/ 的 upstream 独立成文件，方便切换策略或节点集。</li>
  <li>健康检查：结合 max_fails/fail_timeout 或引入独立探针，确保异常节点迅速摘除。</li>
  <li>TLS：务必使用完整链证书（fullchain），并在 ssl_params.conf 固化安全套件与协议。</li>
</ul>

<h1 id="9小结">9.小结</h1>
<ul>
  <li>用黏性 Cookie + 一致性哈希把 WebSocket 等需要会话保持的路由“粘住”，</li>
  <li>其余请求继续走 least_conn，兼顾稳定性与吞吐均衡。</li>
  <li>相比 ip_hash，Cookie 方案更精细、稳健、可治理，更贴近主流云负载均衡器的“植入 Cookie”做法。</li>
</ul>]]></content><author><name>CryoZeroLabs</name></author><category term="devops" /><category term="nginx" /><category term="websocket" /><category term="负载均衡" /><category term="会话保持" /><category term="sticky cookie" /><category term="ubuntu" /><summary type="html"><![CDATA[在 Ubuntu 上用 Nginx 通过自种 Cookie 做会话保持：WebSocket 路由用一致性哈希粘住，其他请求继续 least_conn。本文也会解释为什么不用 ip_hash，以及如何像云厂商 SLB/CLB 的“植入 Cookie”那样复刻到自建 Nginx。]]></summary></entry><entry><title type="html">在 Windows 上部署 NGINX 作为 IIS 前置负载均衡（HTTPS/HTTP/2、WebSocket、服务化全攻略）</title><link href="https://cryozerolabs.github.io/devops/windows-nginx-iis-load-balancer/" rel="alternate" type="text/html" title="在 Windows 上部署 NGINX 作为 IIS 前置负载均衡（HTTPS/HTTP/2、WebSocket、服务化全攻略）" /><published>2025-09-02T00:00:00+00:00</published><updated>2025-09-02T00:00:00+00:00</updated><id>https://cryozerolabs.github.io/devops/windows-nginx-iis-load-balancer</id><content type="html" xml:base="https://cryozerolabs.github.io/devops/windows-nginx-iis-load-balancer/"><![CDATA[<p class="notice">在 Windows 上部署 NGINX 作为 IIS 前置负载均衡的实战指南：配置 HTTPS/HTTP/2 与 WebSocket，least_conn+权重调度，fullchain 证书与安全加固，基于粘性Cookie得会话保持，服务化运行与排错清单，附完整可复制示例。</p>

<!--more-->
<h1 id="1架构与适用场景">1.架构与适用场景</h1>
<p>将 TLS 终止、负载均衡与流量治理集中到 NGINX，IIS 只专注于应用本身。</p>
<ul>
  <li><strong>证书集中</strong>：TLS 只在 NGINX 终止，IIS 走内网纯 HTTP，维护成本更低；</li>
  <li><strong>流量治理</strong>：支持负载均衡、灰度、蓝绿、压测分流；</li>
  <li><strong>跨技术栈</strong>：无论后端是 IIS/.NET 还是其它服务，都能统一被接入（WebSocket 也 OK）。</li>
</ul>

<blockquote class="notice--primary">
  <p>说明：本文中的域名与 IP 统一使用示例值（如 <code class="language-plaintext highlighter-rouge">app.example.com</code>、<code class="language-plaintext highlighter-rouge">10.0.0.x</code>），请替换为你的真实信息。</p>
</blockquote>

<h1 id="2准备与下载windows-稳定版">2.准备与下载（Windows 稳定版）</h1>
<ul>
  <li>前往 <a href="">https://nginx.org/en/download.html</a> 下载 <strong>Stable</strong> 版本（稳定分支）。</li>
  <li>解压到例如 <code class="language-plaintext highlighter-rouge">D:/nginx</code></li>
  <li>在服务器放通 <strong>80/443</strong>（入口）以及到后端 IIS 的 <strong>8080/8081</strong> 等内网端口（按你的配置）。</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>D:/nginx/
├─ certs/ # 证书（fullchain 与私钥）
├─ conf/
│ ├─ conf.d/ # 每个站点的 server 配置
│ ├─ snippets/ # 可复用片段（代理默认、TLS 参数等）
│ ├─ upstreams.d/ # 上游（负载均衡池）定义
│ └─ nginx.conf # 主配置
└─ logs/ # 访问与错误日志
</code></pre></div></div>
<blockquote class="notice--primary">
  <p>Windows 上 <code class="language-plaintext highlighter-rouge">nginx.conf</code> 默认在 <code class="language-plaintext highlighter-rouge">conf/</code> 目录中，<code class="language-plaintext highlighter-rouge">include snippets/*.conf</code> 等相对路径会以 <code class="language-plaintext highlighter-rouge">conf/</code> 为基准。</p>
</blockquote>

<h1 id="3主配置-nginxconf含-websocket-与统一日志">3.主配置 nginx.conf（含 WebSocket 与统一日志）</h1>
<p>将以下内容保存为 <code class="language-plaintext highlighter-rouge">conf/nginx.conf</code>（核心与你提供的一致，已整理可直接用）：</p>
<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">worker_processes</span>  <span class="s">auto</span><span class="p">;</span>

<span class="k">events</span> <span class="p">{</span>
    <span class="kn">worker_connections</span>  <span class="mi">4096</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">http</span> <span class="p">{</span>
    <span class="kn">include</span>       <span class="s">mime.types</span><span class="p">;</span>
    <span class="kn">default_type</span>  <span class="nc">application/octet-stream</span><span class="p">;</span>

    <span class="kn">sendfile</span>        <span class="no">on</span><span class="p">;</span>
    <span class="kn">keepalive_timeout</span>  <span class="mi">65</span><span class="p">;</span>
    <span class="kn">server_tokens</span> <span class="no">off</span><span class="p">;</span>

    <span class="c1"># 统一日志（可按需分域名单独写）</span>
    <span class="kn">log_format</span>  <span class="s">main</span>  <span class="s">'</span><span class="nv">$remote_addr</span> <span class="s">-</span> <span class="nv">$remote_user</span> <span class="s">[</span><span class="nv">$time_local</span><span class="s">]</span> <span class="s">"</span><span class="nv">$request</span><span class="s">"</span> <span class="s">'</span>
                      <span class="s">'</span><span class="nv">$status</span> <span class="nv">$body_bytes_sent</span> <span class="s">"</span><span class="nv">$http_referer</span><span class="s">"</span> <span class="s">'</span>
                      <span class="s">'"</span><span class="nv">$http_user_agent</span><span class="s">"</span> <span class="s">"</span><span class="nv">$http_x_forwarded_for</span><span class="s">"'</span><span class="p">;</span>
    <span class="kn">access_log</span>  <span class="nc">logs/access</span><span class="s">.log</span>  <span class="s">main</span><span class="p">;</span>
    <span class="kn">error_log</span>   <span class="nc">logs/error</span><span class="s">.log</span>   <span class="s">warn</span><span class="p">;</span>

    <span class="c1"># WebSocket 连接升级的辅助变量</span>
    <span class="kn">map</span> <span class="nv">$http_upgrade</span> <span class="nv">$connection_upgrade</span> <span class="p">{</span>
        <span class="kn">default</span> <span class="s">upgrade</span><span class="p">;</span>
        <span class="kn">''</span>      <span class="s">close</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1"># 通用片段（代理默认头、强TLS参数等）</span>
    <span class="kn">include</span> <span class="nc">snippets/proxy</span><span class="s">_defaults.conf</span><span class="p">;</span>
    <span class="kn">include</span> <span class="nc">snippets/ssl</span><span class="s">_params.conf</span><span class="p">;</span>

    <span class="c1"># 所有上游（负载均衡池）</span>
    <span class="kn">include</span> <span class="s">upstreams.d/*.conf</span><span class="p">;</span>

    <span class="c1"># 每个站点/应用的 server 配置</span>
    <span class="kn">include</span> <span class="s">conf.d/*.conf</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="4snippets-片段">4.snippets 片段</h1>
<p>在 <code class="language-plaintext highlighter-rouge">conf/snippets/</code> 下创建以下两个文件。</p>

<h2 id="41-proxy_defaultsconf反代默认头与-ws-支持">4.1 proxy_defaults.conf（反代默认头与 WS 支持）</h2>
<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">proxy_http_version</span> <span class="mi">1</span><span class="s">.1</span><span class="p">;</span>
<span class="k">proxy_set_header</span> <span class="s">Host</span>              <span class="nv">$host</span><span class="p">;</span>
<span class="k">proxy_set_header</span> <span class="s">X-Real-IP</span>         <span class="nv">$remote_addr</span><span class="p">;</span>
<span class="k">proxy_set_header</span> <span class="s">X-Forwarded-For</span>   <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
<span class="k">proxy_set_header</span> <span class="s">X-Forwarded-Proto</span> <span class="nv">$scheme</span><span class="p">;</span>

<span class="c1"># WebSocket 必需</span>
<span class="k">proxy_set_header</span> <span class="s">Upgrade</span>           <span class="nv">$http_upgrade</span><span class="p">;</span>
<span class="k">proxy_set_header</span> <span class="s">Connection</span>        <span class="nv">$connection_upgrade</span><span class="p">;</span>

<span class="c1"># 长连接/WS 建议拉长</span>
<span class="k">proxy_read_timeout</span>   <span class="s">3600s</span><span class="p">;</span>
<span class="k">proxy_send_timeout</span>   <span class="s">3600s</span><span class="p">;</span>

<span class="c1"># 上游失败切换策略（被动健康检查配合）</span>
<span class="k">proxy_next_upstream</span> <span class="s">error</span> <span class="s">timeout</span> <span class="s">http_500</span> <span class="s">http_502</span> <span class="s">http_503</span> <span class="s">http_504</span><span class="p">;</span>

<span class="c1"># 如后端对流式响应敏感，可视情况关闭缓冲</span>
<span class="c1"># proxy_buffering off;</span>
</code></pre></div></div>

<h2 id="42-ssl_paramsconftls-安全参数与响应头">4.2 ssl_params.conf（TLS 安全参数与响应头）</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!3DES:!ADH:!RC4:!DH:!DHE;
ssl_session_cache shared:SSL:20m;
ssl_session_timeout 10m;

# OCSP Stapling（需要 fullchain 及可用 DNS）
ssl_stapling on;
ssl_stapling_verify on;
resolver 223.5.5.5 223.6.6.6 114.114.114.114 119.29.29.29 valid=300s ipv6=off;
resolver_timeout 5s;

# 安全响应头（可按需调整）
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
</code></pre></div></div>

<blockquote class="notice--warning">
  <p>证书务必使用 fullchain；如果中间证书缺失，客户端可能握手失败。</p>
</blockquote>

<h1 id="5定义上游池-upstream负载均衡">5.定义上游池 upstream（负载均衡）</h1>
<p>在 <code class="language-plaintext highlighter-rouge">conf/upstreams.d/</code> 下创建 <code class="language-plaintext highlighter-rouge">app_upstream.conf</code>，按 CPU 核心数 经验分配权重，并启用最少连接法：</p>
<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># conf/upstreams.d/app_upstream.conf</span>
<span class="k">upstream</span> <span class="s">app_upstream</span> <span class="p">{</span>
    <span class="kn">least_conn</span><span class="p">;</span>  <span class="c1"># 最少连接策略</span>

    <span class="c1"># 💡 请替换为你的真实内网 IP 与端口（示例注释中的“16/8核”为权重参考）</span>
    <span class="kn">server</span> <span class="nf">10.0.0.2</span><span class="p">:</span><span class="mi">8080</span> <span class="s">weight=2</span>  <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>  <span class="c1"># 16核</span>
    <span class="kn">server</span> <span class="nf">10.0.0.3</span><span class="p">:</span><span class="mi">8080</span> <span class="s">weight=1</span>  <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>  <span class="c1"># 8核</span>
    <span class="kn">server</span> <span class="nf">10.0.0.4</span><span class="p">:</span><span class="mi">8080</span> <span class="s">weight=1</span>  <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>  <span class="c1"># 8核</span>

    <span class="kn">keepalive</span> <span class="mi">64</span><span class="p">;</span>  <span class="c1"># 复用到上游的长连接数量</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote class="notice--primary">
  <p><code class="language-plaintext highlighter-rouge">max_fails/fail_timeout</code> 属于被动健康检查，当连接/响应失败累计到阈值后，暂时将该节点视为不可用；可结合 <code class="language-plaintext highlighter-rouge">proxy_next_upstream</code> 做快速切换。</p>
</blockquote>

<h1 id="6站点-server-配置httphttpshttp2证书路径">6.站点 server 配置（HTTP→HTTPS、HTTP/2、证书路径）</h1>
<p>在 <code class="language-plaintext highlighter-rouge">conf/conf.d/</code> 下创建 <code class="language-plaintext highlighter-rouge">app.example.com.conf</code>：</p>
<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># conf/conf.d/app.example.com.conf</span>

<span class="c1"># 80 端口：强制跳转到 HTTPS</span>
<span class="k">server</span> <span class="p">{</span>
    <span class="kn">listen</span>      <span class="mi">80</span><span class="p">;</span>
    <span class="kn">server_name</span> <span class="s">app.example.com</span><span class="p">;</span>
    <span class="kn">return</span> <span class="mi">301</span> <span class="s">https://</span><span class="nv">$host$request_uri</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1"># 443 端口：主站（启用 HTTP/2）</span>
<span class="k">server</span> <span class="p">{</span>
    <span class="kn">listen</span>      <span class="mi">443</span> <span class="s">ssl</span><span class="p">;</span>
    <span class="kn">http2</span>       <span class="no">on</span><span class="p">;</span>
    <span class="kn">server_name</span> <span class="s">app.example.com</span><span class="p">;</span>

    <span class="c1"># 证书路径（fullchain + 私钥），请替换为你的真实文件位置</span>
    <span class="kn">ssl_certificate</span>      <span class="s">D:/nginx/certs/app.example.com.pem</span><span class="p">;</span>
    <span class="kn">ssl_certificate_key</span>  <span class="s">D:/nginx/certs/app.example.com.key</span><span class="p">;</span>

    <span class="c1"># 引用 TLS 安全参数 &amp; 通用反代设置</span>
    <span class="kn">include</span> <span class="nc">snippets/ssl</span><span class="s">_params.conf</span><span class="p">;</span>
    <span class="kn">include</span> <span class="nc">snippets/proxy</span><span class="s">_defaults.conf</span><span class="p">;</span>

    <span class="c1"># 应用转发（根路径）</span>
    <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
        <span class="kn">proxy_pass</span> <span class="s">http://app_upstream</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1"># 如需限制上传大小（示例 50M）：</span>
    <span class="c1"># client_max_body_size 50m;</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="7iis-侧设置">7.IIS 侧设置</h1>
<h2 id="iis-操作">IIS 操作：</h2>
<ul>
  <li>站点 A：HTTP 绑定 <code class="language-plaintext highlighter-rouge">*:8080</code>，主机名留空；</li>
  <li>站点 B：HTTP 绑定 <code class="language-plaintext highlighter-rouge">*:8081</code>，主机名留空；</li>
  <li>其它站点以此类推（8082、8083…）。
    <h2 id="nginx-配合示例两个域名分别指向不同端口站点">NGINX 配合（示例：两个域名分别指向不同端口站点）：</h2>
  </li>
</ul>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># conf/upstreams.d/appA_upstream.conf</span>
<span class="k">upstream</span> <span class="s">appA_upstream</span> <span class="p">{</span>
    <span class="kn">least_conn</span><span class="p">;</span>
    <span class="kn">server</span> <span class="nf">10.0.0.2</span><span class="p">:</span><span class="mi">8080</span> <span class="s">weight=2</span> <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>
    <span class="kn">server</span> <span class="nf">10.0.0.3</span><span class="p">:</span><span class="mi">8080</span> <span class="s">weight=1</span> <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>
    <span class="kn">keepalive</span> <span class="mi">64</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1"># conf/upstreams.d/appB_upstream.conf</span>
<span class="k">upstream</span> <span class="s">appB_upstream</span> <span class="p">{</span>
    <span class="kn">least_conn</span><span class="p">;</span>
    <span class="kn">server</span> <span class="nf">10.0.0.2</span><span class="p">:</span><span class="mi">8081</span> <span class="s">weight=2</span> <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>
    <span class="kn">server</span> <span class="nf">10.0.0.4</span><span class="p">:</span><span class="mi">8081</span> <span class="s">weight=1</span> <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>
    <span class="kn">keepalive</span> <span class="mi">64</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1"># conf/conf.d/app.example.com.conf</span>
<span class="k">server</span> <span class="p">{</span>
    <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
    <span class="kn">server_name</span> <span class="s">app.example.com</span><span class="p">;</span>
    <span class="kn">return</span> <span class="mi">301</span> <span class="s">https://</span><span class="nv">$host$request_uri</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">server</span> <span class="p">{</span>
    <span class="kn">listen</span> <span class="mi">443</span> <span class="s">ssl</span><span class="p">;</span>
    <span class="kn">http2</span>  <span class="no">on</span><span class="p">;</span>
    <span class="kn">server_name</span> <span class="s">app.example.com</span><span class="p">;</span>
    <span class="kn">ssl_certificate</span>     <span class="s">D:/nginx/certs/app.example.com.pem</span><span class="p">;</span>
    <span class="kn">ssl_certificate_key</span> <span class="s">D:/nginx/certs/app.example.com.key</span><span class="p">;</span>
    <span class="kn">include</span> <span class="nc">snippets/ssl</span><span class="s">_params.conf</span><span class="p">;</span>
    <span class="kn">include</span> <span class="nc">snippets/proxy</span><span class="s">_defaults.conf</span><span class="p">;</span>
    <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span> <span class="kn">proxy_pass</span> <span class="s">http://appA_upstream</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>

<span class="c1"># conf/conf.d/admin.example.com.conf</span>
<span class="k">server</span> <span class="p">{</span>
    <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
    <span class="kn">server_name</span> <span class="s">admin.example.com</span><span class="p">;</span>
    <span class="kn">return</span> <span class="mi">301</span> <span class="s">https://</span><span class="nv">$host$request_uri</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">server</span> <span class="p">{</span>
    <span class="kn">listen</span> <span class="mi">443</span> <span class="s">ssl</span><span class="p">;</span>
    <span class="kn">http2</span>  <span class="no">on</span><span class="p">;</span>
    <span class="kn">server_name</span> <span class="s">admin.example.com</span><span class="p">;</span>
    <span class="kn">ssl_certificate</span>     <span class="s">D:/nginx/certs/admin.example.com.pem</span><span class="p">;</span>
    <span class="kn">ssl_certificate_key</span> <span class="s">D:/nginx/certs/admin.example.com.key</span><span class="p">;</span>
    <span class="kn">include</span> <span class="nc">snippets/ssl</span><span class="s">_params.conf</span><span class="p">;</span>
    <span class="kn">include</span> <span class="nc">snippets/proxy</span><span class="s">_defaults.conf</span><span class="p">;</span>
    <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span> <span class="kn">proxy_pass</span> <span class="s">http://appB_upstream</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="8将-nginx-以-windows-服务运行服务化">8.将 NGINX 以 Windows 服务运行（服务化）</h1>
<p>Windows 版本的 nginx 被视为测试版本，截止至<code class="language-plaintext highlighter-rouge">1.29.1</code>，nginx作为服务运行，仍然是future。
幸运的是，我找到了一个开源的解决方案 <a href="">https://github.com/winsw/winsw</a>，它是用.net构建的，允许将任何可执行文件作为服务运行。</p>

<p>winsw允许将任何.exe 文件作为 Windows 服务使用。它使用 XML 来处理服务的配置和安装。
对于Windows x64可以直接下载</p>

<p><a href="">https://github.com/winsw/winsw/releases/download/v2.12.0/WinSW-x64.exe</a></p>

<p>下载后，进入nginx目录，并将文件复制到目录下，在这个例子中是 <code class="language-plaintext highlighter-rouge">D:/nginx</code>，并将其重命名为 <code class="language-plaintext highlighter-rouge">nginx-service.exe</code>。</p>

<p>接下来，创建一个 nginx-service.xml 来描述服务，注意调整nginx路径：</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;service&gt;</span>
  <span class="nt">&lt;id&gt;</span>nginx<span class="nt">&lt;/id&gt;</span>
  <span class="nt">&lt;name&gt;</span>Nginx<span class="nt">&lt;/name&gt;</span>
  <span class="nt">&lt;description&gt;</span>Nginx web server<span class="nt">&lt;/description&gt;</span>

  <span class="nt">&lt;executable&gt;</span>D:\nginx\nginx.exe<span class="nt">&lt;/executable&gt;</span>
  <span class="nt">&lt;workingdirectory&gt;</span>D:\nginx<span class="nt">&lt;/workingdirectory&gt;</span>

  <span class="c">&lt;!-- 停服务时优雅退出 --&gt;</span>
  <span class="nt">&lt;stopexecutable&gt;</span>D:\nginx\nginx.exe<span class="nt">&lt;/stopexecutable&gt;</span>
  <span class="nt">&lt;stoparguments&gt;</span>-s quit<span class="nt">&lt;/stoparguments&gt;</span>

  <span class="nt">&lt;startmode&gt;</span>Automatic<span class="nt">&lt;/startmode&gt;</span>

  <span class="nt">&lt;logpath&gt;</span>D:\nginx\logs<span class="nt">&lt;/logpath&gt;</span>
  <span class="nt">&lt;log</span> <span class="na">mode=</span><span class="s">"roll-by-size"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;sizeThreshold&gt;</span>10485760<span class="nt">&lt;/sizeThreshold&gt;</span>
    <span class="nt">&lt;keepFiles&gt;</span>5<span class="nt">&lt;/keepFiles&gt;</span>
  <span class="nt">&lt;/log&gt;</span>
<span class="nt">&lt;/service&gt;</span>
</code></pre></div></div>

<p>以管理员权限打开 PowerShell 或 CMD：</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 安装为服务（nginx-service.exe）</span><span class="w">
</span><span class="o">.</span><span class="n">\nginx-service.exe</span><span class="w"> </span><span class="nx">install</span><span class="w">

</span><span class="c"># 启动服务</span><span class="w">
</span><span class="o">.</span><span class="n">\nginx-service.exe</span><span class="w"> </span><span class="nx">start</span><span class="w">

</span><span class="c"># 停止服务</span><span class="w">
</span><span class="o">.</span><span class="n">\nginx-service.exe</span><span class="w"> </span><span class="nx">stop</span><span class="w">
</span><span class="c"># 重启服务</span><span class="w">
</span><span class="o">.</span><span class="n">\nginx-service.exe</span><span class="w"> </span><span class="nx">stop</span><span class="w">
</span><span class="c"># 卸载服务</span><span class="w">
</span><span class="o">.</span><span class="n">\nginx-service.exe</span><span class="w"> </span><span class="nx">uninstall</span><span class="w">
</span></code></pre></div></div>

<p>nginx自检仍然使用 <code class="language-plaintext highlighter-rouge">.\nginx.exe -t</code></p>

<p>如果出现意外，比如自己用.\nginx.exe 启动了nginx 而导致脱离了服务的控制，无法通过nginx-service.exe对服务进行重启的，就需要强制结束了。
所以，不要自己的用.\nginx启动，要重启之前，先自检，因为重启报错也可能导致脱离服务管控。</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>taskkill /F /IM nginx.exe

</code></pre></div></div>

<blockquote class="notice--warning">
  <p>注意：</p>
  <ul>
    <li>确认进程权限与工作目录正确，否则热加载/服务管理可能失败；</li>
    <li>路径含空格时，建议简化安装路径或使用引号。</li>
  </ul>
</blockquote>

<h1 id="9补充实现基于粘性cookie的会话保持">9.（补充）实现基于粘性Cookie的会话保持</h1>
<p>前面配置的upstream，使用least_conn更均衡的分配。但是用到websocket就不行了。
这里我参考了阿里云负载均衡服务（SLB）中CLB的 植入Cookie方式，来保持会话粘性。</p>
<h2 id="91-修改nginxconf">9.1 修改nginx.conf</h2>
<p>在 <code class="language-plaintext highlighter-rouge">http {}</code> 片段中插入代码，其中<code class="language-plaintext highlighter-rouge">SRV_STICKY</code>就是我们要植入的cookie，记住等会会用到，当然也可以根据实际情况修改。</p>
<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code>	<span class="c1"># 是否需要种黏性Cookie</span>
	<span class="k">map</span> <span class="nv">$cookie_SRV_STICKY</span> <span class="nv">$need_sticky</span> <span class="p">{</span>
		<span class="kn">""</span>      <span class="mi">1</span><span class="p">;</span>   <span class="c1"># 没有 -&gt; 需要下发</span>
		<span class="kn">default</span> <span class="mi">0</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="c1"># 生成要下发的Cookie值（只在 need_sticky=1 时使用）</span>
	<span class="k">map</span> <span class="nv">$request_id</span> <span class="nv">$sticky_value</span> <span class="p">{</span>
		<span class="kn">default</span> <span class="nv">$request_id</span><span class="p">;</span>
	<span class="p">}</span>
</code></pre></div></div>

<h2 id="93-增加upstream以支持sticky">9.3 增加upstream以支持sticky</h2>
<p>修改<code class="language-plaintext highlighter-rouge">conf/upstreams.d/app_upstream.conf</code> 增加app_signalr_cookie</p>
<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># conf/upstreams.d/app_upstream.conf</span>
<span class="k">upstream</span> <span class="s">app_upstream</span> <span class="p">{</span>
    <span class="kn">...</span> <span class="s">原来的不要动</span>
<span class="err">}</span>

<span class="c1"># 新增的</span>
<span class="s">upstream</span> <span class="s">app_signalr_cookie</span> <span class="p">{</span>
    <span class="kn">hash</span> <span class="nv">$cookie_SRV_STICKY</span> <span class="s">consistent</span><span class="p">;</span>  <span class="c1"># 按我们种的 cookie 黏性</span>

    <span class="c1"># 💡 请替换为你的真实内网 IP 与端口（示例注释中的“16/8核”为权重参考）</span>
    <span class="kn">server</span> <span class="nf">10.0.0.2</span><span class="p">:</span><span class="mi">8080</span> <span class="s">weight=2</span>  <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>  <span class="c1"># 16核</span>
    <span class="kn">server</span> <span class="nf">10.0.0.3</span><span class="p">:</span><span class="mi">8080</span> <span class="s">weight=1</span>  <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>  <span class="c1"># 8核</span>
    <span class="kn">server</span> <span class="nf">10.0.0.4</span><span class="p">:</span><span class="mi">8080</span> <span class="s">weight=1</span>  <span class="s">max_fails=3</span> <span class="s">fail_timeout=10s</span><span class="p">;</span>  <span class="c1"># 8核</span>

    <span class="kn">keepalive</span> <span class="mi">64</span><span class="p">;</span>  <span class="c1"># 复用到上游的长连接数量</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="92-对指定的websocket路径设置粘性会话">9.2 对指定的websocket路径设置粘性会话</h2>
<p>修改<code class="language-plaintext highlighter-rouge">conf/conf.d/app.example.com.conf</code>， 增加一条规则。</p>
<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">location</span> <span class="s">^~</span> <span class="n">/ws/</span> <span class="p">{</span>
		<span class="c1"># 第一次没 Cookie 才种</span>
		<span class="kn">if</span> <span class="s">(</span><span class="nv">$need_sticky</span><span class="s">)</span> <span class="p">{</span>
			<span class="kn">add_header</span> <span class="s">Set-Cookie</span> <span class="s">"SRV_STICKY=</span><span class="nv">$sticky_value</span><span class="p">;</span> <span class="kn">Path=/</span><span class="p">;</span> <span class="kn">Max-Age=3600</span><span class="p">;</span> <span class="kn">HttpOnly</span><span class="p">;</span> <span class="kn">Secure</span><span class="p">;</span> <span class="kn">SameSite=Lax"</span> <span class="s">always</span><span class="p">;</span>
		<span class="p">}</span>

		<span class="kn">proxy_pass</span> <span class="s">http://app_signalr_cookie</span><span class="p">;</span>
		<span class="kn">proxy_buffering</span> <span class="no">off</span><span class="p">;</span>
		<span class="kn">proxy_read_timeout</span>  <span class="s">3600s</span><span class="p">;</span>
		<span class="kn">proxy_send_timeout</span>  <span class="s">3600s</span><span class="p">;</span>
	<span class="p">}</span>
</code></pre></div></div>

<p>接下来</p>
<ol>
  <li>重启nginx</li>
  <li>在浏览器中验证Cookie中是否有key：<code class="language-plaintext highlighter-rouge">SRV_STICKY</code></li>
</ol>

<h1 id="10常见问题与排错清单">10.常见问题与排错清单</h1>
<ul>
  <li>502/504：上游不可达或超时。检查：
    <ul>
      <li>内网防火墙是否放通 8080/8081；</li>
      <li>IIS 站点是否启动、应用池是否健康；</li>
      <li><code class="language-plaintext highlighter-rouge">max_fails/fail_timeout</code> 是否过于严格；</li>
      <li><code class="language-plaintext highlighter-rouge">proxy_read_timeout/proxy_send_timeout</code> 是否偏短。</li>
    </ul>
  </li>
  <li>413（Payload Too Large）：增加 client_max_body_size（例如 50m）。</li>
  <li>无法握手/证书错误：确保证书为 fullchain，密钥与证书匹配，域名一致。</li>
  <li>WebSocket 400/426：确认已设置 <code class="language-plaintext highlighter-rouge">proxy_http_version 1.1</code>、<code class="language-plaintext highlighter-rouge">Upgrade/Connection</code> 头与 map $http_upgrade。</li>
  <li>真实客户端 IP：应用/日志读取 <code class="language-plaintext highlighter-rouge">X-Forwarded-For；IIS</code> 可在高级日志字段中记录该头。</li>
  <li>日志定位：查看 <code class="language-plaintext highlighter-rouge">logs/error.log</code> 与 <code class="language-plaintext highlighter-rouge">logs/access.log</code>，快速筛选关键字符串或状态码。</li>
  <li>端口占用：Windows 上用 <code class="language-plaintext highlighter-rouge">netstat -ano | findstr :80 / :443 / :8080</code> 定位占用进程。</li>
</ul>

<h1 id="11发布前检查清单checklist">11.发布前检查清单（Checklist）</h1>
<ul>
  <li>✅ 域名解析正确，公网仅指向 NGINX 所在机器；</li>
  <li>✅ 证书（fullchain/privkey）路径与权限无误；</li>
  <li>✅ Windows 防火墙、云安全组等放通 80/443（入口）与 8080/8081（到 IIS）；</li>
  <li>✅ NGINX 配置 <code class="language-plaintext highlighter-rouge">nginx -t</code> 自检通过，<code class="language-plaintext highlighter-rouge">-s reload</code> 生效；</li>
  <li>✅ 访问 https://app.example.com/ 正常；后端 8080/8081 健康；</li>
  <li>✅ WebSocket/长连接业务已验证，超时时间设置合理；</li>
  <li>✅ 日志中出现 200/301/101 等预期状态码，后端无异常 5xx。</li>
</ul>]]></content><author><name>CryoZeroLabs</name></author><category term="devops" /><category term="nginx" /><category term="iis" /><category term="reverse-proxy" /><category term="websocket" /><category term="https" /><category term="windows-service" /><summary type="html"><![CDATA[在 Windows 上部署 NGINX 作为 IIS 前置负载均衡的实战指南：配置 HTTPS/HTTP/2 与 WebSocket，least_conn+权重调度，fullchain 证书与安全加固，基于粘性Cookie得会话保持，服务化运行与排错清单，附完整可复制示例。]]></summary></entry></feed>