# Nginx 单端口多项目部署方案

## 目标

在同一台服务器、同一个公网 IP 上，让多个项目共同使用 `80` 和后续的 `443` 端口，同时保持项目之间互不覆盖。

本方案采用：

- 每个项目使用独立的内部端口；
- 每个项目使用独立的 Nginx `.conf` 文件；
- Nginx 根据请求的主机名 `server_name` 将流量转发到对应项目；
- 项目始终部署在网站根路径 `/`，不使用 `/a/`、`/b/` 这类子路径，避免修改应用路由、静态资源路径和后台地址。

当前银妆项目的内部服务地址为：

```text
127.0.0.1:8790
```

## 请求链路

临时展示阶段：

```text
http://yinzhuang.101-126-10-129.sslip.io
    → Nginx 80
    → 127.0.0.1:8790
    → 银妆项目
```

其他项目使用各自的临时主机名和内部端口，例如：

```text
http://project-b.101-126-10-129.sslip.io
    → Nginx 80
    → 127.0.0.1:8790
    → Project B
```

`sslip.io` 会把主机名中包含的 IP 自动解析到对应服务器，无需提前购买域名。它只适合临时开发和展示，不应作为正式生产域名。

## 一个项目一个配置文件

服务器配置目录示例：

```text
/etc/nginx/conf.d/
├── yinzhuang.conf
├── project-b.conf
└── other-project.conf
```

每个项目只维护自己的配置文件，不修改其他项目的配置。

银妆项目使用：

```text
/etc/nginx/conf.d/yinzhuang.conf
```

仓库中的配置源文件为：

```text
deploy/nginx/yinzhuang.conf
```

现有 GitLab CI 会把该文件安装到服务器的 `/etc/nginx/conf.d/yinzhuang.conf`。因此，银妆的 Nginx 修改必须提交到仓库，不能只在服务器上手工修改，否则后续部署会将手工修改覆盖。

## 临时 HTTP 配置

银妆项目可使用以下配置：

```nginx
server {
    listen 80;
    server_name yinzhuang.101-126-10-129.sslip.io;

    client_max_body_size 12m;

    location / {
        proxy_pass http://127.0.0.1:8790;
        proxy_http_version 1.1;

        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;
        proxy_set_header X-Forwarded-Host $host;
    }
}
```

另一个项目应在自己的 `.conf` 文件中配置，例如：

```nginx
server {
    listen 80;
    server_name project-b.101-126-10-129.sslip.io;

    location / {
        proxy_pass http://127.0.0.1:8790;
        proxy_http_version 1.1;

        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;
        proxy_set_header X-Forwarded-Host $host;
    }
}
```

虽然多个配置都声明了 `listen 80`，但 Nginx 会根据不同的 `server_name` 选择对应项目，因此不会产生端口冲突。

## 配置约束

- 每个项目必须使用不同的 `server_name`。
- 每个应用必须使用不同的内部端口。
- 不要在多个配置文件中重复声明相同的 `server_name`。
- 整台服务器最多保留一个 `default_server`，不要让每个项目都成为默认站点。
- Node/Astro 服务应只监听 `127.0.0.1`，不直接向公网开放内部端口。
- 云服务器安全组只需对公网开放 `80` 和 `443`；`8790` 等应用端口不应对公网开放。

## 应用和检查配置

修改配置后执行：

```bash
sudo nginx -t
sudo systemctl reload nginx
```

检查临时主机名是否解析到当前服务器：

```bash
dig +short yinzhuang.101-126-10-129.sslip.io A
```

预期返回：

```text
101.126.10.129
```

检查 HTTP 入口：

```bash
curl -I http://yinzhuang.101-126-10-129.sslip.io/
```

检查应用内部端口：

```bash
curl -I http://127.0.0.1:8790/
```

如果内部端口正常、临时主机名异常，应优先检查 Nginx 配置、安全组和防火墙。

## 正式域名上线

拿到正式域名后，不需要修改应用内部端口，也不需要把项目改成子路径部署。

外部人员需要完成：

1. 提供最终域名，例如 `example.com`；
2. 将域名的 DNS 解析到服务器公网 IP `101.126.10.129`；
3. 如果服务器位于中国大陆，完成所需的备案和接入手续。

后端部署人员需要完成：

1. 将 `server_name` 从临时主机名替换为正式域名；
2. 确认正式域名通过 HTTP 可以访问当前项目；
3. 在服务器申请正式域名的 HTTPS 证书；
4. 在仓库的 Nginx 配置中增加 `443 ssl`，并让 `80` 跳转到 HTTPS；
5. 更新项目中的站点规范地址，例如 Astro 的 `site` 配置；
6. 提交配置并通过现有 CI 部署。

正式域名的 HTTP 配置示例：

```nginx
server {
    listen 80;
    server_name example.com www.example.com;

    location / {
        proxy_pass http://127.0.0.1:8790;
        proxy_http_version 1.1;

        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;
        proxy_set_header X-Forwarded-Host $host;
    }
}
```

## 正式 HTTPS 配置

DNS 生效并确认 HTTP 可访问后，可在服务器申请证书：

```bash
sudo certbot certonly --nginx -d example.com -d www.example.com
```

证书通常保存在：

```text
/etc/letsencrypt/live/example.com/fullchain.pem
/etc/letsencrypt/live/example.com/privkey.pem
```

证书和私钥只保存在服务器，不提交到 Git。仓库只保存证书路径引用。

最终 Nginx 配置示例：

```nginx
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com www.example.com;

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

    client_max_body_size 12m;

    location / {
        proxy_pass http://127.0.0.1:8790;
        proxy_http_version 1.1;

        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;
        proxy_set_header X-Forwarded-Host $host;
    }
}
```

多个项目可以共同使用服务器的 `443` 端口。Nginx 会根据域名选择对应的 `server` 块和证书，不会影响已经部署的其他 HTTPS 网站。

申请证书后检查自动续期：

```bash
sudo certbot renew --dry-run
sudo systemctl status certbot.timer
```

## 从临时地址迁移到正式域名时的最小改动

采用本方案后，正式上线主要只修改：

1. Nginx 的 `server_name`；
2. Nginx 的 HTTPS 证书路径和 HTTP 跳转；
3. GitLab CI 中用于入口验证的 `PUBLIC_HOST`；
4. Astro 的正式站点地址 `site`（仅当最终域名与现有配置不一致时）。

应用仍然运行在 `127.0.0.1:8790`，页面路由、静态资源、后台地址和文章地址不需要因为域名迁移而修改。
