从域名到生产部署

从域名到生产部署

ffy Lv3

在这次部署过程中,我从零开始搭建了一个完整的 Next.js + Nginx + pm2 的生产环境。

在介绍整体的配置流程之前,需要说明一系列配置的必要性——即论证“为什么不能只用公网 IP”

部署逻辑与原理

当服务器上的 Next.js 应用可以通过 http://<公网IP>:3000 被访问时,为什么还要购买域名、配置 SSL、再通过 Nginx 反向代理?

这个问题的答案既与网络协议有关,也与 iOS 平台的安全机制有关。

域名:互联网的稳定入口

公网 IP 虽然可以直接访问,但它并不是互联网通信中可靠的标识。

服务器的 IP 可能会因重启、迁移、负载均衡或云平台调整而变化。

而域名通过 DNS 系统 将请求动态解析到最新的服务器 IP,使访问入口稳定、可记忆、可扩展。

此外,许多应用层安全机制(例如 HTTPS 证书校验、跨域策略)都依赖域名而非 IP。

SSL 证书:建立加密信任链

HTTP 协议是明文传输,任何中间节点都能截获或篡改数据。

HTTPS 则在其上加入了 TLS/SSL 加密层,通过公私钥交换确保通信内容的机密性和完整性。

SSL 证书正是这一信任链的核心:它由权威 CA 签发,用于证明“访问的服务器确实是 xxx,而不是某个伪装者”。

为什么 iOS App 无法直接访问 HTTP?

自 iOS 9 起,苹果在系统层面引入 App Transport Security (ATS) 策略:

App 默认只能访问 HTTPS 域名,且证书必须有效、由受信任机构签发。

这意味着:

  • 若访问 http://<公网IP>,App 会直接拒绝请求;
  • 即使在 Info.plist 中临时开放 HTTP,也可能遭到 Apple 审核的拒绝;
  • 使用域名 + 有效证书是唯一符合 ATS 要求的正式方案。

Nginx:桥接安全与服务逻辑

Nginx 承担着“外部世界与应用服务之间的桥梁”角色。

它监听 80/443 端口,处理加密握手、转发流量到内部的 Node 服务端口(3000),同时还能进行日志、缓存、负载均衡。

这使得 Next.js 应用不需要直接暴露在公网环境下,而是由 Nginx 统一接入和管理。


总的来说,域名提供了身份,SSL 提供了信任,Nginx 提供了通道与隔离

而 iOS 的安全策略则决定了:仅配置公网 IP 与 HTTP 服务,无法满足现代应用的安全访问标准。

因此,接下来将按照上述逻辑,复盘配置的流程

域名与 SSL:建立可信入口

要让应用可被公网访问,第一步是申请一个域名。域名相当于互联网中的“门牌号”,用户访问 https://carboncoin.asia 的请求会通过 DNS 解析被指向服务器的公网 IP。

拷贝证书到服务器

假设本地已经从云厂商下载到了证书文件:

  • carboncoin.crt
  • carboncoin.key

先在服务器上为证书创建目录:

1
sudo mkdir -p /etc/nginx/ssl

使用 scp 将证书拷贝到服务器:

1
2
scp carboncoin.crt root@服务器IP:/etc/nginx/ssl/carboncoin.crt
scp carboncoin.key root@服务器IP:/etc/nginx/ssl/carboncoin.key

如果能够通过 GUI 操作,也可以直接在平台中创建文件

验证证书链

配置完成后,可以用 openssl 检查证书与链路是否正常:

1
openssl s_client -connect www.carboncoin.asia:443 -servername www.carboncoin.asia

在输出末尾看到:

1
Verify return code: 0 (ok)

说明证书链配置正确。


Nginx:反向代理与流量中枢

Nginx 是一个高性能的反向代理与静态资源服务器,它的主要作用是:

  • 接收外部的 HTTP/HTTPS 请求;
  • 将这些请求转发到运行在本地端口(如 3000)的 Node.js 应用;
  • 提供静态资源缓存、连接复用等能力。

安装与基本配置

以常见的 Linux 发行版为例,可以通过包管理器安装:

1
2
3
4
5
6
# CentOS / Rocky / OpenCloudOS
sudo yum install -y nginx

# 或 Ubuntu / Debian
# sudo apt-get update
# sudo apt-get install -y nginx

编辑 Nginx 配置(路径可能是 /etc/nginx/nginx.conf/etc/nginx/conf.d/default.conf):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 将HTTP请求重定向到HTTPS
server {
listen 80;
server_name carboncoin.asia www.carboncoin.asia;
return 301 https://$host$request_uri;
}

server {
listen 443 ssl;
server_name carboncoin.asia www.carboncoin.asia;

# 证书配置
ssl_certificate /etc/nginx/ssl/carboncoin.crt;
ssl_certificate_key /etc/nginx/ssl/carboncoin.key;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;

# 反向代理到Node.js
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}

整体流程:

  1. 用户访问 http://carboncoin.asia –> 被第一段配置 301 重定向到 https://carboncoin.asia
  2. Nginx 接收 HTTPS 请求并完成 TLS 握手
  3. 请求转发至本地端口 3000 的 Next.js 服务
  4. Node.js 生成响应,Nginx 通过 HTTPS 返回给客户端

检查配置并重载 Nginx

修改完成后,用以下命令验证配置语法:

1
sudo nginx -t

看到类似:

1
2
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

说明配置正确,可以重载:

1
2
3
4
5
6
7
8
# 若 Nginx 未启动
sudo systemctl start nginx

# 修改配置后重载
sudo systemctl reload nginx

# 设置开机自启
sudo systemctl enable nginx

此时,从外部访问 https://www.carboncoin.asia/ 就会被代理到本机 127.0.0.1:3000


DNS 解析:让域名指向服务器

先查看服务器的公网 IP,如果平台没有给出,可以在 ssh 中使用命令查看:

1
curl ifconfig.me

然后在 DNS 管理平台添加两条 A 记录

  • carboncoin.asia → 服务器公网 IP
  • www.carboncoin.asia → 服务器公网 IP

等待解析生效后,可以通过 dig 命令验证:

1
2
dig +short carboncoin.asia
dig +short www.carboncoin.asia

如果输出的 IP 与服务器公网 IP 一致,说明 DNS 解析已经正确


Next.js 构建与运行

Next.js 是 React 的全栈框架,提供了前后端统一的 App Router 架构。
在开发阶段,可以使用:

1
2
npm run dev
# => next dev --turbopack

Turbopack 只建议用于开发,生产构建需要使用标准的 next build。否则 App Router 下的 /api 路由可能根本没有被打包进 server 端,导致线上访问 /api/... 出现 404 或奇怪的文本输出。

什么是–turbopack

Turbopack is an incremental bundler optimized for JavaScript and TypeScript, written in Rust, and built into Next.js. You can use Turbopack with both the Pages and App Router for a much faster local development experience.

–turbopack 是 Next.js 官方在 13+ 版本 引入的新一代编译与打包引擎,用来替代传统的 Webpack

核心特性:

  • 增量编译:只编译修改过的文件,极大缩短冷启动时间
  • 多线程并行:利用多核 CPU 加速构建过程
  • 按需加载:只加载当前路由所需的代码,减小初始包体积

但是问题是,官方文档中提及--turbopack 当前主要用于 local development experience,在实际部署的时候,我同样发现使用该配置进行 build 然后 start,导致无法访问 /api 路由。因此最终的脚本配置如下:

1
2
3
4
5
6
7
"scripts": {
"dev": "next dev --turbopack",
"build": "prisma generate && next build",
"start": "next start -H 0.0.0.0 -p 3000",
"lint": "eslint",
"format": "prettier --write \"src/**/*.ts\""
}

其中 -H 0.0.0.0 是为了让 Next.js 服务器监听所有网络接口。


pm2:守护与持久化运行

pm2 是一个 Node.js 进程管理器,用来管理生产环境里的 Node 服务。它可以:

  • 在应用崩溃后自动重启;
  • 在系统重启后自动拉起进程;
  • 提供日志与监控。

使用 pm2 启动 Next.js

在项目根目录执行:

1
pm2 start "npm run start -- -p 3000" --name carboncoin

查看进程状态:

1
2
pm2 list
pm2 status carboncoin

查看日志:

1
pm2 logs carboncoin

保存当前进程并设置开机自启:

1
2
3
pm2 save
pm2 startup
# 按照输出提示执行一次对应的 systemd 命令

至此,即使服务器重启,Next.js 服务也会自动拉起,Nginx 仍然将外部请求转发到本地 3000 端口。


其他问题

iOS 与 SSL 缓存的隐性问题

在迁移到国内服务器后,我遇到过一个非常隐蔽的问题:

  • 我自己的 iOS 设备访问一切正常;
  • 新用户在首次访问时,却出现 TLS/SSL 证书链错误。

原因在于:iOS 系统和 App 都会缓存 SSL/TLS 相关的配置,尤其是:

  • 证书链的验证结果;
  • ATS(App Transport Security)的例外配置。

已经访问过旧环境的设备,可能还在沿用旧的验证结果,而新设备会直接按照最新配置进行验证,结果就会“分裂”成:

  • 老设备:依赖缓存,访问正常;
  • 新设备:严格校验新证书链,报 TLS/SSL 错误。

调试过程中的常用命令包括:

1
2
3
4
5
6
# DNS 解析检查
dig +short carboncoin.asia
dig +short www.carboncoin.asia

# 在服务器上进行证书链检查
openssl s_client -connect www.carboncoin.asia:443 -servername www.carboncoin.asia

Nginx 配置问题

在上述的 Nginx 配置中,Wi-Fi 环境访问正常,但是蜂窝网络下出现 503 upstream connect error.

问题源于部分移动运营商默认优先通过 HTTP/2 与服务器建立 TLS 连接,如果 Nginx 只支持 HTTP/1.1,可能在 TLS 协商阶段中断

因此,我们可以修改配置,提高兼容性与握手成功率:

  1. 启用 HTTP/2:

    1
    listen 443 ssl http2;
  2. 增加响应超时时间:

    1
    2
    3
    4
    proxy_connect_timeout 30s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;
    send_timeout 60s;
  3. 添加代理头信息:

    1
    2
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;

修改之后完整的 Nginx 配置;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# HTTP重定向到HTTPS
server {
listen 80;
server_name carboncoin.asia www.carboncoin.asia;

# 强制 HTTPS
return 301 https://$host$request_uri;
}

server {
listen 443 ssl http2;

...

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;

# 超时设置 防止移动网络慢触发 upstream error
proxy_connect_timeout 30s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
send_timeout 60s;

location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
  • 标题: 从域名到生产部署
  • 作者: ffy
  • 创建于 : 2025-11-08 14:13:29
  • 更新于 : 2025-11-08 15:23:41
  • 链接: https://ffy6511.github.io/2025/11/08/从域名到生产部署/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论