服务器运维学习记录 —— Nginx 多站点部署(Rocky 9)
一台服务器、一个 IP,如何同时承载多个网站?
服务器只有一个公网 IP,想再做一个"运维笔记"网站并不需要再买服务器。浏览器请求中携带的
Host 请求头就是区分依据:Nginx 按域名把请求分给不同的站点,
这就是虚拟主机——在 Nginx 中,每个网站对应一个 server 块。
先看看 Nginx 在服务器上是怎么跑的:
# 列出所有进程,grep 筛出 nginx 相关的行;[n] 是个小技巧,避免把 grep 自身也显示出来
ps -ef | grep [n]ginx
# 查看 CPU 核数(工作进程数与它相等)
nproc
会看到一个 master process(主进程)和若干 worker process(工作进程): 主进程以 root 身份运行,负责读取配置、管理工作进程;工作进程以 nginx 用户身份运行,真正处理请求。 第 2 章 403 实验里"Nginx 以 nginx 用户读取网页文件",说的就是工作进程。
# 去掉注释和空行再看主配置:第一个 grep -v 去掉以 # 开头的注释行,第二个去掉空行
grep -v '^\s*#' /etc/nginx/nginx.conf | grep -v '^\s*$'
Nginx 的配置是一层套一层的结构:
nginx.conf
├── 全局:user nginx; worker_processes auto; error_log ...; pid ...;
├── events { ... } 连接处理方式,如每个工作进程的最大连接数
└── http { ... } 所有网站共用的设置
├── log_format、access_log、sendfile、keepalive_timeout、include mime.types ...
├── include /etc/nginx/conf.d/*.conf; ← 我们的 mysite.conf 就是从这里加载的
└── server { ... } 系统自带的默认站点
└── location { ... }
| 关键指令 | 含义 | 和我们的关联 |
|---|---|---|
user nginx; | 工作进程以 nginx 用户身份运行 | 第 2 章的 403 实验 |
worker_processes auto; | 工作进程数自动等于 CPU 核数 | 进程列表里看到 2 个工作进程 |
include /etc/nginx/conf.d/*.conf; | 把 conf.d 下所有 .conf 文件插入到这个位置 | mysite.conf 能生效的原因;也是备份文件不能以 .conf 结尾的原因 |
access_log /var/log/nginx/access.log main; | 默认的访问日志 | 各站点各自另写了 access_log |
外层的指令,内层默认继承;内层自己写了同名指令,就以内层为准。
例如 http 块中写了 access_log,所有站点默认都写这个文件;
而 mysite.conf 的 server 块里自己写了自己的日志路径,主站的访问记录就进了自己的日志文件。
# 查看完整生效配置;按空格翻页,按 / 输入关键词搜索,按 q 退出
sudo nginx -T | less
# 只看各站点监听的端口和域名:-n 显示行号,-E 使用扩展正则
sudo nginx -T 2>/dev/null | grep -n -E "^\s*(listen|server_name)"
# 确认默认站点:带 default_server 的就是默认站点
grep -n "default_server" /etc/nginx/nginx.conf
nginx -T 会先检查语法,再把主配置和所有被 include 的文件按加载顺序合并输出——
看到的就是 Nginx 实际使用的完整配置,是排查问题的利器。
一个请求路径可能同时匹配多个 location。Nginx 的规则是:普通前缀匹配取最长的那个。
例如请求 /.well-known/acme-challenge/abc 既匹配 location /
也匹配 location /.well-known/acme-challenge/,走后者,证书验证文件才能被正常发出。
| 写法 | 含义 | 优先级 |
|---|---|---|
location = /路径 | 精确匹配 | 最高,命中即停止 |
location ^~ /路径 | 前缀匹配,命中后不再检查正则 | 次之 |
location ~ 正则 / ~* | 正则匹配(区分 / 不区分大小写),按书写顺序取第一个 | 再次 |
location /路径 | 普通前缀匹配,取最长的 | 最低 |
给"运维笔记"网站安家:notes.你的域名
第二个网站使用子域名 notes.你的域名。
子域名属于已备案的主域名,一般不需要另行备案,只需添加一条解析记录。
解析需要时间生效,所以先做这一步。
进入阿里云 云解析 DNS → 域名解析 → 解析设置 → 添加记录: 记录类型选 A,主机记录填 notes,记录值填服务器公网 IP,TTL 保持默认。
在自己的电脑上验证(可能需要等几分钟):
# Address 一栏应是你的服务器公网 IP
nslookup notes.你的域名
# 为子站点单独建目录,和主站互不干扰
sudo mkdir -p /var/www/notes
# 交给 study 用户,之后上传网页不必用 sudo
sudo chown -R study:study /var/www/notes
# 目录权限 755:Nginx 工作进程才能进入和读取
sudo chmod 755 /var/www/notes
# 核对:drwxr-xr-x,属主为 study
ls -ld /var/www/notes
网站内容就是前三章的运维笔记:每一章做了什么、用到的关键命令、遇到的问题和解决办法。 排版要求:
index.html,标题"我的运维笔记",按"第 1 讲、第 2 讲、第 3 讲"分节;# 上传到子站点目录
scp index.html study@你的服务器公网IP:/var/www/notes/
80 端口配置 → 默认站点实验 → 单独申请证书 → 443 端口 → 验证与排错
子站点的配置结构与主站相同:80 端口负责证书验证和跳转,443 端口提供内容。
但现在还没有证书,如果把 443 部分写进去,nginx -t 会报错,
所以先只写 80 端口部分:
sudo vi /etc/nginx/conf.d/notes.conf
# notes 子站点 ① 80 端口:为证书申请和续期提供验证文件;其余请求跳转到 HTTPS
server {
listen 80;
server_name notes.example.com;
location /.well-known/acme-challenge/ {
root /var/www/notes; # 注意是子站点自己的目录
}
location / {
return 301 https://$host$request_uri;
}
}
# 检查语法
sudo nginx -t
# 重新加载
sudo systemctl reload nginx
访问 http://notes.你的域名 会跳转到 https,浏览器弹出证书警告——
拿到的竟然是主站的证书。原因:
server_name 是 notes 子域名;default_server 的系统自带站点;
443 端口上没人写 default_server,就由第一个监听 443 的站点当默认站点——
conf.d 按文件名顺序加载,mysite.conf 排在前面,于是主站成了默认站点,浏览器拿到的就是主站证书。在服务器上用两条命令验证:
# 80 端口:用一个没有配置的域名访问本机,返回的是系统默认页(看 <title> 一行)
curl -s -H "Host: nosuch.example.org" http://127.0.0.1 | grep -i "<title>"
# 443 端口:以 notes 域名发起加密连接,看服务器出示的证书属于哪些域名
# s_client 建立 TLS 连接;-servername 告诉服务器要访问的域名;
# </dev/null 表示连接后立即结束;后半段只取证书中的域名列表
openssl s_client -connect 127.0.0.1:443 -servername notes.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -ext subjectAltName
第二条命令显示的是主站的两个域名,印证了上面的分析。
子站点使用单独的证书而不是并入主站证书,好处是互不牵连: 以后撤掉某个子站点,直接删除它的证书即可,不影响主站。 第 3 章已注册过 Let's Encrypt 账户,这次不用再填邮箱。 依然是先演练、再正式:
# 演练:-w 是子站点的目录 /var/www/notes,-d 只写 notes 子域名
sudo certbot certonly --webroot -w /var/www/notes -d notes.example.com --dry-run
# 看到 The dry run was successful. 之后,去掉 --dry-run 正式申请
sudo certbot certonly --webroot -w /var/www/notes -d notes.example.com
# 现在应列出两张证书:主站一张,notes 一张
sudo certbot certificates
sudo vi /etc/nginx/conf.d/notes.conf
在文件末尾追加:
# notes 子站点 ② 443 端口:提供网站内容
server {
listen 443 ssl;
server_name notes.example.com;
ssl_certificate /etc/letsencrypt/live/notes.example.com/fullchain.pem; # notes 自己的证书
ssl_certificate_key /etc/letsencrypt/live/notes.example.com/privkey.pem; # notes 自己的私钥
ssl_protocols TLSv1.2 TLSv1.3;
root /var/www/notes;
index index.html;
# 一站一日志:子站点的访问记录单独存放
access_log /var/log/nginx/notes_access.log;
error_log /var/log/nginx/notes_error.log;
location / {
try_files $uri $uri/ =404;
}
}
sudo nginx -t
sudo systemctl reload nginx
浏览器访问 http://notes.你的域名:自动跳转 https,出现安全锁,显示运维笔记。
再用 3.2 节的 openssl 命令检查一次,这次应只显示 notes 子域名。
续期不需要额外配置:certbot-renew.timer 会检查本机所有证书, deploy 钩子会在任何一张证书续期后重载 Nginx。演练确认:
# 应显示两张证书的模拟续期都成功
sudo certbot renew --dry-run
# 一条命令盘点:四个自己的 server 块(主站 80/443、notes 80/443)+ 系统默认站点
sudo nginx -T 2>/dev/null | grep -n -E "^\s*(listen|server_name)"
开两个终端分别实时查看两个站点的访问日志,请同学分别访问两个网站—— 访问哪个网站,记录就只出现在哪个网站的日志中:
# 窗口一:主站日志
sudo tail -f /var/log/nginx/mysite_access.log
# 窗口二:notes 站日志
sudo tail -f /var/log/nginx/notes_access.log
一站一日志的好处:某个网站出了问题,直接看它自己的日志,不会被其他网站的记录淹没; 后面做日志分析时,分离的日志也让统计简单得多。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| nslookup notes.域名 查不到 | 解析未添加或未生效 | 检查云解析中主机记录是否为 notes;等待 TTL 后再试 |
| certbot 报 unauthorized 且有 404 | 验证文件没被访问到 | 核对 notes.conf 中 acme 路径的 root 是否为 /var/www/notes,与 -w 一致 |
| 访问 notes 有证书警告,证书上是主站域名 | notes 的 443 部分未生效 | 检查 443 配置是否已追加并 reload;用 nginx -T 确认 |
| 访问 notes 显示的是主站内容 | server_name 写错 | 用 nginx -T 核对 notes.conf 的 server_name |
| nginx -t 报 cannot load certificate | 证书路径写错,或证书还没申请就写了 443 部分 | 对照 sudo certbot certificates 中的路径 |
| nginx -t 报 conflicting server name | 同一域名在两个 server 块中重复出现 | 用 nginx -T 找出重复的 server_name,删掉多余的 |
| 403 Forbidden | 目录或文件权限不足 | ls -l 核对:目录 755、文件 644 |
| 内容 | 要点 |
|---|---|
| 虚拟主机 | 一个 IP 上的多个网站,Nginx 按请求中的域名(Host)分配给对应的 server 块 |
| 配置结构 | 全局 → events → http → server → location,外层设置被内层继承,内层可以覆盖 |
| include | conf.d 下的 .conf 文件按文件名顺序插入 http 块 |
| 默认站点 | 匹配不到域名时:交给带 default_server 的站点;没有则交给该端口的第一个站点 |
| location | 精确 = > ^~ > 正则 > 最长前缀 |
| 子站点 | 解析 → 目录 → 80 配置 → 证书 → 443 配置;一站一文件、一站一日志、一站一证书 |