第四单元:服务交付与安全基线

Web 服务、反向代理、HTTPS 与 systemd

黄玮

2026-01

Topic 1: Web 服务架构

为什么需要反向代理 (Reverse Proxy)?

你写的 Python/Node.js 程序通常监听在 localhost:8000。 为什么不直接暴露给公网?

  1. 安全: Nginx 更加健壮,能抗住慢连接攻击。
  2. 性能: 处理静态文件、Gzip 压缩、缓存。
  3. 灵活: 负载均衡、SSL 卸载、统一日志。

直接暴露 vs 反向代理

Nginx 核心配置

/etc/nginx/sites-available/default:

server {
    listen 80;
    server_name example.com;

    # 静态文件
    location /static/ {
        root /var/www/html;
    }

    # 反向代理
    location /api/ {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Nginx 配置验证与重载

修改配置后,不要直接重启服务(会断开现有连接):

# 检查语法(养成习惯:每次改配置都先检查)
sudo nginx -t

# 输出示例:
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful

# 平滑重载配置(不断开连接)
sudo systemctl reload nginx

口诀:改配置 → nginx -treload。永远不要跳过测试步骤。

Caddy 简介

Caddy — 默认安全,零配置 HTTPS 的 Web 服务器。

在共享服务器上你没有 root 权限,无法安装/配置 Nginx。 Caddy 可以在用户态运行,一行命令启动反向代理。

Caddy 安装方式一:官方 apt 源(推荐)

适用于有 root 权限的 Debian/Ubuntu 系统:

# 1. 安装依赖
sudo apt install -y debian-keyring debian-archive-keyring \
  apt-transport-https curl

# 2. 添加 GPG 签名密钥
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
  | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg

# 3. 添加软件源
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
  | sudo tee /etc/apt/sources.list.d/caddy-stable.list

# 4. 安装
sudo apt update && sudo apt install caddy

优势:自动注册为 systemd 服务,apt upgrade 即可跟随上游”傻瓜式”滚动升级。

管道命令的隐患 — 国内网络实战

上面的第 2、3 步在国内网络环境下大概率失败

$ curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
  | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl: (6) Could not resolve host: dl.cloudsmith.io

问题出在哪里?

  1. curl 失败(DNS 无法解析),但管道 | 不会阻止后续命令执行
  2. gpg --dearmor 读到空输入,可能创建空文件或报错
  3. 如果 gpg 没报错,apt update 后续会遇到签名验证失败——错误被”延迟暴露”

教训:不要盲目复制粘贴”一行安装”命令。执行前先验证网络连通性。

防御性编程:让管道”出错即停”

# 在脚本开头加上 pipefail,管道中任一命令失败则整体失败
set -euo pipefail

# 执行前先验证网络连通性
curl -sSf 'https://dl.cloudsmith.io' > /dev/null && echo "网络可达" || echo "网络不通"

# 或者将管道拆成多步,逐步检查
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
  -o /tmp/caddy-key.asc || { echo "下载 GPG 密钥失败"; exit 1; }
sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg \
  /tmp/caddy-key.asc

口诀set -euo pipefail 是脚本的”安全带”。管道不是黑箱,拆开看才放心。

Caddy 安装方式二:直接下载二进制(备用)

无需 root,适合共享服务器 / 开发环境:

# 从 GitHub Releases 下载(选择对应架构)
# https://github.com/caddyserver/caddy/releases

# 解压并放到 ~/bin/ 或其他 PATH 目录
chmod +x caddy
mv caddy ~/bin/

启动反向代理:

# 反向代理 :8000 → :8080
caddy reverse-proxy --from :8000 --to :8080

# 指定域名时自动申请 HTTPS 证书
caddy reverse-proxy --from myapp.example.com --to :8080

Caddy 两种安装方式对比

方式 apt 源 直接下载二进制
需要 root
自动 systemd 服务 需手动配置
版本升级 apt upgrade 傻瓜式滚动更新 手动下载替换二进制
网络要求 需访问 cloudsmith.io 需访问 github.com
适合场景 有 root 的生产服务器 共享服务器 / 开发环境

Nginx vs Caddy 对比

特性 Nginx Caddy
行业地位 事实标准,33%+ 市场份额 新兴选择,快速成长
配置复杂度 手动编写 conf 文件 极简命令行 / Caddyfile
需要 root 是(监听 80/443) 否(可监听高端口)
HTTPS 需手动配置证书 自动申请/续期
适合场景 生产环境、复杂需求 开发环境、共享服务器

策略:生产用 Nginx,开发/共享服务器用 Caddy。两条路都掌握。

Topic 2: 日志与观测

Access Log 黄金指标

tail -f /var/log/nginx/access.log

192.168.1.5 - - [12/Jan/2026:10:00:00 +0800] "GET /api/v1/users HTTP/1.1" 200 1024 "-" "Mozilla/5.0..."

关注点:

  1. $remote_addr — 谁在访问?
  2. $status — 200(OK), 404(Not Found), 500(Server Error)。
  3. $request_time — 响应慢吗?(需在 nginx.conf 中自定义 log_format)

自定义 log_format 添加 $request_time

log_format timed '$remote_addr - $remote_user [$time_local] '
                 '"$request" $status $body_bytes_sent '
                 '"$http_referer" "$http_user_agent" $request_time';
access_log /var/log/nginx/access.log timed;

日志分析实战

场景:老板说「网站好像有点慢」,你需要用数据说话。

# 1. 状态码分布 — 服务健康吗?
awk '{print $9}' access.log | sort | uniq -c | sort -rn
#   1523 200
#    234 404
#     12 500    ← 500 错误需要关注!

# 2. 响应最慢的 5 个请求(假设已配置 $request_time)
awk '{print $NF, $0}' access.log | sort -rn | head -5

# 3. 访问量 Top 10 IP
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10

# 4. 实时监控 500 错误
tail -f access.log | awk '$9 == 500 {print $0}'

管道思维awk 提取字段sort 排序uniq -c 计数sort -rn 按数量排序。这套组合拳适用于任何文本日志分析。

Topic 3: HTTPS 与 TLS

为什么需要 HTTPS?

HTTP 是明文传输,任何人都可以截获你的数据:

TLS 握手(简化版):客户端和服务器协商密钥 → 之后所有数据加密传输。

2018 年起,Chrome 对所有 HTTP 站点标记「不安全」。HTTPS 已是必选项,不是可选项。

自签名证书(Nginx)

局域网/开发环境,没有公网域名?用自签名证书:

# 生成自签名证书(一行命令)
openssl req -x509 -newkey rsa:2048 \
  -keyout key.pem -out cert.pem \
  -days 365 -nodes \
  -subj "/CN=localhost"

Nginx 配置 HTTPS:

server {
    listen 443 ssl;
    server_name localhost;

    ssl_certificate     /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
    }
}

验证:curl -k https://localhost-k 跳过证书验证警告)

自动 HTTPS(Caddy)

Caddy 的 HTTPS 是零配置的:

# 有公网域名 → 自动申请 Let's Encrypt 证书
caddy reverse-proxy --from example.com --to :8080

# 局域网/localhost → 自动生成自签名证书
caddy reverse-proxy --from :443 --to :8080
场景 Nginx Caddy
公网域名 手动配置 certbot + 证书 自动(Let’s Encrypt)
局域网/localhost 手动 openssl + 配置 自动(自签名)
证书续期 手动 crontab 自动后台续期

实验时:两条路都走一遍,体验「手动 vs 自动」的差异。

Topic 4: systemd 服务管理

从临时进程到系统服务

python3 -m http.server 8080 & 的问题:

  1. 关终端就停 — SIGHUP 信号杀死后台进程
  2. 开机不自动启动 — 每次手动运行
  3. 没有日志管理 — 输出丢失,难以排查
  4. 没有重启策略 — 崩溃后无人拉起

systemd = Linux 的「服务管家」,解决以上所有问题。

systemd 基础命令

# 服务生命周期管理
sudo systemctl start   myweb     # 启动
sudo systemctl stop    myweb     # 停止
sudo systemctl restart myweb     # 重启
sudo systemctl status  myweb     # 查看状态(最常用!)

# 开机自启动
sudo systemctl enable  myweb     # 开机自动启动
sudo systemctl disable myweb     # 取消自动启动

# 查看日志(神器)
sudo journalctl -u myweb              # 查看全部日志
sudo journalctl -u myweb -f           # 实时跟踪日志(类似 tail -f)
sudo journalctl -u myweb --since today # 今天的日志

记忆技巧systemctl = 「系统控制」,所有服务操作都走它。

编写你的第一个 .service 文件

创建 /etc/systemd/system/myweb.service

[Unit]
Description=My Python Web Server
After=network.target

[Service]
Type=simple
User=你的用户名
WorkingDirectory=/home/你的用户名/www
ExecStart=/usr/bin/python3 -m http.server 8080
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

启用并启动:

sudo systemctl daemon-reload    # 通知 systemd 读取新配置
sudo systemctl enable myweb     # 开机自启
sudo systemctl start myweb      # 立即启动
sudo systemctl status myweb     # 确认运行状态

验证闭环:关掉终端 → curl http://localhost:8080 → 仍然可以访问 = 成功!

无 root?用户级 systemd(共享服务器)

没有 sudo 权限也能用 systemd,使用 --user 模式:

# 创建用户级服务目录
mkdir -p ~/.config/systemd/user/

# 放置 service 文件(去掉 User= 行,默认当前用户)
cp myweb.service ~/.config/systemd/user/

# 所有命令加 --user,无需 sudo
systemctl --user daemon-reload
systemctl --user enable --now myweb
systemctl --user status myweb
systemctl --user journalctl -u myweb   # 查看日志也不需要 sudo
特性 系统级 (/etc/systemd/system/) 用户级 (~/.config/systemd/user/)
需要 root
用户登录时才运行 否(开机即运行) 默认是(可配置 lingering)
端口限制 只能绑定 >1024 端口

开启 lingering(用户注销后服务继续运行):loginctl enable-linger 你的用户名

小结:Web 服务交付完整链路

每一步都是生产环境的基本要求。你现在已经掌握了 Web 服务交付的完整链路。