域名购买与 HTTPS 配置过程

· coding · #博客搭建

域名购买与 HTTPS 配置过程

适用场景:个人博客 · 香港服务器(免备案)· Docker Compose 部署 · 大陆读者为主 说明:本文所有真实域名、IP、邮箱、密钥均以占位符代替,使用时替换成你自己的值。


写在前面

给博客配域名,是「从能访问到像样」的关键一步。本文记录一次完整实践:在香港服务器上,把原本「IP + 端口」的博客升级为「域名 + 子域名 + HTTPS」,顺带讲清楚 DNS、Cloudflare、CDN 这三个最容易混淆的概念,以及证书签发、配置改造、部署验证的全流程。

整个过程不需要 ICP 备案(香港服务器免备案),全程使用免费方案(Let's Encrypt 证书 + Cloudflare DNS),适合个人博客照做。建议先读第 2 节搞懂概念,再按目录跳到对应章节动手。


目录

  1. 域名购买
  2. 三个关键概念
  3. DNS 解析
  4. 服务器侧配置改造
  5. 签发 HTTPS 证书
  6. 第三方回调更新
  7. 打包、上传与部署
  8. 安全提醒
  9. 验证清单

1. 域名购买

1.1 后缀选择

后缀 评价 建议
.com 通用、续费稳定、最保值 ✅ 首选
.me 「我」的含义,适合个人 ✅ 备选
.dev 强制 HTTPS ⚠️ 可用但配置略麻烦
.io 技术圈流行 ❌ 续费贵
.top / .xyz 首年便宜 ⚠️ 垃圾站扎堆,影响形象

香港服务器不需要 ICP 备案,域名买来解析后即可直接用,这是最大便利。

1.2 注册商选择

核心判断点:大陆注册商要实名认证,海外注册商不用。

注册商 优势 注意
Cloudflare Registrar 成本价、续费不涨价、免费隐私保护 必须用它的 DNS
Porkbun 便宜、免费隐私保护 界面英文
Namecheap / Dynadot 老牌、支持支付宝 注意续费价
阿里云 / 腾讯云 / 西部数码 国内支付方便 需实名认证(不备案,但实名还是要)

1.3 过户与实名

1.4 买之前避坑

  1. 看续费价,别看首年价。
  2. 开 WHOIS 隐私保护,防姓名/邮箱公开。
  3. 开自动续费,避免到期进入赎回期(赎回费上百刀)。
  4. 别买太冷门的后缀,邮件易进垃圾箱。

2. 三个关键概念:DNS / Cloudflare / CDN

2.1 DNS 解析:域名 → IP 的「电话本」

2.2 Cloudflare 是什么,和 DNS 什么关系

Cloudflare 「身兼两职」,这是最容易混淆的地方:

两件事是分开的:可以只用 Cloudflare 的 DNS,不用它的 CDN。

2.3 CDN 是什么

CDN = 内容分发网络,把网页/图片缓存到离用户近的节点,就近访问。

重要结论:香港服务器 + 大陆读者,免费海外 CDN(Cloudflare 橙云)会绕路变慢,因为免费版在大陆没有边缘节点。真正快的大陆 CDN(阿里云/腾讯云)又需要 ICP 备案。所以个人博客阶段直接「灰云直连 + 源站 HTTPS」最快最省心。


3. DNS 解析

前置条件:域名已过户完成 + 实名通过。假设服务器公网 IP 为 1.2.3.4(替换成你的)。

3.1 方法 A:注册商 DNS(新手推荐,最简单)

在注册商后台加 4 条 A 记录:

主机记录 类型 记录值 作用
@ A 1.2.3.4 主域名
www A 1.2.3.4 www 前缀
admin A 1.2.3.4 后台管理子域名
img A 1.2.3.4 图片子域名

验证:

ping yourdomain.top     # 看到返回 1.2.3.4 即生效
nslookup yourdomain.top

生效需几分钟到几小时(各地 DNS 缓存不同)。

3.2 方法 B:Cloudflare DNS(进阶,灰云直连)

  1. dash.cloudflare.com 添加站点 → 选 Free 计划。
  2. Cloudflare 给出两个 NS 地址(形如 xxx.ns.cloudflare.com)。
  3. 回注册商,把域名的「域名服务器 / DNS」改成这两个 NS,删掉原来的。
  4. 等 NS 生效(几小时到 48 小时),Cloudflare 状态变 Active。
  5. 在 Cloudflare → DNS → Records 加同样 4 条 A 记录,每条把代理开关切成灰云(DNS only)。

⚠️ 关键:默认是橙云(Proxied),必须点成灰云,否则大陆访问走 Cloudflare 代理反而变慢。

3.3 两条路线对比

方法 A:注册商 DNS 方法 B:Cloudflare DNS
步骤 少 多(改 NS)
生效等待 分钟~小时 小时~48h
抗 DDoS / 功能 基础 强
大陆访问速度 一样(都直连香港) 一样(灰云直连)

4. 服务器侧配置改造

背景:博客架构为「前台 80 / 后台 8001 / 后端 8080 / 图片 MinIO 9000」。改造目标是把「IP + 端口」升级为「域名 + 子域名 + HTTPS」,用三个子域名取代端口:

  • yourdomain.top / www.yourdomain.top → 前台
  • admin.yourdomain.top → 后台
  • img.yourdomain.top → 图片(MinIO)

4.1 最终 nginx 配置 nginx.conf

events {
    worker_connections 1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    sendfile      on;
    keepalive_timeout 65;
    client_max_body_size 50m;

    gzip on;
    gzip_min_length 1k;
    gzip_comp_level 4;
    gzip_types text/plain text/css application/javascript application/json image/jpeg image/png image/gif;

    # ============ 前台博客 ============
    server {
        listen 80;
        server_name yourdomain.top www.yourdomain.top;
        location /.well-known/acme-challenge/ { root /var/www/certbot; }
        location / { return 301 https://$host$request_uri; }
    }

    server {
        listen 443 ssl;
        server_name yourdomain.top www.yourdomain.top;

        ssl_certificate     /etc/letsencrypt/live/yourdomain.top/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/yourdomain.top/privkey.pem;

        root /var/www/aurora/blog;
        index index.html;

        location / { try_files $uri $uri/ /index.html; }

        location ^~ /api/ {
            proxy_pass http://backend:8080/;
            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;
        }
    }

    # ============ 后台管理 ============
    server {
        listen 80;
        server_name admin.yourdomain.top;
        location /.well-known/acme-challenge/ { root /var/www/certbot; }
        location / { return 301 https://$host$request_uri; }
    }

    server {
        listen 443 ssl;
        server_name admin.yourdomain.top;

        ssl_certificate     /etc/letsencrypt/live/yourdomain.top/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/yourdomain.top/privkey.pem;

        root /var/www/aurora/admin;
        index index.html;

        location / { try_files $uri $uri/ /index.html; }

        location ^~ /api/ {
            proxy_pass http://backend:8080/;
            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;
        }
    }

    # ============ 图片 → MinIO ============
    server {
        listen 80;
        server_name img.yourdomain.top;
        location /.well-known/acme-challenge/ { root /var/www/certbot; }
        location / { return 301 https://$host$request_uri; }
    }

    server {
        listen 443 ssl;
        server_name img.yourdomain.top;

        ssl_certificate     /etc/letsencrypt/live/yourdomain.top/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/yourdomain.top/privkey.pem;

        location / {
            proxy_pass http://minio:9000;
            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;
        }
    }
}

为什么加 img 子域名:主站上 HTTPS 后,浏览器会拦截「HTTPS 页面里嵌 HTTP 图片」(混合内容),所以 MinIO 也用 nginx 反代到 img 子域名,让图片走 HTTPS。

4.2 nginx 配置详解

nginx 在这里扮演两个角色:静态文件服务器(托管前端 dist)和反向代理(把 /api 转给后端、把 img 转给 MinIO)。下面按「层级 → 关键指令」拆解这份配置。

4.2.1 配置文件的层级结构

events     # 连接处理相关
http       # HTTP 全局配置
  server   # 一个「虚拟主机」= 一个域名/端口组合
    location  # 一条 URL 路径匹配规则

4.2.2 关键指令逐个解释

指令 作用 本例说明
listen 80 / listen 443 ssl 监听端口,ssl 表示该端口走 TLS 80 用于跳转和证书验证,443 用于实际加密服务
server_name 按域名匹配虚拟主机 nginx 用请求的 Host 头匹配 server_name,命中的 server 块处理该请求
root 静态文件根目录 root /var/www/aurora/blog; 表示访问 /index.html 时去该目录找文件
index 默认首页文件 访问 / 时返回 index.html
location / 匹配所有路径 前缀匹配,优先级最低的兜底
location ^~ /api/ 前缀匹配,命中后不再检查正则 ^~ 表示「前缀匹配且优先」,用于把 API 请求转给后端
try_files 按顺序尝试找文件,找不到走最后一个 见下方 SPA 说明
proxy_pass 反向代理,把请求转发给上游 把请求转给后端容器 backend:8080 或 minio:9000
proxy_set_header 转发时改写请求头 让后端能拿到真实客户端信息
return 301 返回重定向 把 HTTP 请求 301 永久跳转到 HTTPS
ssl_certificate 指定证书文件 用 fullchain 证书链

4.2.3 server_name:一个域名一个「家」

nginx 通过请求里的 Host 头(浏览器自动带上域名)来匹配 server_name,于是:

旧配置里写 server_name _;,_ 是通配符,表示「不管什么域名都进这个块」——所以以前用 IP 也能访问。改成具体域名后,只有匹配的域名才进对应站点,更安全也更清晰。

4.2.4 try_files $uri $uri/ /index.html:SPA 路由的关键

前端是单页应用(SPA),路由(如 /about)其实由前端 JS 在浏览器里处理,服务器上并没有 /about 这个文件。try_files 的意思是:

  1. 先找 $uri(如 /about 这个文件)——找不到;
  2. 再找 $uri/(如 /about/ 目录)——也找不到;
  3. 最后回退到 /index.html,交给前端路由处理。

没有这一行,直接刷新 /about 这类子页面会报 404。

4.2.5 proxy_pass 反向代理:转发给后端

浏览器 → nginx(:443) → /api 请求 → backend:8080
                      → /     请求 → 本地静态文件
                      → img   请求 → minio:9000

4.2.6 proxy_set_header:让后端知道「真实请求」是谁

请求头 含义
Host 原始域名,后端据此识别站点
X-Real-IP 客户端真实 IP
X-Forwarded-For 完整的代理链(客户端 → 代理)
X-Forwarded-Proto 原始协议(http/https),后端据此判断是否走 HTTPS

不设置这些,后端看到的 IP 是 nginx 容器的内网 IP,日志里全是同一个地址,排查问题无从下手。

4.2.7 为什么每个子域名都要「80 + 443」两个块

HTTPS 的标准做法是**「80 负责跳转,443 负责加密」**:

两个块共用同一个 server_name,nginx 按端口区分,互不干扰。

4.3 临时 nginx 配置 nginx-http.conf(首次签证书用)

作用:第一次签证书时,最终版引用的证书文件还不存在,nginx 会启动失败。所以先用一个纯 HTTP、无 SSL 的临时配置,让 80 端口能正常响应 certbot 的验证请求。

events {
    worker_connections 1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    sendfile      on;
    keepalive_timeout 65;
    client_max_body_size 50m;

    gzip on;
    gzip_min_length 1k;
    gzip_comp_level 4;
    gzip_types text/plain text/css application/javascript application/json image/jpeg image/png image/gif;

    server {
        listen 80;
        server_name yourdomain.top www.yourdomain.top;
        location /.well-known/acme-challenge/ { root /var/www/certbot; }
        root /var/www/aurora/blog;
        index index.html;
        location / { try_files $uri $uri/ /index.html; }
        location ^~ /api/ {
            proxy_pass http://backend:8080/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }

    server {
        listen 80;
        server_name admin.yourdomain.top;
        location /.well-known/acme-challenge/ { root /var/www/certbot; }
        root /var/www/aurora/admin;
        index index.html;
        location / { try_files $uri $uri/ /index.html; }
        location ^~ /api/ {
            proxy_pass http://backend:8080/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }

    server {
        listen 80;
        server_name img.yourdomain.top;
        location /.well-known/acme-challenge/ { root /var/www/certbot; }
        location / {
            proxy_pass http://minio:9000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

4.4 docker-compose.yml 改动

nginx 服务:加 443 端口、挂证书卷,移除原来的 8001 端口;新增 certbot 自动续期容器。

  nginx:
    image: nginx:alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./frontend/blog:/var/www/aurora/blog:ro
      - ./frontend/admin:/var/www/aurora/admin:ro
      - ./certbot/www:/var/www/certbot:ro
      - ./certbot/conf:/etc/letsencrypt:ro
    depends_on:
      - backend

  certbot:
    image: certbot/certbot
    restart: unless-stopped
    volumes:
      - ./certbot/www:/var/www/certbot:rw
      - ./certbot/conf:/etc/letsencrypt:rw
    command: /bin/sh -c "trap exit TERM; while :; do certbot renew --webroot -w /var/www/certbot --quiet; sleep 12h & wait $${!}; done"

4.5 后端 application-prod.yml 改动

upload:
  mode: minio
  minio:
    url: https://img.yourdomain.top/aurora/    # 原来是 http://服务器IP:9000/aurora/
    endpoint: http://minio:9000
    accessKey: 你的accessKey
    secretKey: 你的secretKey
    bucketName: aurora

website:
  url: https://yourdomain.top                  # 原来是 https://前台域名

4.6 前端 config.ts 改动(GitHub 登录回调)

githubLogin: {
  CLIENT_ID: '你的ClientID',
  REDIRECT_URI: 'https://yourdomain.top/oauth/login/github'   // 原来是 http://服务器IP/oauth/login/github
}

5. 签发 HTTPS 证书(certbot + Let's Encrypt)

5.1 certbot 与 Let's Encrypt 是什么

5.2 三种域名验证方式(怎么证明域名是你的)

方式 原理 适用场景
HTTP-01 + webroot 把验证文件放到网站目录,Let's Encrypt 通过 http://域名/.well-known/... 访问验证 80 端口已被 nginx 占用(本文用这个)
HTTP-01 + standalone certbot 自己临时起一个服务器占用 80 端口验证 80 端口空闲、暂时没跑网站
DNS-01 在 DNS 加一条 TXT 记录验证 泛域名证书、80 端口被防火墙挡、用 CDN

本文选 webroot 的原因:nginx 一直占着 80 端口,且 webroot 方式能天然配合后面的自动续期(续期时 nginx 无需停服)。

5.3 关键点:为什么「先签证书、再启用最终配置」

最终版 nginx.conf 里写了:

ssl_certificate /etc/letsencrypt/live/yourdomain.top/fullchain.pem;

如果这个文件还没签出来就启动 nginx,nginx 会直接报错退出。所以流程必须是:

  1. 先用纯 HTTP 的临时配置让 nginx 在 80 端口跑起来;
  2. 用 webroot 方式签出证书;
  3. 再换成带 SSL 的最终配置重启。

5.4 签发命令逐段拆解

cd ~/aurora

# ① 首次签证书前,临时用纯 HTTP 配置
cp nginx/nginx.conf nginx/nginx-final.conf
cp nginx/nginx-http.conf nginx/nginx.conf
docker compose up -d nginx

# ② 签发证书(webroot 方式,一次覆盖 4 个域名)
docker compose run --rm certbot certonly --webroot -w /var/www/certbot \
  -d yourdomain.top -d www.yourdomain.top \
  -d admin.yourdomain.top -d img.yourdomain.top \
  --email 你的邮箱 --agree-tos --no-eff-email

# ③ 换回最终 HTTPS 配置并重启
cp nginx/nginx-final.conf nginx/nginx.conf
docker compose restart nginx

# ④ 启动自动续期容器(之后每 12h 检查一次,快到期自动续)
docker compose up -d certbot

参数含义:

参数 含义
certonly 只签发证书,不自动改 nginx 配置(比 run 更可控)
--webroot 用 webroot 方式验证
-w /var/www/certbot 验证文件放到哪个目录(对应 nginx 里 root /var/www/certbot)
-d 域名(多个) 一次签一张多域名(SAN)证书,4 个域名共用一张
--email 到期提醒邮箱
--agree-tos / --no-eff-email 同意服务条款 / 不接收推广邮件
--rm 容器跑完自动删除,不留垃圾

-d 一次写多个域名 = 一张证书覆盖多个域名,所以 3 个子域名只需签一张,证书里同时含 yourdomain.top、www、admin、img。

5.5 证书文件结构(nginx 里为什么用 fullchain.pem)

签完后证书在 /etc/letsencrypt/live/yourdomain.top/,几个文件各有用处:

文件 作用
fullchain.pem 证书 + 中间证书链(nginx 用这个)
privkey.pem 私钥(nginx 用这个,绝不能泄露)
cert.pem 仅服务器证书
chain.pem 中间证书链

用 fullchain.pem 而不是 cert.pem:部分浏览器/客户端需要完整的证书链才能验证,缺了中间证书会报「证书不受信任」。

5.6 自动续期机制

5.7 常见问题排查

现象 可能原因 解决
签证书报「connection refused / timeout」 80 端口没开放或没通 检查安全组放行 80;确认 DNS 已解析到本机
报「no valid A records」 域名解析还没生效 等解析生效后再签(ping 确认)
nginx 启动失败,日志「cannot load certificate」 证书文件不存在 说明没按顺序——先临时配置签证书,再换最终配置
续期失败 80 端口被占 / 目录挂载错 确认 nginx 的 /.well-known 指向 /var/www/certbot
浏览器提示证书不受信任 用了 cert.pem 而非 fullchain.pem 改回 fullchain.pem

6. 第三方回调更新

GitHub OAuth App(在 github.com 手动改,代码里改不了)

到 GitHub → Settings → Developer settings → OAuth Apps:

不改的话 GitHub 登录会回调失败。

邮件 / 其他

如果 SMTP、邮件通知里写了服务器 IP 或回信地址,也一并换成域名。


7. 打包、上传与部署

改了后端配置(application-prod.yml)和前端配置(config.ts)后,必须重新打包,因为配置打进 jar / dist。

7.1 本地打包

# 后端(IDEA:clean + package,JDK 8)
# 产物:aurora-springboot/target/aurora-springboot-0.0.1.jar

# 前端(Node 16)
cd aurora-vue/aurora-blog && npm run build
cd ../aurora-admin && npm run build

7.2 上传到服务器

scp aurora-deploy/nginx/nginx.conf      blog@你的服务器IP:~/aurora/nginx/
scp aurora-deploy/nginx/nginx-http.conf blog@你的服务器IP:~/aurora/nginx/
scp aurora-deploy/docker-compose.yml    blog@你的服务器IP:~/aurora/
scp aurora/aurora-springboot/target/aurora-springboot-0.0.1.jar blog@你的服务器IP:~/aurora/backend/
scp -r aurora/aurora-vue/aurora-blog/dist/.  blog@你的服务器IP:~/aurora/frontend/blog/
scp -r aurora/aurora-vue/aurora-admin/dist/. blog@你的服务器IP:~/aurora/frontend/admin/

7.3 启动

cd ~/aurora
docker compose up -d --build
docker compose ps          # 查看容器状态

8. 安全提醒

  1. 配置文件含密钥,务必保密:不要公开、不要分享含真实密码的配置。
  2. 规范做法:对外只保留脱敏模板(如 application-prod.yml.example),真实配置文件只在服务器上保存。
  3. 以下密钥若疑似泄露,立刻重置(后果最严重):
    • Gmail 应用密码
    • GitHub OAuth clientSecret
    • 数据库 / Redis / RabbitMQ 密码
    • MinIO accessKey / secretKey

9. 验证清单


写在最后

到这里,博客就完成了「IP → 域名 → HTTPS」的完整升级。回顾一下核心思路:

如果卡在某一步,优先检查三件事:A 记录是否生效、证书是否签成功、nginx 是否用的最终配置。

← 全部文章