# Windows 自建站指南：Nginx + Cloudflare Tunnel

> 在 Windows 上部署一个可以从外网访问的网站。
> 不需要公网 IP，不需要备案。
>
> 本文档设计为 AI agent 可直接执行的完整指南。
> 替换文中的 `example.com` 和 `YourName` 为你自己的信息。

---

## 目录

1. [整体架构](#1-整体架构)
2. [前置准备](#2-前置准备)
3. [注册 Cloudflare + 买域名](#3-注册-cloudflare--买域名)
4. [创建目录结构](#4-创建目录结构)
5. [Nginx 下载配置](#5-nginx-下载配置)
6. [验证 Nginx](#6-验证-nginx)
7. [准备网站首页](#7-准备网站首页)
8. [Cloudflare Tunnel 配置](#8-cloudflare-tunnel-配置)
9. [编写启动脚本](#9-编写启动脚本)
10. [完整验证清单](#10-完整验证清单)
11. [踩坑](#11-踩坑)

---

## 1. 整体架构

```
用户 → 浏览器 → your-domain.com
                      ↓
              Cloudflare 边缘节点
                      ↓
              Cloudflare Tunnel（加密隧道，你的电脑主动建连）
                      ↓
              本地 Nginx（:80）→ /www/ 目录
```

- **Nginx** — 接收 HTTP 请求、服务静态文件（HTML/CSS/JS/图片）
- **Cloudflare Tunnel** — 外网请求经加密隧道到你的电脑，不暴露任何端口

备选方案：有公网 IP 可用 DDNS + 端口转发（但暴露端口）；不想用 Nginx 可用 Caddy（自动 SSL）。

---

## 2. 前置准备

- **一台 Windows 电脑** — 能开机上网即可，不必一直开机
- **一个 Cloudflare 账号** — 见下一节注册
- **一个域名** — 强烈建议直接在 Cloudflare 买，一步到位
- **管理员权限** — 修改 hosts、杀进程、装服务需要。右键 PowerShell → 以管理员身份运行

---

## 3. 注册 Cloudflare + 买域名

### 3.1 注册

打开 https://dash.cloudflare.com/sign-up 注册账号。

### 3.2 买域名（推荐）

在 Cloudflare 控制台左侧 → Domain Registration → 搜索购买。
**直接在 Cloudflare 买最省事**：DNS 自动托管、Tunnel 可用，不需要任何转移操作。
如果已经有在其他注册商买的域名：把它添加到 Cloudflare → 按提示改 NS 记录 → 等待生效（几分钟到几小时）。

---

## 4. 创建目录结构

所有文件集中管理。在 `C:\Users\YourName\` 下创建：

```
Server/
├── nginx/          ← 稍后解压 Nginx
├── www/            ← 网站文件（HTML / CSS / 字体 / 图片）
│   └── index.html  ← 主页，稍后创建
└── cloudflared/    ← 稍后放 cloudflared.exe
```

Nginx 和 cloudflared 都不在系统 PATH 里，之后所有命令都用完整路径或先 cd 到对应目录执行。

---

## 5. Nginx 下载配置

### 5.1 下载

1. 打开 https://nginx.org/en/download.html
2. 找到 **Windows 最新稳定版**（标注 `Stable version`，文件名类似 `nginx-1.xx.x.zip`）
3. 下载 zip 包
4. **不需要解压工具**：Windows 直接右键 zip → 全部解压缩
5. 把解压后的文件夹**里面所有内容**复制到 `C:\Users\YourName\Server\nginx\`
6. 确认 `C:\Users\YourName\Server\nginx\nginx.exe` 存在

### 5.2 编辑 nginx.conf

Nginx 自带的 `conf/nginx.conf` 是一个演示配置。替换为以下内容（替换 `YourName` 和 `example.com`）：

```nginx
worker_processes  1;
events {
    worker_connections  1024;
}
http {
    include       mime.types;              # 这行很重要，没有它 CSS/JS 加载不了
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    # Gzip 压缩（让网页加载更快）
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml text/javascript
               application/json application/javascript
               font/truetype image/svg+xml;

    server {
        listen       80;
        server_name  example.com www.example.com localhost;

        root   C:/Users/YourName/Server/www;   # ⚠️ 正斜杠，不是反斜杠
        index  index.html;

        error_page 404 /404.html;

        location / {
            try_files $uri $uri/ =404;
        }
    }
}
```

> ⚠️ `include mime.types;` 引用的是 nginx 自带的 `conf/mime.types` 文件。
> 如果这个文件丢了或路径不对，浏览器访问 .css 文件时会当作纯文本下载而不是加载样式。

### 5.3 可选：缓存配置

在 `server { }` 块内加入（放在 `location /` 之前）：

```nginx
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|avif)$ {
    expires 1y; add_header Cache-Control "public, immutable";
}
location ~* \.(css|js)$ {
    expires 1M; add_header Cache-Control "public";
}
location ~* \.(woff|woff2|ttf|otf|eot)$ {
    expires 1y; add_header Cache-Control "public, immutable";
}
```

### 5.4 可选：安全头

```nginx
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
```

### 5.5 启动 Nginx

```powershell
# 1. 先检查配置文件语法（重要：每次改 conf 后都要跑）
& "C:\Users\YourName\Server\nginx\nginx.exe" -t -p "C:/Users/YourName/Server/nginx"
# 应该输出：syntax is ok 和 test is successful

# 2. 语法正确则启动
& "C:\Users\YourName\Server\nginx\nginx.exe" -p "C:/Users/YourName/Server/nginx"
```

如果输出了 `syntax is ok` 但命令行没返回（卡住了）→ 这是 MSYS/bash 的问题。关掉终端重开一个 **PowerShell** 窗口执行。

### 5.6 验证 Nginx 是否在运行

```powershell
# 方法 1：看端口
netstat -ano | findstr ":80"
# 应显示一条 LISTENING，PID 对应 nginx.exe

# 方法 2：发 HTTP 请求
curl -s -o nul -w "%{http_code}" http://localhost
# 应返回 404（因为没有 index.html 时 Nginx 返回 404）
# 如果返回 200 或 301 也说明 Nginx 在运行
```

如果端口 80 被占用（`netstat` 显示其他 PID），看踩坑部分「IIS 占端口」和「Nginx 进程滞留」。

### 5.7 停止 Nginx

```powershell
# 从 Nginx 目录执行
cd C:\Users\YourName\Server\nginx
nginx.exe -s stop

# 如果 stop 没反应，强制全杀
Stop-Process -Name nginx -Force
```

> **不要用 `taskkill /f /im nginx.exe`**（可能漏杀）。PowerShell 的 `Stop-Process` 更彻底。

---

## 6. 验证 Nginx

### 6.1 确认 mime.types 存在

检查 `C:\Users\YourName\Server\nginx\conf\mime.types` 存在且不为空。
这个文件告诉浏览器 .css 是样式表、.js 是脚本。没有它网站样式全丢。

### 6.2 确认端口 80 没被其他程序占用

```powershell
netstat -ano | findstr ":80"
```
如果看到 PID 不是 nginx 的（比如是 `System` PID=4），说明 IIS 或其他服务占了端口 → 看踩坑。

---

## 7. 准备网站首页

### 7.1 创建 index.html

在 `C:\Users\YourName\Server\www\` 下创建：

```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我的网站</title>
</head>
<body>
<h1>Hello World</h1>
<p>我的网站已上线。</p>
</body>
</html>
```

### 7.2 验证

```powershell
curl http://localhost
# 应返回 HTML 内容，状态码 200
```

### 7.3 后续

按需求继续添加页面、样式、字体等。CSS/JS 统一放 `/assets/`。CSS 改后加 `?v=N` 版本号。

---

## 8. Cloudflare Tunnel 配置

### 8.1 下载 cloudflared

从 Cloudflare 下载 `cloudflared.exe`（单文件，约 50MB），放到 `C:\Users\YourName\Server\cloudflared\`。

```powershell
# 确认文件存在
dir C:\Users\YourName\Server\cloudflared\cloudflared.exe
```

### 8.2 登录 Cloudflare（生成 cert.pem）

```powershell
cd C:\Users\YourName\Server\cloudflared
.\cloudflared.exe tunnel login
```

- 自动打开浏览器
- 登录你的 Cloudflare 账号
- 选择你的域名 → 点击 Authorize
- 成功后 `C:\Users\YourName\.cloudflared\cert.pem` 生成（注意是在用户目录下，不是在 Server 目录）

> ⚠️ 如果浏览器没自动打开，复制终端里显示的 URL 手动打开。

### 8.3 创建隧道（生成 JSON 凭证）

```powershell
.\cloudflared.exe tunnel create my-tunnel
```

输出类似：
```
Created tunnel my-tunnel with id 86f43a4d-a078-4d3d-889c-010a55642b1d
```

记下这个 UUID。同时 `C:\Users\YourName\.cloudflared\` 下生成 `<UUID>.json` 凭证文件。

> ⚠️ 两个文件：`cert.pem`（登录产生）和 `<UUID>.json`（创建隧道产生），都在 `~\.cloudflared\` 下。

### 8.4 DNS 路由

```powershell
.\cloudflared.exe tunnel route dns my-tunnel example.com
.\cloudflared.exe tunnel route dns my-tunnel www.example.com
```

### 8.5 编辑 config.yml

编辑 `C:\Users\YourName\.cloudflared\config.yml`：

```yaml
tunnel: <你的隧道ID>                      # 上一步得到的 UUID
credentials-file: C:\Users\YourName\.cloudflared\<隧道ID>.json

ingress:
  - hostname: example.com
    service: http://localhost:80           # 指向本地 Nginx
  - hostname: www.example.com
    service: http://localhost:80
  - service: http_status:404               # 兜底：其他域名返回 404
```

> 注意路径里的分隔符是 `\`（Windows 反斜杠），但 Nginx 的 root 要用 `/`（正斜杠）。

### 8.6 运行隧道

```powershell
cd C:\Users\YourName\Server\cloudflared
.\cloudflared.exe tunnel --protocol http2 run my-tunnel
```

看到 `Connection registered` 或 `Connected` 即成功。
保持这个窗口开着（关了就断了）。

> 国内网络务必加 `--protocol http2`。强制 TCP 协议绕过运营商对 UDP 的干扰。

### 8.7 配置本地 hosts（必做，否则自己打不开）

因为 `example.com` 的 DNS 指向 Cloudflare，从你的电脑访问会形成回路。

用**管理员身份**编辑 `C:\Windows\System32\drivers\etc\hosts`，在末尾添加：

```
127.0.0.1 example.com
127.0.0.1 www.example.com
```

之后本机浏览器访问 `http://example.com` 直接走本地 Nginx。
手机用流量访问 `https://example.com` 正常走 Cloudflare，不需要改 hosts。

> hosts 文件没有扩展名，直接用记事本打开编辑。如果提示没有权限，右键记事本 → 以管理员身份运行。

### 8.8 外网验证

1. 关掉电脑的 WiFi（或开手机热点给电脑）—— 确保不走本地 hosts
2. 浏览器打开 `http://example.com`（或 `https://example.com`）
3. 如果看到你的网站 → 成功 🎉

或者用手机开流量直接访问。

---

## 9. 编写启动脚本

每次手动启动 Nginx 再启动隧道很麻烦。写一个 bat 文件，双击一键启动。

在桌面创建 `start Server.bat`：

```batch
@echo off
title VIDDsys Server
@start "" /B "C:\Users\YourName\Server\nginx\nginx.exe" -p "C:\Users\YourName\Server\nginx"
@"C:\Users\YourName\Server\cloudflared\cloudflared.exe" tunnel --protocol http2 run my-tunnel
@taskkill /f /im nginx.exe >nul 2>&1
```

**原理**：
1. Nginx 后台启动（`start /B`，不弹新窗口）
2. cloudflared 前台运行（黑窗保持打开）
3. 关窗或 Ctrl+C → cloudflared 退出 → 自动清理 Nginx 进程

> bat 文件的换行必须是 CRLF（`\r\n`）。如果用 LF 换行，cmd.exe 会报错。
> 提示文字建议用英文或 `chcp 936`（GBK），`chcp 65001`（UTF-8）容易乱码。

---

## 10. 完整验证清单

| # | 步骤 | 命令/操作 | 期望结果 |
|---|------|----------|---------|
| 1 | Nginx 配置正确 | `nginx -t -p "C:/Users/YourName/Server/nginx"` | `syntax is ok` |
| 2 | Nginx 在运行 | `netstat -ano \| findstr ":80"` | LISTENING |
| 3 | 本地能访问 | `curl http://localhost` | 200，返回 HTML |
| 4 | .cloudflared 有 cert.pem | `dir %USERPROFILE%\.cloudflared\cert.pem` | 文件存在 |
| 5 | .cloudflared 有 JSON 凭证 | `dir %USERPROFILE%\.cloudflared\*.json` | 文件存在 |
| 6 | config.yml 路径正确 | 检查凭证路径和隧道 ID | 与实际文件一致 |
| 7 | 隧道在运行 | 终端显示 `Connected` | 无报错 |
| 8 | hosts 配置了 | `ping example.com` 返回 `127.0.0.1` | 本地解析 |
| 9 | 本机能访问 | 浏览器打开 `http://example.com` | 看到你的网站 |
| 10 | 外网能访问 | 手机流量打开 `https://example.com` | 看到你的网站 |

---

## 11. 踩坑

### Nginx 提示 `syntax is ok` 但没启动
**原因**：`nginx -t` 只是语法检查，没有启动。需要单独执行 `nginx -p "..."`。

### nginx.exe 启动后终端卡住
**原因**：MSYS/bash 中直接运行 nginx.exe 会挂起终端。
**解决**：关掉重开一个 **Windows PowerShell**（不是 git-bash）运行。或者用 `start "" /B` 包裹。

### `-s reload` 没生效
**原因**：Windows 上 nginx 的 `-s reload` 可能因 `OpenEvent` 失败，旧进程继续占端口。
**解决**：用 PowerShell `Stop-Process -Name nginx -Force` 全杀再启动。

### IIS 占用了端口 80
**检查**：`netstat -ano | findstr ":80"` 看到 PID 为 4（System）或显示 `w3svc`。
**解决**：管理员 PowerShell 执行：
```powershell
net stop w3svc /y
sc config w3svc start= disabled
```
不影响正常上网。

### 浏览器访问 .css 文件变成下载
**原因**：nginx.conf 缺少 `include mime.types;` 或 mime.types 文件不存在。
**解决**：检查 `conf/mime.types` 存在，且 nginx.conf 中 `http { }` 块内有 `include mime.types;`。

### cloudflared tunnel login 浏览器没弹出来
**解决**：复制终端输出的 URL 手动粘贴到浏览器打开。

### 隧道连不上（一直重试）
**排查顺序**：
1. Nginx 在跑吗？→ `curl http://localhost`
2. config.yml 路径对吗？→ 检查 `credentials-file` 指向的 json 文件是否存在
3. 隧道 ID 填对了吗？→ config.yml 的 `tunnel:` 值 = 创建隧道时返回的 UUID
4. 网络环境特殊吗？→ 加 `--protocol http2`

### 本机访问域名提示 502 / 连接不上
**原因**：hosts 文件没有配置或配置错误。
**解决**：`ping example.com` 看是否返回 `127.0.0.1`。如果不是，检查 hosts 文件。

### 手机能访问但电脑访问不了
**正常**：手机走外网（Cloudflare），电脑需要 hosts 文件才能直接访问。

### CSS 改了但浏览器看不到变化
**原因**：Nginx 缓存（1 月）+ Cloudflare 边缘缓存。
**解决**：HTML 里 CSS 路径加 `?v=N`，如 `style.css?v=2`，每次改 CSS 后递增。加了就不能删。
