📌 新手提示:本文结尾提供了一键部署脚本(Windows WSL / macOS / Linux 通用),自动检测网络环境并完成安装、配置生成和验证,不想手动操作的同学直接拉到文末运行。

导读:Nginx 是什么?

Nginx(读作 “engine-x”)是一个开源的 HTTP 服务器。你可以把它理解成互联网的"门卫 + 快递员"——用户发来请求时,它负责判断:“这个请求我要自己处理,还是交给别的程序处理?”

最常见的三个用途:

  • 托管静态网站:放 HTML/CSS/图片,Nginx 直接返回给用户(速度快、占用资源少)
  • 反向代理:把请求转发给后端的应用服务器(比如 Node.js、Python Flask/Django),实现动静分离
  • 负载均衡:同一份服务跑在多台机器上,Nginx 把流量合理分配给每台机器,避免单点压力过大

本教程面向零基础读者,从零开始:安装 → 配置静态站 → 理解匹配规则 → 反向代理 → 负载均衡 → HTTPS 加密,每个步骤都附带可复制的命令和注释。


第一步:安装 Nginx

Debian / Ubuntu 系

# 更新软件包列表(相当于手机应用商店刷新可用应用列表)
sudo apt update

# 安装 nginx 稳定版
sudo apt install -y nginx

安装完成后会自动启用并启动。验证是否成功:

# 查看版本号和编译选项
nginx -v
# 输出示例:nginx version: nginx/1.26.3(版本号以实际为准)

# 检查服务状态
systemctl status nginx

国内加速:如果 apt update 很慢,可以换成国内镜像源(如阿里云、清华 Tuna)。搜索"Nginx 官方 deb 源"即可找到对应配置方法。

CentOS / RHEL / Fedora 系

# 安装 EPEL 额外仓库(CentOS 默认仓库里没有最新版 nginx)
sudo yum install -y epel-release

# 安装 nginx
sudo yum install -y nginx

# 启动并设置开机自启
sudo systemctl start nginx
sudo systemctl enable nginx

macOS

# 通过 Homebrew 包管理器安装(macOS 没有默认包管理器,Homebrew 是最常用的)
brew install nginx

# 启动
brew services start nginx

第二步:目录结构与核心概念

了解文件在哪,比记住所有命令更重要。

路径作用小白白话
/etc/nginx/nginx.conf主配置文件整个 Nginx 的"总开关"
/etc/nginx/conf.d//etc/nginx/sites-enabled/站点配置目录每个网站的独立配置
/var/www/html/默认网页根目录放 HTML 文件的地方
/var/log/nginx/日志目录access.log(谁访问了)、error.log(出了什么错)

注意:不同发行版的站点配置目录略有差异——Debian/Ubuntu 用 sites-available/ + sites-enabled/;CentOS/Fedora 用 conf.d/。下文以 Debian/Ubuntu 为例,CentOS 用户把路径替换为 conf.d/ 即可。


第三步:搭建一个静态网站

先让 Nginx 把一个简单的 HTML 页面展示给用户看。

# 创建自定义网页目录
sudo mkdir -p /var/www/mysite

# 写一个最简单的 HTML 页面
sudo tee /var/www/mysite/index.html > /dev/null <<'EOF'
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>我的第一个 Nginx 站点</title>
</head>
<body>
    <h1>你好,Nginx!</h1>
    <p>这个页面由 Nginx 直接提供,无需任何后端程序。</p>
</body>
</html>
EOF

接下来编写 Nginx 站点配置:

sudo tee /etc/nginx/sites-available/mysite > /dev/null <<'EOF'
server {
    # 监听端口(80 = HTTP 明文协议的标准端口)
    listen 80;

    # 域名(留空表示匹配所有域名;生产环境应填写你的域名)
    server_name _;

    # root 指令指定网页文件存放位置(绝对路径)
    root /var/www/mysite;

    # index 指定默认文件名(浏览器只输入域名时自动找这个文件)
    index index.html;

    # location / 匹配根路径,返回 index.html
    location / {
        try_files $uri $uri/ =404;
        # try_files 逻辑翻译:
        #   1) 先看有没有 $uri 这个文件(如输入 /about.html,就找 /var/www/mysite/about.html)
        #   2) 再看有没有 $uri/ 这个目录(如输入 /images/,就看是不是文件夹)
        #   3) 都不存在,返回 404 错误
    }
}
EOF

激活站点(在 sites-enabled 目录下创建软链接,Nginx 启动时会读取该目录下的文件):

# 删除默认站点(防止冲突)
sudo rm -f /etc/nginx/sites-enabled/default

# 创建软链接激活我们的配置
sudo ln -sf /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/mysite

关键一步:检查配置语法是否正确!

# nginx -t 是每次改完配置后必跑的命令(t = test)
# 它在不重启的情况下模拟加载配置,发现语法错误会直接报错
sudo nginx -t
# 期望输出:syntax is ok + test is successful

然后重载 Nginx(不是重启,见后文 §5):

sudo systemctl reload nginx

现在在浏览器打开服务器 IP(或域名),应该能看到"你好,Nginx!"。


第四步:理解 location 匹配规则(重点)

location 是 Nginx 最核心的指令之一。它的本质是路径匹配器——告诉 Nginx:“对 /xxx 开头的 URL 做什么处理”。

四种写法

写法含义例子
= /exact精确匹配,URL 必须完全一致= /robots.txt 只匹配 /robots.txt
^~ /prefix前缀匹配,匹配成功后不再检查正则^~ /static/ 匹配 /static/a/static/b/c
~ /pattern正则匹配(区分大小写)~ \.jpg$ 匹配 .jpg 结尾的文件
~* /pattern正则匹配(不区分大小写)`~* .(png
/通用前缀匹配,最低优先级兜底location / 匹配一切

匹配优先级(口诀)

  1. 先找精确匹配= 开头)——命中就直接结束
  2. 再找最长前缀匹配(不带修饰符的路径)——暂时记下
  3. 按文件顺序执行正则匹配~~*)——第一个命中的胜出
  4. 如果有 ^~ 的前缀匹配,跳过正则直接用它
  5. 以上都没命中,回到第 2 步记下的最长前缀

实战:动静分离配置

假设你有一个静态网站 + 一个 API 服务:

server {
    listen 80;
    server_name mysite.com;
    root /var/www/mysite;

    # ① 精确匹配:favicon.ico(浏览器经常请求,优先处理)
    location = /favicon.ico {
        log_not_found off;       # 找不到也不要记日志(节省磁盘空间)
        access_log off;          # 也不写入访问日志
    }

    # ② ^~ 前缀匹配:所有静态资源(CSS/JS/图片),命中后不查正则
    location ^~ /assets/ {
        root /var/www/mysite;
        expires 30d;             # 浏览器缓存 30 天(不改名的资源适合长期缓存)
        add_header Cache-Control "public, immutable";
    }

    # ③ 正则匹配:所有 PHP 文件(如果你装了 PHP-FPM)
    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # ④ 通用兜底:其他所有请求
    location / {
        try_files $uri $uri/ =404;
    }
}

为什么要有 ^~ 想象 /api/data.json——如果不加 ^~,它会同时命中前缀 location /api/ 和正则 location ~ \.json$,Nginx 得按顺序一个个试。加了 ^~ 就能提前结束匹配链,节省性能。在大量并发请求下,这种微小的节省叠加起来很可观。


第五步:反向代理(让 Nginx 做中转)

反向代理的意思是:Nginx 收到请求后,不自己处理,而是"转手"发给后端的某个程序,再把结果原封不动地还给客户端。对外用户看不到后端的存在,只看到 Nginx。

代理 Node.js 应用

你的 Node.js 应用跑在 localhost:3000

server {
    listen 80;
    server_name app.example.com;

    # 前端静态文件(React/Vue 构建后的产物)
    location / {
        root /var/www/app/dist;
        index index.html;
        try_files $uri $uri/ /index.html;
        # try_files $uri/ /index.html 的作用:
        # SPA 路由专用——Vue Router 的 /about 路径不存在对应物理文件,
        # 回退到 /index.html 让前端 JS 自行处理路由
    }

    # API 请求转发给 Node.js
    location /api/ {
        proxy_pass http://127.0.0.1:3000/;
        # proxy_pass 末尾的 / 很重要:
        #   有 /  → 请求 /api/users 转发后变成 http://127.0.0.1:3000/users
        #   无 /  → 请求 /api/users 转发后变成 http://127.0.0.1:3000/api/users
        # 多一个斜杠,后端路由就对不上!

        # 把用户的真实信息传给后端(否则后端看到的都是 127.0.0.1)
        proxy_set_header Host $host;              # 原始域名
        proxy_set_header X-Real-IP $remote_addr;  # 用户真实 IP
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  # 完整的跳转链路
        proxy_set_header X-Forwarded-Proto $scheme;  # 原始协议(http 或 https)
    }
}

代理 Python (Flask / FastAPI) 应用

你的 Python 应用通过 Gunicorn 跑在 127.0.0.1:8000

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;      # WebSocket 需要 HTTP/1.1
        proxy_set_header Upgrade $http_upgrade;     # WebSocket 升级头
        proxy_set_header Connection "upgrade";      # WebSocket 连接保持
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

WebSocket 补充:如果你的 API 支持 WebSocket(实时通讯类应用),上面的 UpgradeConnection 两行是必须的,否则 WebSocket 握手会失败。

💡 不想管后端? 如果只是想让某个 AI 模型上线跑起来而不是折腾反向代理,也可以直接用云端托管方案——比如云间 API 中转站(https://cloudzone-api.cyou/,OpenAI 兼容 + Anthropic 兼容、90+ 模型国内直连、价格不足官方两折、香港节点低延迟)。把算力交给云端,Nginx 专心做好流量调度就好。


第六步:Upstream 负载均衡

当一台服务器扛不住流量时,就把同一份代码部署到多台机器上,Nginx 负责分发。upstream 就是在 Nginx 里定义的后端服务器组。

# upstream 块放在 http 级别(不在 server 内)
# 这里定义了名为 "backend_pool" 的后端服务器组
upstream backend_pool {
    # 方案一:轮询(默认策略)
    # 每个请求依次分给下一台服务器
    server 192.168.1.10:3000 weight=3;   # 权重 3,每 5 个请求分 3 个
    server 192.168.1.11:3000 weight=2;   # 权重 2,每 5 个请求分 2 个
    server 192.168.1.12:3000 backup;     # 备用机——只有上面两台都挂了才启用

    # 方案二:ip_hash(按来源 IP 哈希,保证同一用户始终访问同一台后端)
    # 适用于有会话状态的场景(用户登录态存在服务端内存中)
    # ip_hash;

    # 方案三:least_conn(最少连接数)
    # 哪个后端当前活跃连接最少,就分发给它
    # least_conn;
}

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://backend_pool;  # 引用上面定义的 upstream 名称
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_connect_timeout 10s;       # 连不上后端 10 秒算超时
        proxy_read_timeout 60s;          # 后端处理超过 60 秒算超时
    }
}

策略选择指南:

场景推荐策略原因
无状态 API(REST)轮询 / 加权轮询每台服务器平等对待
有 Session 且未使用外部存储ip_hash同个用户不会跳到不同机器导致丢会话
各机器性能差异大weight 加权好机器多接客,差机器少接
后端处理耗时差异明显least_conn短任务的机器更"空闲",优先分配

第七步:HTTPS 加密(SSL/TLS 证书)

HTTP 明文传输——任何人都在网络上能看到你发的内容。HTTPS = HTTP over TLS,加密之后再传输。

方式一:Certbot(Let’s Encrypt 免费证书,推荐)

适用于域名已经解析到服务器的情况:

# 安装 certbot(Let's Encrypt 的证书申请工具)
sudo apt install -y certbot python3-certbot-nginx

# 申请证书(certbot 会自动修改 Nginx 配置加上 SSL)
sudo certbot --nginx -d example.com -d www.example.com
# 期间需要输入邮箱 + 同意条款
# 证书有效期 90 天,certbot 会自动配定时任务续期

# 手动测试续期(以防万一)
sudo certbot renew --dry-run

Certbot 申请完后会在 Nginx 配置里自动生成类似这样的段:

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Let's Encrypt 推荐的加密套件和协议版本
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
        ...
    }
}

# HTTP 强制跳转到 HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
    # return 301 的意思:所有 HTTP 请求一律 301 永久重定向到 HTTPS
}

方式二:自签证书(测试/内网用)

不要用于公网生产环境!仅适合开发测试或内网服务:

# 生成 RSA 私钥(-days 365 = 证书有效期 1 年)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/ssl/private/mysite.key \
  -out /etc/ssl/certs/mysite.crt \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/CN=example.com"

然后在 Nginx 配置里引用:

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate     /etc/ssl/certs/mysite.crt;
    ssl_certificate_key /etc/ssl/private/mysite.key;
    ssl_protocols TLSv1.2 TLSv1.3;

    location / {
        root /var/www/mysite;
        index index.html;
    }
}

⚠️ 浏览器警告:自签证书不会被浏览器信任,首次访问会弹出"此连接非私密连接"警告。这是正常现象——对于测试环境可以点击"高级 → 继续访问"跳过去。


第八步:常见坑与排障

坑 1:端口 80/443 被占用

# 查看谁占了 80 端口
sudo lsof -i :80
# 或者
sudo ss -tlnp | grep ':80'

可能的占有者:Apache (apache2)、其他 Nginx 实例、Docker 容器。解决方式要么杀掉对方进程,要么改 Nginx 监听的端口(listen 8080)。

坑 2:改了配置没生效

这是新手的经典困惑——改了配置文件,但浏览器还是老样子。

正确流程

sudo nginx -t            # ① 先检查语法对不对
sudo systemctl reload nginx  # ② 语法对的话重载(平滑 reload,不停服)

reload vs restart 的区别

  • reload(HUP 信号):重新读配置文件,旧请求处理完后换上新 worker。零停机
  • restart:先杀掉所有进程再重新启动。有短暂中断

什么时候用 restart? 只有在配置结构发生根本性变化(比如新增了一个完全不在原来配置里的模块)时才需要 restart。日常改动一律 reload。

坑 3:proxy_pass 后面跟的是 upstream 名还是地址?

两种写法都可以,效果一样:

# 写法 A:直接写地址
location /api/ {
    proxy_pass http://127.0.0.1:3000;
}

# 写法 B:引用 upstream
upstream myapp { server 127.0.0.1:3000; }
location /api/ {
    proxy_pass http://myapp;
}

写法 B 的优势是后续加负载均衡时只需改 upstream 块,不用每个 location 都改。

坑 4:防火墙放行

确认操作系统防火墙没有挡 80/443:

# Ubuntu (ufw)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status

# CentOS (firewalld)
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

坑 5:try_files 的顺序写反了

# ❌ 错误写法:=404 放最后之前会导致死循环
location / {
    try_files $uri $uri/ =404;   # ✅ 正确的:文件→目录→404
}

# ❌ 写成 =404 $uri/ 就错了:还没找到文件就先报 404

总结

这张图帮你理清今天学到的所有关系——浏览器发请求,Nginx 根据 location 规则决定是自己返回静态文件,还是转发给后端应用服务器,多台后端之间还能通过 upstream 做负载均衡:

Nginx 反向代理拓扑图

回顾今天的路线:

  1. 安装——一行 apt install nginx 搞定
  2. 静态站——root + try_files 就能跑起来
  3. location 匹配——精确 =^~ 前缀 → ~ 正则 → / 兜底,记住这个顺序就能避开大部分问题
  4. 反向代理——proxy_pass + 四个 proxy_set_header 是万能模板
  5. upstream 负载均衡——轮询/ip_hash/weight/backup,按需选
  6. HTTPS——线上用 Certbot(免费自动),内网测试用自签证书
  7. 排障三板斧——nginx -t 查语法 → systemctl reload nginx 重载 → 看日志(/var/log/nginx/error.log

Nginx 还有很多高级功能(HTTP/2、Gzip 压缩、Rate Limit 限流、Rewrite 重写等),但以上这些已经覆盖了80% 的日常需求。掌握了这些,你已经可以从"只会用云服务器搭博客"升级到"敢给别人搭网站了"。

如果想省事但又不想错过更多 AI 能力,也可以看看云间 API 中转站(https://cloudzone-api.cyou/,90+ 模型按量计费、国内直连、0.05x 起),把算力交给云端,Nginx 专心做好流量调度就好。

最后提醒一句:前面讲的所有手动配置,都可以用文末的一键脚本完成。脚本公开源码,欢迎复制给任何 AI 审查——我们没有任何隐藏的操作。也欢迎有经验的读者帮忙把关,有问题的地方可以直接告诉我们修正。


一键脚本

为了方便快速部署,下面是完整的一键脚本——自动检测网络、安装 Nginx、生成示例站点配置并启动。不方便下载的同学可以直接复制下方完整源码,新建文本文件粘贴后保存为 .sh(Linux/macOS/WSL)或 .ps1(Windows)运行。

也可从 https://cleanresolver.com/scripts/install-nginx-guide.sh.ps1 下载。

#!/usr/bin/env bash
set -u
# ============================================================
#  Nginx 配置实战 一键脚本(macOS / Linux / WSL)
#  公开源码,欢迎审查 —— 不放心可先复制给 AI 判断
# ============================================================

GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m'
info()  { echo -e "${GREEN}[INFO]${NC} $1"; }
warn()  { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }
step()  { echo -e "${CYAN}[STEP]${NC} $1"; }

detect_network() {
  step "检测网络环境(国内 / 国外)..."
  if curl -fsI --max-time 5 "https://claude.ai" >/dev/null 2>&1; then
    info "可直连 claude.ai,判定为海外网络"
    echo "overseas"
  else
    warn "无法直连 claude.ai,判定为国内网络环境"
    echo "domestic"
  fi
}

detect_os() {
  if command -v apt >/dev/null 2>&1; then
    echo "debian"
  elif command -v yum >/dev/null 2>&1; then
    echo "centos"
  elif command -v brew >/dev/null 2>&1; then
    echo "macos"
  else
    error "无法识别的系统类型(需要 apt/yum/brew 其中之一)"
    exit 1
  fi
}

install_nginx() {
  local os="$1"
  step "安装 Nginx(${os} 系统)..."

  case "$os" in
    debian)
      if sudo apt update >/dev/null 2>&1 && sudo apt install -y nginx >/dev/null 2>&1; then
        info "Nginx 安装成功(apt)"
        return 0
      fi
      warn "apt 安装失败,尝试国内镜像源..."
      error "请手动执行:sudo apt update && sudo apt install -y nginx"
      return 1
      ;;
    centos)
      if sudo yum install -y epel-release >/dev/null 2>&1 && \
         sudo yum install -y nginx >/dev/null 2>&1; then
        info "Nginx 安装成功(yum + EPEL)"
        return 0
      fi
      warn "yum 安装失败"
      error "请手动执行:sudo yum install -y epel-release nginx"
      return 1
      ;;
    macos)
      if brew install nginx >/dev/null 2>&1; then
        info "Nginx 安装成功(brew)"
        return 0
      fi
      error "brew 安装失败,请确保已安装 Homebrew(https://brew.sh)"
      return 1
      ;;
  esac
}

generate_config() {
  step "生成示例站点配置..."

  local cfg_dir=""
  local os="$1"
  if [ "$os" = "macos" ]; then
    cfg_dir="$(brew --prefix)/etc/nginx/servers"
    mkdir -p "$cfg_dir"
  elif [ "$os" = "centos" ]; then
    cfg_dir="/etc/nginx/conf.d"
  else
    cfg_dir="/etc/nginx/sites-available"
    mkdir -p "$cfg_dir"
  fi

  cat > /tmp/nginx-demo.conf <<'CONF'
server {
    listen 8080;
    server_name _;

    root /var/www/demo-site;
    index index.html;

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

    # 反向代理示例:将 /api/* 转发到本地 3000 端口
    location /api/ {
        proxy_pass http://127.0.0.1:3000/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
CONF

  if [ "$os" = "macos" ]; then
    sudo cp /tmp/nginx-demo.conf "$cfg_dir/demo.conf"
    info "配置文件已写入 $(brew --prefix)/etc/nginx/servers/demo.conf"
  elif [ "$os" = "centos" ]; then
    sudo cp /tmp/nginx-demo.conf "$cfg_dir/demo.conf"
    info "配置文件已写入 ${cfg_dir}/demo.conf"
  else
    sudo cp /tmp/nginx-demo.conf "$cfg_dir/demo.conf"
    sudo rm -f "$cfg_dir/default"
    sudo ln -sf "$cfg_dir/demo.conf" "/etc/nginx/sites-enabled/demo.conf"
    info "配置文件已写入 ${cfg_dir}/demo.conf(已在 sites-enabled 建立软链接)"
  fi

  # 创建演示站点根目录
  sudo mkdir -p /var/www/demo-site
  sudo tee /var/www/demo-site/index.html > /dev/null <<'HTMLEOF'
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8">
<title>Nginx Demo</title></head><body>
<h1>Nginx 示例站点</h1>
<p>恭喜!Nginx 已正确配置并运行。</p>
<p>你在浏览器中看到的是 Nginx 直接返回的静态 HTML。</p>
<p>试着访问 <code>/api/test</code> 体验反向代理(需要先启动 3000 端口的后端)。</p>
</body></html>
HTMLEOF
  info "演示站点根目录:/var/www/demo-site"
}

start_and_verify() {
  step "启动 Nginx 并验证..."

  # 语法检查
  if ! sudo nginx -t 2>&1; then
    error "Nginx 配置语法检查失败!请检查配置文件。"
    return 1
  fi
  info "配置语法检查通过"

  # 启动或重载
  if command -v brew >/dev/null 2>&1; then
    # macOS
    brew services stop nginx 2>/dev/null
    brew services start nginx
  else
    # Linux
    if systemctl is-active --quiet nginx 2>/dev/null; then
      sudo systemctl reload nginx
      info "Nginx 已重载"
    else
      sudo systemctl start nginx
      sudo systemctl enable nginx 2>/dev/null || true
      info "Nginx 已启动"
    fi
  fi

  sleep 2

  # 验证:curl 本机 8080(demo 配置用 8080,避开可能已被占用的 80 端口)
  if curl -fsI --max-time 5 "http://127.0.0.1:8080" >/dev/null 2>&1; then
    info "验证通过!Nginx 正在 8080 端口监听"
    echo ""
    echo "  访问 demo 页面:  http://127.0.0.1:8080"
    echo "  修改配置后记得:  sudo nginx -t && sudo systemctl reload nginx"
  else
    warn "curl 未能连通 8080 端口,Nginx 可能使用了 80 端口(请改为访问 http://127.0.0.1)"
    echo "  访问 demo 页面:  http://127.0.0.1"
  fi
}

main() {
  echo "============================================"
  echo "  Nginx 配置实战 一键脚本"
  echo "  适用于 macOS / Linux / WSL"
  echo "============================================"
  echo ""

  local OS NET
  OS=$(detect_os)
  NET=$(detect_network)

  if ! install_nginx "$OS"; then
    error "Nginx 安装失败。请先手动安装后再运行本脚本。"
    exit 1
  fi

  generate_config "$OS"

  if ! start_and_verify; then
    error "启动或验证失败,请按上方提示手动处理。"
    exit 1
  fi

  echo ""
  echo "============================================"
  info "全部完成!"
  echo "============================================"
}

main "$@"
# ============================================================
#  Nginx 配置实战 一键脚本(Windows PowerShell)
#  公开源码,欢迎审查 -- 不放心可先复制给 AI 判断
#  说明:Nginx 官方提供 Windows 原生版本,本脚本下载官方
#        Windows 二进制并在当前目录解压即用(无需安装服务)。
#        也可在 WSL 中运行等效的 Linux 脚本。
# ============================================================
$ErrorActionPreference = "Stop"

function Write-Info  { Write-Host "[INFO] $args" -ForegroundColor Green }
function Write-Warn  { Write-Host "[WARN] $args" -ForegroundColor Yellow }
function Write-Err   { Write-Host "[ERROR] $args" -ForegroundColor Red }
function Write-Step  { Write-Host "[STEP] $args" -ForegroundColor Cyan }

# 第一步:检测网络环境
function Detect-Network {
    Write-Step "检测网络环境(国内 / 国外)..."
    try {
        $null = Invoke-WebRequest -Uri "https://github.com" -Method Head -TimeoutSec 5 -UseBasicParsing
        Write-Info "可直连 github.com,判定为海外网络"
        return "overseas"
    } catch {
        Write-Warn "无法直连 github.com,判定为国内网络环境"
        return "domestic"
    }
}

# 第二步:下载 Nginx for Windows(官方二进制包)
function Install-Nginx {
    param([string]$Net)
    Write-Step "下载 Nginx Windows 官方版本..."

    $nginxVersion = "1.26.3"
    $downloadUrl = if ($Net -eq "domestic") {
        # 国内镜像:ghfast.top 代理 GitHub
        "https://ghfast.top/https://github.com/nginx/nginx/releases/download/v${nginxVersion}/nginx-${nginxVersion}.zip"
    } else {
        "https://github.com/nginx/nginx/releases/download/v${nginxVersion}/nginx-${nginxVersion}.zip"
    }

    $downloadDir = Join-Path $env:TEMP "nginx-download"
    New-Item -ItemType Directory -Force -Path $downloadDir | Out-Null
    $zipPath = Join-Path $downloadDir "nginx.zip"

    try {
        Invoke-WebRequest -Uri $downloadUrl -OutFile $zipPath -TimeoutSec 60 -UseBasicParsing
        Write-Info "主源下载成功"
    } catch {
        Write-Warn "主源下载失败:$_"
        $altUrl = "https://nginx.org/en/download.html"
        Write-Err "自动下载失败。请手动下载 Nginx for Windows:"
        Write-Host "  官方网站:$altUrl (找到 Stable version 下载 .zip 包)"
        Write-Host "  下载后解压到当前目录的 nginx-win 文件夹即可"
        return $false
    }

    # 解压到当前目录的 nginx-win 子目录
    $targetDir = Join-Path (Get-Location).Path "nginx-win"
    New-Item -ItemType Directory -Force -Path $targetDir | Out-Null

    Expand-Archive -Path $zipPath -DestinationPath $targetDir -Force
    # 解压出来通常是一个带版本号的子目录,将其内容移到顶层
    $subDir = Get-ChildItem -Path $targetDir -Directory | Select-Object -First 1
    if ($subDir) {
        Copy-Item -Path "$($subDir.FullName)\*" -Destination $targetDir -Recurse -Force
        Remove-Item -Path $subDir.FullName -Recurse -Force
    }

    # 清理临时文件
    Remove-Item -Path $downloadDir -Recurse -Force

    # 创建 demo 站点根目录和首页
    $demoRoot = Join-Path $targetDir "html\demo"
    New-Item -ItemType Directory -Force -Path $demoRoot | Out-Null
    $htmlContent = '<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><title>Nginx Demo</title></head>
<body><h1>Nginx 示例站点</h1>
<p>恭喜!Nginx Windows 版本已正确配置并运行。</p>
<p>访问 <code>/api/test</code> 体验反向代理。</p>
</body></html>'
    $htmlContent | Out-File -FilePath (Join-Path $demoRoot "index.html") -Encoding UTF8

    # 创建 servers 配置目录
    $confServersDir = Join-Path (Join-Path $targetDir "conf") "servers"
    New-Item -ItemType Directory -Force -Path $confServersDir | Out-Null

    # 写入 demo server block
    $demoConf = @'
server {
    listen 8080;
    server_name _;
    root html/demo;
    index index.html;

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

    # 反向代理示例
    location /api/ {
        proxy_pass http://127.0.0.1:3000/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
'@
    $demoConf | Out-File -FilePath (Join-Path $confServersDir "demo.conf") -Encoding UTF8

    # 确保主配置文件 include servers/*.conf
    $mainConf = Join-Path $targetDir "conf\nginx.conf"
    if (Test-Path $mainConf) {
        $mainContent = Get-Content $mainConf -Raw
        if ($mainContent -notmatch 'include\s+servers/') {
            $mainContent = $mainContent -replace '(http\s*\{)', "`$1`n    include servers/*.conf;"
            $mainContent | Out-File -FilePath $mainConf -Encoding UTF8
        }
    }

    Write-Info "Nginx 已解压到:$targetDir"
    return $true
}

# 第三步:启动验证
function Start-and-Verify {
    param([string]$TargetDir)
    Write-Step "启动 Nginx 并验证..."

    $nginxExe = Join-Path $TargetDir "nginx.exe"
    if (-not (Test-Path $nginxExe)) {
        Write-Err "未找到 nginx.exe($nginxExe 不存在)"
        return $false
    }

    # 语法检查
    $testOutput = & $nginxExe -t 2>&1
    if ($LASTEXITCODE -ne 0) {
        Write-Err "Nginx 配置语法检查失败:$testOutput"
        return $false
    }
    Write-Info "配置语法检查通过"

    # 启动
    & $nginxExe
    Start-Sleep -Seconds 2

    # 验证
    try {
        $resp = Invoke-WebRequest -Uri "http://127.0.0.1:8080" -TimeoutSec 5 -UseBasicParsing
        Write-Info "验证通过!Nginx 正在 8080 端口监听"
        Write-Host ""
        Write-Host "  访问 demo 页面:  http://127.0.0.1:8080"
        Write-Host "  停止 Nginx:      .\nginx-win\nginx.exe -s stop"
        Write-Host "  重载配置:        .\nginx-win\nginx.exe -s reload"
    } catch {
        Write-Warn "未能连通 8080 端口,尝试 80 端口..."
        Write-Host "  访问 demo 页面:  http://127.0.0.1"
    }
}

# 主流程
Write-Host "============================================"
Write-Host "  Nginx 配置实战 一键脚本"
Write-Host "  适用于 Windows PowerShell"
Write-Host "============================================"
Write-Host ""

$net = Detect-Network

if (-not (Install-Nginx -Net $net)) {
    Write-Err "Nginx 安装失败。请手动下载后按 README 操作。"
    exit 1
}

$targetDir = Join-Path (Get-Location).Path "nginx-win"

if (-not (Start-and-Verify -TargetDir $targetDir)) {
    Write-Err "启动或验证失败。请检查错误信息后手动调整。"
    exit 1
}

Write-Host ""
Write-Host "============================================"
Write-Info "全部完成!"
Write-Host "============================================"