1. 最终目标

假设已有:

  • 一个域名:example.com
  • 一台拥有公网 IPv4 的 VPS:203.0.113.10
  • 主站应用监听:127.0.0.1:18080
  • 管理后台监听:127.0.0.1:18081
  • Nginx 已安装并监听公网 80/443

最终希望得到:

公网访问:

https://example.com
https://www.example.com
        ↓
      Nginx
        ↓
127.0.0.1:18080
        ↓
      主站


后台访问:

本地电脑
   ↓ SSH Tunnel
VPS 127.0.0.1:18081
   ↓
/admin

核心原则是:

主站公开,后台不公开。


2. 配置域名 DNS

在域名服务商的 DNS 控制台添加 A 记录:

记录类型 主机记录 记录值


A @ VPS 公网 IPv4 A www VPS 公网 IPv4

其中:

  • @ 表示根域名 example.com
  • www 表示 www.example.com

例如:

A    @      203.0.113.10
A    www    203.0.113.10

如果未来确实需要子域名,也可以添加:

A    admin    203.0.113.10

但对于只通过 SSH 隧道访问的后台,没有必要保留 admin 的公网 DNS 记录。


3. 检查 DNS 是否生效

macOS / Linux 可以使用:

dig +short example.com
dig +short www.example.com

正常情况下应返回 VPS 公网 IP。

也可以直接查询域名的权威 DNS:

dig NS example.com +short

然后:

dig @<权威DNS服务器> example.com A +short

Clash / 代理环境下的特殊情况

如果本机使用 Clash、Clash Verge 等代理,并启用了 Fake-IP 模式,可能看到类似:

198.18.x.x

这通常不是网站真实的公网 IP,而是代理软件分配的 Fake-IP。

因此不要仅凭本机普通 dig 结果判断 DNS 配置错误。

可以:

  1. 查询权威 DNS;
  2. 暂时绕过代理;
  3. 或直接在 VPS 上安装 dnsutils 后查询。

Ubuntu / Debian:

sudo apt update
sudo apt install dnsutils

4. 确认 VPS 服务监听状态

检查:

sudo ss -lntp | grep -E '18080|18081|:80|:443'

理想状态类似:

127.0.0.1:18080    gunicorn
127.0.0.1:18081    gunicorn
0.0.0.0:80         nginx
[::]:80            nginx
0.0.0.0:443        nginx
[::]:443           nginx

这里非常重要的一点是:

127.0.0.1:18080
127.0.0.1:18081

应用服务只绑定回环地址,而不是:

0.0.0.0:18080
0.0.0.0:18081

这样公网无法绕过 Nginx 直接访问 Gunicorn。


5. 配置 Nginx 主站

假设原本已经有:

server {
    listen 80;
    listen [::]:80;

    server_name 203.0.113.10;

    client_max_body_size 1m;

    location ^~ /admin {
        return 404;
    }

    location / {
        proxy_pass http://127.0.0.1:18080;

        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_read_timeout 50s;
    }
}

为了让域名访问主站,把:

server_name 203.0.113.10;

修改为:

server_name example.com www.example.com 203.0.113.10;

完整结构:

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com 203.0.113.10;

    client_max_body_size 1m;

    location ^~ /admin {
        return 404;
    }

    location / {
        proxy_pass http://127.0.0.1:18080;

        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_read_timeout 50s;
    }
}

其中:

location ^~ /admin {
    return 404;
}

可以阻止通过公开主站访问后台路径:

https://example.com/admin

6. 检查并重载 Nginx

每次修改配置后,先测试,后重载:

sudo nginx -t

看到:

syntax is ok
test is successful

再执行:

sudo systemctl reload nginx

不要在配置尚未通过检查时直接重启 Nginx。


7. 测试 HTTP 主站

执行:

curl -I http://example.com
curl -I http://www.example.com

如果看到:

HTTP/1.1 200 OK
Server: nginx

说明:

DNS
 ↓
VPS
 ↓
Nginx
 ↓
Gunicorn :18080

整条 HTTP 链路已经打通。


8. 使用 Let's Encrypt 配置 HTTPS

安装 Certbot:

sudo apt install certbot python3-certbot-nginx

申请主站证书:

sudo certbot --nginx -d example.com -d www.example.com

配置完成后测试:

curl -I https://example.com
curl -I https://www.example.com

并检查 443:

sudo ss -lntp | grep ':443'

此时主站结构为:

Internet
   ↓
https://example.com
   ↓ :443
 Nginx
   ↓
127.0.0.1:18080
   ↓
Gunicorn

9. 为什么不把管理后台直接暴露公网

管理后台虽然可以配置成:

admin.example.com
        ↓
      Nginx
        ↓
127.0.0.1:18081

但如果后台本来就是按"SSH 隧道访问"设计的,应用通常还会主动检查请求来源。

例如公网访问时可能返回:

400

请通过 SSH 隧道使用本机地址访问后台。

这不是 Nginx 故障,而是应用自己的安全策略正在生效。

如果后台只供站长个人使用,没有必要为了一个漂亮的子域名拆掉这层保护。


10. 使用 SSH Tunnel 访问后台

假设后台监听:

127.0.0.1:18081

在本地电脑运行:

ssh -L 18081:127.0.0.1:18081 user@VPS_IP

保持 SSH 会话开启。

然后浏览器访问:

http://127.0.0.1:18081/admin

数据路径为:

本地浏览器
    ↓
127.0.0.1:18081
    ↓
SSH 加密隧道
    ↓
VPS
    ↓
127.0.0.1:18081
    ↓
管理后台

后台端口不需要对公网开放。


11. 如果曾经配置过 admin 子域名

如果之前已经建立:

admin.example.com

后来决定恢复为纯 SSH 隧道访问,应清理三处。

DNS

删除:

A    admin    VPS_IP

Nginx

删除所有:

server_name admin.example.com;

对应的 server { ... }。

修改后:

sudo nginx -t
sudo systemctl reload nginx

Let's Encrypt 证书

查看:

sudo certbot certificates

如果存在独立的 admin 证书:

sudo certbot delete --cert-name admin.example.com

12. Nginx 排错技巧

查看 Nginx 实际加载的配置

不要只看 /etc/nginx/sites-available/ 中的文件。

真正可靠的是:

sudo nginx -T

例如:

sudo nginx -T | grep -n -E 'server_name|proxy_pass|18080|18081'

它显示的是 Nginx 当前实际加载的完整配置。

查看启用的网站

ls -l /etc/nginx/sites-enabled/

sites-available 里的配置文件通常需要在 sites-enabled 中存在符号链接才会被加载。

绕过 DNS 测试 Host 匹配

HTTP:

curl -I -H 'Host: example.com' http://127.0.0.1

HTTPS:

curl -k -I \
  --resolve example.com:443:127.0.0.1 \
  https://example.com

这样可以把 DNS 问题和 Nginx 问题彻底分开。


13. systemd / Gunicorn 排错

如果不同端口返回了相同页面,先不要怀疑 Nginx。

检查服务:

systemctl cat your-public.service
systemctl cat your-admin.service

查看完整启动命令:

grep '^ExecStart=' /etc/systemd/system/your-public.service
grep '^ExecStart=' /etc/systemd/system/your-admin.service

理想情况下类似:

18080 → public_wsgi:app
18081 → admin_wsgi:app

直接访问两个 Gunicorn:

curl -I http://127.0.0.1:18080
curl -I http://127.0.0.1:18081

如果要确认返回页面:

curl -s http://127.0.0.1:18080 | head
curl -s http://127.0.0.1:18081 | head

这样可以判断问题究竟发生在:

DNS
Nginx
Gunicorn
Flask / Web 应用

哪一层。


14. 最终推荐架构

对于个人博客,推荐保持:

                  Internet
                     │
             HTTPS :443
                     │
                  Nginx
                     │
              127.0.0.1:18080
                     │
                   主站

后台则:

                本地电脑
                     │
                 SSH Tunnel
                     │
                   VPS
                     │
              127.0.0.1:18081
                     │
                  /admin

安全边界很清楚:

  • 80/443:允许公网访问;
  • 18080:仅 VPS 本机;
  • 18081:仅 VPS 本机;
  • 后台:仅通过 SSH 隧道进入;
  • /admin:公开主站直接返回 404;
  • HTTPS:由 Nginx + Let's Encrypt 负责。

15. 一份部署检查清单

[ ] @ A 记录指向 VPS
[ ] www A 记录指向 VPS
[ ] DNS 权威解析正确

[ ] 主站只监听 127.0.0.1:18080
[ ] 后台只监听 127.0.0.1:18081

[ ] Nginx 监听 80
[ ] Nginx 监听 443
[ ] server_name 包含根域名和 www

[ ] nginx -t 通过
[ ] HTTP 主站正常
[ ] HTTPS 主站正常
[ ] HTTP 自动跳转 HTTPS

[ ] 公网 /admin 被阻止
[ ] admin 子域名未公开解析
[ ] 后台只能通过 SSH Tunnel 访问

[ ] Certbot 证书正常
[ ] 不再使用的证书已清理

结语

整个部署过程最重要的不是"让域名能打开网页",而是把每一层职责拆清楚:

DNS      负责找到服务器
Nginx    负责域名、HTTPS 与反向代理
Gunicorn 负责运行 Python Web 应用
SSH      负责保护私人管理入口

主站留在阳光里,后台藏在 SSH 隧道之后。结构简单,出了问题也容易沿着链路逐层定位。