← 首页·文章列表

云服务器运维核心知识点

这是一份关于「一条 HTTP 请求从敲回车到看到网页,中间发生了什么」的知识体系。

读完脑子有一张地图,不只是知道8个知识点。


目录


00 一条请求的完整旅程

你今天看了多少次网页?

你有没有想过,从你敲下回车,到页面出现在屏幕上,这中间到底发生了什么?

我不是在说"打开浏览器"这种废话。我说的是——这条请求,它走了哪些站,每站干什么,它怎么从你这里出去,又怎么回来。

这条路线搞清楚,你就知道网站是怎么跑起来的,不是背下来的。


从敲回车开始

你在浏览器地址栏输入 https://pbti.duckdns.org/,回车。

你看到的是网页。

但这中间发生的事,我跟你说,我一开始以为就是"发了个请求,服务器返回了网页"。就这么简单。结果我发现不是。

我们来一层层拆。


第一站:DNS——域名在哪

你输入的是域名,服务器只认 IP。

DNS 干的就是这件事:把域名翻译成 IP 地址。

pbti.duckdns.org → 49.234.178.53

就像你打电话,查号台告诉你号码在哪。但查号台不管接通之后说什么。

DNS 完成了它的任务:把域名翻译成 IP 地址。

请求现在带着 IP 地址出发了。


第二站:到达服务器

请求到了服务器,IP 你是知道的,端口是 80(HTTP)或 443(HTTPS)。

但服务器上可能跑着十个网站。

服务器怎么知道这个请求应该交给哪个网站处理?

这就到了 Nginx 登场的时候了。


第三站:Nginx——请求进门

Nginx 是服务器上的守门人。请求进门,先经过它。

Nginx 看请求头里的一个字段:Host。

当你访问 https://pbti.duckdns.org/ 时,浏览器发出的请求里有一行:

Host: pbti.duckdns.org

就这一行。浏览器在告诉你:"我要找的是 pbti.duckdns.org。"

Nginx 根据这个字段,把请求交给对应的配置块处理。

一个 IP 能跑多个网站,靠的就是这个字段。


第四站:Nginx 的第二个决策

Nginx 知道该找哪个网站之后,还要决定一件事:

这个请求是直接读文件,还是转给后端程序处理?

两种情况:

情况一:静态网站。 PBTI 是 Next.js 编译出来的静态文件,Nginx 直接读文件,返回 index.html。就跟你打开本地文件夹一样,读文件返回内容。

情况二:动态网站。 Observatory 有 Node 进程在跑,请求要转给这个程序处理。程序处理完了,把结果返回给 Nginx,Nginx 再返回给你。

这个决策由 location 匹配规则决定。location /observatory/ 匹配到了,就把请求 proxy_pass 转给后端程序。


第五站:后端程序

对于动态网站,请求已经到了后端程序手里。

Node 进程跑在服务器上,但不是你打开终端的那个会话——是 PM2 这个工具在管着它。

PM2 让程序脱离终端一直跑。关掉电脑网络还在,后端还在。

这跟直接 node app.js 跑不一样——那样关掉终端进程就没了。


第六站:SSL 加密(HTTPS)

如果你是通过 HTTPS 访问,在 Nginx 返回之前,还经过 SSL 加密这一站。

Nginx 拿证书证明"我是 pbti.duckdns.org"。浏览器用证书链验证信任。

证书哪来的?Let's Encrypt 签的。Let's Encrypt 怎么验证域名是你的?DNS-01 验证——在 DNS 记录里加一条 TXT 记录来证明。

这个流程走通了,浏览器才显示小锁。


第七站:返回

结果从后端程序流出来:

Nginx 收到后端程序的响应,转给你。如果用了反向代理,它还会在返回路径上做一些处理——比如把一些内部的头信息去掉。


把整条路走一遍

现在你脑子里的图是这样的:

你敲回车
  ↓
DNS:域名 → IP
  ↓
服务器:请求到达
  ↓
Nginx(第一决策):根据 Host 头选哪个网站
  ↓
Nginx(第二决策):读文件还是转给后端
  ↓
后端程序(PM2 管着,一直在跑)
  ↓
SSL 加密(返回之前)
  ↓
回到你的屏幕

这是一条完整的路。每篇讲一个站。

全部读完,你脑子里就有了这张图——不是背的,是理解的。


01 请求进门:Nginx 怎么知道该找哪个网站


我那天卡住的问题

我以为一台服务器只能跑一个网站。

我拿到一个 IP 地址,想跑两个网站——一个 PBTI,一个 Observatory。我当时是真的想过买两台服务器。

后来发现不是这么回事。

HTTP 请求头里有一个字段叫 Host,浏览器在请求里写着"我要找哪个网站"。Nginx 就是看这个字段决定把请求交给哪个配置块处理。

DNS 告诉你域名在哪。Host 头告诉你"我找谁"。Nginx 把你转到该去的地方。

就这么回事。


一条 HTTP 请求长什么样

你敲回车,浏览器发出去的请求大概长这样:

GET / HTTP/1.1
Host: pbti.duckdns.org
User-Agent: Mozilla/5.0...

就三行。中间那行,Host 字段。

服务器收到请求,Nginx 看到 Host: pbti.duckdns.org,就去找负责这个域名的配置块。

配置块怎么写:

server {
    listen 443 ssl;
    server_name pbti.duckdns.org;
    ...
}

server_name pbti.duckdns.org 意思就是:这个配置块负责处理 pbti.duckdns.org 的请求。

再看第二个网站:

server {
    listen 443 ssl;
    server_name observatory-jie.duckdns.org;
    ...
}

两个配置块,共用同一个 443 端口,但 server_name 不同。Nginx 自动分流,不用任何特殊配置。


我踩过的坑:proxy_pass 后面那个 /

我配好之后,Observatory 一直收不到正确的路径。浏览器访问 /observatory/admin,Node 那边收到的是 /admin,源码里明明写的是 /observatory/admin。

排查了一下午。

规则是这样:location 匹配了什么路径,就从转发路径里去掉什么路径。

location /observatory/ {
    proxy_pass http://172.17.0.1:8080;  # 后面没 /
}

location /observatory/ 匹配了 /observatory/ 这四个字符,Nginx 转发时把它从 URI 里剥掉了。所以浏览器请求 /observatory/admin,Nginx 转发出去的是 /admin。

如果 proxy_pass 后面加个 /,那就是完整路径转发:/observatory/admin。

我一开始以为 Nginx 有 bug,搞清楚规则之后就明白了——不是 bug,是我不知道这个规则。


你第一次配可能会卡的地方

Let's Encrypt 续期的问题。 很多教程让你把 HTTP 全部跳转到 HTTPS,一行 return 301 https://$host$request_uri。但 Let's Encrypt 续期时要访问 /.well-known/acme-challenge/ 路径做验证,全站重定向就把这个路径也跳走了,续期失败。

记得单独放行这个路径:

location /.well-known/acme-challenge/ {
    root /var/www/acme-challenge;
}

这一站结束了: 请求到了 Nginx,Nginx 看 Host 头把它送到该去的地方——这是第一个岔路口。


02 静态还是动态:Nginx 读文件还是转给后端


两种网站,两种跑法

我一开始以为网站就是"把代码扔到服务器上,别人就能访问"。后来发现不是这么回事。

有两种完全不同的跑法。

第一种:静态网站。 代码提前编译好了,就是一堆 HTML、CSS、JS 文件。Nginx 做的事跟打开文件夹一样——浏览器要哪个文件,Nginx 读出来返回。文件在硬盘上,不在任何程序里。

PBTI 就是这样。Next.js 编译出来 out/ 目录,往服务器上一扔,Nginx 直接读。

第二种:动态网站。 代码里有程序在跑。用户发请求,程序处理完返回结果。程序处理的东西每次都不一样——根据用户是谁、数据在哪、处理逻辑是什么。

Observatory 就是这样。Express 写的后端程序跑着,每次返回的文章列表、弹幕内容,都是程序实时生成的。

这两种网站,Nginx 的处理方式完全不一样。


静态:Nginx 读文件

对于 PBTI,Nginx 配置是这样的:

server {
    server_name pbti.duckdns.org;
    root /var/www/html/;   # 文件在这个目录里

    location / {
        index index.html;  # 找不到文件时返回 index.html
    }
}

root /var/www/html/ 告诉 Nginx:文件在这个目录里。浏览器要哪个文件,Nginx 去这个目录里找,返回。

就这么简单。文件在硬盘上,不在任何程序里。


动态:Nginx 转给后端

对于 Observatory,Nginx 配置是这样的:

server {
    server_name observatory-jie.duckdns.org;

    location /observatory/ {
        proxy_pass http://172.17.0.1:8080;
    }
}

location /observatory/ 匹配到路径,就把请求转发给 172.17.0.1:8080——那是 Express 程序跑的端口。

请求在 Express 程序里走一圈,处理完了,返回给 Nginx,Nginx 再返回给浏览器。

关键区别:文件不在硬盘上,在程序里。


坑:localhost 在容器里是谁

我 Nginx 跑在 Docker 容器里,Express 跑在宿主机上。

一开始 proxy_pass 配的是 http://localhost:8080,一直 502。

容器里的 localhost 指的是容器自己,不是服务器。容器要访问宿主机,得用 172.17.0.1——Docker bridge 网络给宿主机分配的地址。

改了就通了。


坑:子路径部署的路径问题

如果你把网站部署在子路径下,比如 /observatory/,但源码里所有静态资源的路径写的是 /style.css,部署上去浏览器请求的是 /observatory/style.css,Nginx 去找,找不到,因为文件其实在 /observatory/style.css。

构建的时候就要处理路径前缀这件事。


这一站结束了: Nginx 看完 Host 头,决定——这个请求是直接读文件,还是转给后端程序。


03 容器里 Nginx:配置怎么不丢


为什么用 Docker 跑 Nginx

我一开始 Nginx 直接装在系统里。后来换成 Docker。

原因很简单:服务器重装或者迁移的时候,Docker 一把拉起来,所有环境不用重新配。

但有个坑——我第一次用的时候,没把配置目录挂载出来。


坑:容器重建后配置没了

有一天容器崩了,我 docker rm web 删除重建。

结果 Nginx 配置全没了。从头配了一遍。

因为配置文件在容器内部。删容器 = 删配置。

后来的做法是把配置目录从宿主机挂载出来:

docker run -d --name web \
  -p 80:80 -p 443:443 \
  -v /var/www/html:/usr/share/nginx/html:ro \
  -v /etc/nginx/conf.d:/etc/nginx/conf.d:ro \
  -v /etc/nginx/certs:/etc/nginx/certs:ro \
  nginx:alpine

conf.d(配置)、certs(证书)、auth(认证)、html(静态文件)——四个目录全部从宿主机挂出来,用 :ro 只读挂载。

容器删了重建,配置和证书都还在,不会丢。

:ro 只读的意思是:配置文件虽然从宿主机挂出来了,但容器只能读,不能改。这样容器内部不能破坏配置,更安全。


这一站结束了: Nginx 部署方式决定了配置会不会丢——容器化要持久化,挂载是必须做的事。


04 后端一直在跑:PM2 干的是什么


坑:半夜爬起来手动开服务

有一天服务器断电重启。

第二天我打开网站,打不开。SSH 连上去一看,进程没了。

我用的是 node app.js 直接跑。终端关掉,进程就没了。

后来才知道原因——终端退出会发送 SIGHUP 信号,这个信号会终止进程。


进程与会话的关系

你在终端里跑 node app.js,这个进程属于这个终端会话。终端关掉,会话结束,SIGHUP 信号发给进程,进程终止。

程序跟着终端一起死了。

但网站不能这样——你睡觉的时候也得有人访问,断电重启了也得自动跑起来。


PM2 怎么解决

PM2 接管 Node 进程,让它脱离终端会话。

pm2 start app.js --name observatory
pm2 save        # 保存进程列表
pm2 startup      # 生成开机自启脚本

pm2 start 跑起来的进程,不属于任何终端会话。关掉终端,进程还活着。

pm2 save 把当前进程列表保存下来,pm2 startup 生成开机自启脚本,服务器重启之后自动把所有进程拉起来。

服务器断电重启,PM2 自动把 Observatory 拉起来,不用手动开。


这一站结束了: 后端程序跑在服务器上,PM2 让它脱离终端一直跑——这是让网站24小时在线的关键。


05 HTTPS 怎么工作的:证书链是什么


坑:配了证书浏览器还是报错

我配好 Nginx SSL 证书,浏览器一直报 NET::ERR_CERT_AUTHORITY_INVALID。

我反复检查配置,没问题啊。证书签下来了,怎么浏览器还不认?

后来才发现,我只配了"叶子证书",没有配"中间证书"。


证书链是什么

浏览器信任一个网站,不是信任那个网站本身,而是信任签发证书的那个机构(CA)。

但 CA 不是直接签叶子证书——它通过中间证书来签。

链条是这样的:

浏览器内置的根证书(信任)
    ↓ 签
Let's Encrypt 的中间证书
    ↓ 签
给我域名签的叶子证书

浏览器验证的时候,从叶子证书出发,一路追溯到根证书。全链路可信,浏览器才显示小锁。

acme.sh 签出来的文件有两个:

我只配了叶子证书,浏览器找不到中间证书,无法建立信任链,所以报错。

修复:

ssl_certificate /etc/nginx/certs/pbti.fullchain.crt;  # 指向 fullchain,不是叶子
ssl_certificate_key /etc/nginx/certs/pbti.key;

然后 docker exec web nginx -s reload。


所以 HTTPS 是这样的

HTTPS 返回之前,Nginx 拿证书证明"我是 pbti.duckdns.org"。浏览器用证书链验证信任——从叶子证书追到根证书,全链路可信才显示小锁。

就这么回事。


这一站结束了: HTTPS 返回之前,Nginx 拿证书证明"我是这个网站"——证书链是信任的根基。fullchain.crt 包含完整的信任链。


06 免费证书怎么申请:DNS-01 绕过拦截


坑:从本地测明明是通的

Let's Encrypt 需要验证域名是你的。HTTP-01 验证是:你服务器上放一个 Let's Encrypt 指定的文件,Let's Encrypt 来读,读到了证明域名是你的。

从我自己电脑上测,curl http://pbti.duckdns.org/.well-known/acme-challenge/test 返回 200 OK,没问题。

但 Let's Encrypt 那边报 Connection reset。

腾讯云的安全组件把 Let's Encrypt 验证服务器的 IP 拦了。

从外面测是通的,但 Let's Encrypt 那边被拦了,根本访问不到。


解法:DNS-01 验证

HTTP-01 的问题是 Let's Encrypt 要连你的服务器,云厂商能拦。

DNS-01 不连你的服务器,而是在 DNS 记录里加一条 TXT 记录来证明域名是你的。Let's Encrypt 去 DNS 服务商那里查这条记录,查到了就证明域名是你的。

云厂商能拦 HTTP 请求,但管不了 DNS 记录。


做法

注册 DuckDNS(免费子域名),安装 acme.sh,用 DNS-01 验证:

export DuckDNS_Token="你的token"
~/.acme.sh/acme.sh --issue --dns dns_duckdns \
  -d pbti.duckdns.org

acme.sh 会自动在 DuckDNS 上加一条 TXT 记录,Let's Encrypt 去查,查到了就签发证书。然后 acme.sh 设置 cron 任务,到期前自动续签,完全不用管。


这一站结束了: 证书申请这一步,用 DNS-01 验证绕过云厂商拦截——这是免费证书能成功签出来的关键。


07 部署自动化:怎么把代码部署到服务器


坑:Windows ssh 怎么敲都没反应

我一开始用 Windows 自带的 ssh 连服务器,经常卡死。敲命令没反应,有时候超时断开。

scp 传大文件也不稳定,有几次传到一半断了。

后来我用 Python paramiko 写了一个统一入口,不再用原生 ssh。


方案

打包 → 上传 → 服务器构建 → PM2 重启 → 验证

# 打包
tar --exclude=node_modules --exclude=.git --exclude=_site \
    -czf /tmp/deploy.tar.gz .

# 上传
python ssh_connect.py upload /tmp/deploy.tar.gz /tmp/deploy.tar.gz

# 服务器构建
python ssh_connect.py "
  cd /var/www/observatory
  tar xzf /tmp/deploy.tar.gz -C .
  npm install
  npm run build
  pm2 restart observatory
"

关键是用 tar 打包成一个文件传,比 scp -r 稳定很多。


这一站结束了: 代码写完了,要部署到服务器上——这一步决定了开发效率。tar + paramiko 替代 ssh/scp,是 Windows 环境的最佳实践。


08 完整旅程:把地图从头走一遍


现在我们把整条路走一遍。

你在浏览器地址栏输入 https://pbti.duckdns.org/,回车。

第一站:DNS pbti.duckdns.org → 49.234.178.53。查号台告诉你号码在哪。

第二站:到达服务器 请求带着 IP 地址到达服务器的 443 端口,进入 Nginx。

第三站:Nginx 第一决策(Host 头) Nginx 看到请求头的 Host: pbti.duckdns.org,找到负责这个域名的 server block。

第四站:Nginx 第二决策(location 匹配) location / 匹配根路径,不需要 proxy_pass,直接读文件。

第五站:读文件 从 /var/www/html/ 目录读 index.html,加上 SSL 加密(证书链验证)。

第六站:返回 Nginx 把文件返回给你的浏览器。浏览器渲染,页面出现。


再看 Observatory 这条路:

第一站:DNS observatory-jie.duckdns.org → 49.234.178.53。

第二站:到达服务器 请求到达 443 端口,进入 Nginx。

第三站:Nginx 第一决策(Host 头) Nginx 找到 Observatory 的 server block。

第四站:Nginx 第二决策(location 匹配) location /observatory/ 匹配到了,proxy_pass 转发给 172.17.0.1:8080——Express 程序。

第五站:后端程序 PM2 管着的 Node 进程处理请求,生成内容。

第六站:返回 Express 把内容返回给 Nginx,Nginx 加 SSL 加密,返回给你。


两张图,现在你脑子里都有了。

不是背的,是理解的。


完