适用:有公网 IP 的服务器 + 一个域名 耗时:约 40 分钟 前置:S1(nginx 会配)、S3(懂端口映射)

现在你的服务可能跑在 http://IP:8080。这一篇让它变成 https://你的域名。

两件事:反向代理(把域名转到正确的服务)和 HTTPS(加证书)。


一、先搞清"反向代理"是什么

正向 vs 反向

代理谁例子
正向代理代理客户端科学上网、公司上网代理
反向代理代理服务端nginx 把请求转给后端

你只需要理解反向代理:

用户浏览器  →  https://example.com  →  nginx (443)
                                          ↓ 转发
                                      后端服务 (127.0.0.1:3000)

用户只知道 example.com,不知道后端跑在哪个端口。

为什么需要它

  1. 一个域名对应一个服务,但服务器上跑着好几个 —— 靠 nginx 按域名分流
  2. 证书只配一次 —— 在后端逐个配证书很麻烦
  3. 后端只监听 127.0.0.1 —— 不暴露到公网,安全
  4. 能加缓存、限流、安全头 —— 都在 nginx 层做

二、反向代理配置

假设:后端服务跑在 127.0.0.1:3000,域名 example.com。

server {
    listen 80;
    server_name example.com www.example.com;

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

        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_set_header 为什么必须有

头作用不设的后果
Host告诉后端"用户访问的是哪个域名"后端生成的链接可能错
X-Real-IP用户的真实 IP后端日志里全是 127.0.0.1
X-Forwarded-For经过的代理链同上
X-Forwarded-Proto用户用的是 http 还是 https后端可能生成 http:// 链接导致混合内容

没有 X-Real-IP,你的后端日志永远看不到真实访客——全是 nginx 转过来的 127.0.0.1。这个坑很常见。

支持 WebSocket

如果后端用 WebSocket(很多实时应用),还要加:

location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

不加这两行,WebSocket 会连不上。


三、HTTPS:申请免费证书

Let’s Encrypt 提供免费证书,90 天有效,可以用 certbot 自动续期。

前置条件(必须都满足)

  1. 有公网 IP
  2. 域名 A 记录已经指向这个 IP(nslookup 你的域名 能看到 IP)
  3. 80 端口从公网可访问(ACME 验证要用)
  4. nginx 已经在跑

第 2 条最容易卡住——DNS 有缓存,改完可能要等几分钟到几小时。

装 certbot

# Debian 系
sudo apt install certbot python3-certbot-nginx

# RPM 系
sudo dnf install certbot python3-certbot-nginx

一条命令搞定

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

它会:

  1. 验证域名指向你这台机器(通过 80 端口)
  2. 申请证书
  3. 自动改 nginx 配置(加 443 监听和证书路径)
  4. reload nginx

过程中会问:

Enter email address: 你的邮箱          ← 证书过期提醒
Agree to Terms of Service? Y
Share your email with EFF? N          ← 随意
Redirect HTTP to HTTPS? 选 2(重定向)  ← 推荐

完成后浏览器访问 https://example.com 就是绿锁了。

验证

sudo certbot certificates

输出会显示证书路径和到期时间。

自动续期

certbot 装的时候会自动加一个 systemd timer 或 cron,每天检查一次,快到期时自动续。

# 查看定时任务
systemctl list-timers | grep certbot
# 或
ls /etc/cron.d/ | grep certbot

测试续期是否正常(不真的续,只演练):

sudo certbot renew --dry-run

一定要跑这个 --dry-run。 证书 90 天后过期,如果续期失败而你不知道,站点会突然全线报证书错误。


四、手动管理证书(更多控制)

--nginx 模式会改你的配置。如果想自己控制,用 --webroot:

sudo mkdir -p /var/www/certbot

nginx 配置里加(80 端口的 server 块):

location ^~ /.well-known/acme-challenge/ {
    root /var/www/certbot;
    default_type "text/plain";
}

申请:

sudo certbot certonly --webroot -w /var/www/certbot \
  -d example.com -d www.example.com \
  --cert-name example.com

证书在:

/etc/letsencrypt/live/example.com/fullchain.pem    ← 证书链(配置里用这个)
/etc/letsencrypt/live/example.com/privkey.pem      ← 私钥

然后自己写 443 配置:

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    # HSTS(见下一节,慎用)
    add_header Strict-Transport-Security "max-age=31536000" always;

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

# HTTP 全部跳 HTTPS
server {
    listen 80;
    server_name example.com www.example.com;

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

    location / {
        return 301 https://$host$request_uri;
    }
}

五、⚠️ HSTS:开了就收不回来

HSTS 告诉浏览器:“这个域名以后必须用 HTTPS,不许用 HTTP。”

add_header Strict-Transport-Security "max-age=31536000" always;

max-age=31536000 = 一年。

为什么危险

一旦浏览器收到这个头,浏览器会记住一年。这一年里:

  • 用户访问 http://你的域名 → 浏览器直接改成 HTTPS
  • 你的证书一旦出问题(过期、配置错)→ 用户完全无法访问,没有"用 HTTP 凑合一下"的选项

而且这个记忆在用户端,你无法远程清除。

所以

情况建议
刚上 HTTPS,还在调先加短 max-age(比如 max-age=300)
稳定运行几周后再改成一年
域名可能会停用不要开 HSTS

我的建议:一开始用 max-age=300,确认站点稳定一个月后再改长。


六、多域名 / 多服务怎么配

一台机器跑多个站点,靠 server_name 分流。

# 站点 A
server {
    listen 443 ssl http2;
    server_name a.example.com;
    ssl_certificate     /etc/letsencrypt/live/a.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/a.example.com/privkey.pem;
    location / { proxy_pass http://127.0.0.1:3001; }
}

# 站点 B
server {
    listen 443 ssl http2;
    server_name b.example.com;
    ssl_certificate     /etc/letsencrypt/live/b.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/b.example.com/privkey.pem;
    location / { proxy_pass http://127.0.0.1:3002; }
}

每个域名一张证书(certbot 会自动处理)。

一张证书管多个域名(SAN)

sudo certbot certonly --webroot -w /var/www/certbot \
  --cert-name multi \
  -d a.example.com -d b.example.com -d c.example.com

优点:管一张证书。 缺点:一个域名出问题(比如 DNS 没了)会导致整张证书续期失败,全部域名一起挂。

⚠️ Let’s Encrypt 单张证书上限 100 个域名。 超过要拆成多张。 这也是"多张独立证书"更安全的原因——一张出问题不会连累其他。

⚠️ 绝对不要做的事

不要用 certbot revoke 去"撤销某个域名"。

revoke 撤销的是整张证书——共享这张证书的所有域名会立刻全部失效。

要移除某个域名,正确做法是重新签发(去掉那个域名):

sudo certbot certonly --cert-name multi \
  -d a.example.com -d b.example.com \
  --webroot -w /var/www/certbot

这个坑我踩过:删一个库时想"撤销那个域名",执行了 revoke,结果全站 20 多个子域的 HTTPS 瞬间全挂。


七、用自签证书(内网/测试)

没有公网域名时,可以用自签证书(浏览器会警告,但能加密)。

# 生成自签证书(有效期 365 天)
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/nginx/ssl/self.key \
  -out /etc/nginx/ssl/self.crt \
  -subj "/CN=myserver.local"

配置:

ssl_certificate     /etc/nginx/ssl/self.crt;
ssl_certificate_key /etc/nginx/ssl/self.key;

浏览器会显示"不安全"——因为证书不是受信任 CA 签的。内网自用可以接受,但不能给外部用户看。


八、动手:五个练习

练习 1:给一个本地服务配反向代理(不用证书)

# 起一个简单的后端(用 Python 内置服务器)
mkdir -p /tmp/backend && echo "后端页面" > /tmp/backend/index.html
cd /tmp/backend && python3 -m http.server 3000 &

nginx 配置:

server {
    listen 8080;
    server_name _;
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
sudo nginx -t && sudo systemctl reload nginx
curl http://127.0.0.1:8080

练习 2:验证真实 IP 传递

# 后端日志里应该看到你的 IP,不是 127.0.0.1
curl http://127.0.0.1:8080
# 看后端终端有没有输出请求

练习 3:检查现有证书(如果有)

sudo certbot certificates

练习 4:测试续期演练

sudo certbot renew --dry-run

看有没有报错。

练习 5:检查 HTTPS 配置质量

# 看用了什么 TLS 版本
curl -vI https://你的域名 2>&1 | grep -i "SSL connection"

# 看证书详情
echo | openssl s_client -connect 你的域名:443 -servername 你的域名 2>/dev/null | openssl x509 -noout -dates -subject

九、验证:你过关了吗

  • 能说清反向代理的作用
  • 会配 proxy_pass 和四个 proxy_set_header
  • 知道 WebSocket 要加 Upgrade 头
  • 会用 certbot --nginx 一条命令上 HTTPS
  • 会 certbot renew --dry-run 测试续期
  • 知道 HSTS 开了就一年的不可逆性
  • 知道 certbot revoke 会撤整张证书,不能用它删单个域名
  • 知道多域名共享证书的风险

十、不行怎么办

Q1:certbot 报 DNS problem: NXDOMAIN 域名还没解析到这台机器。检查:

nslookup 你的域名

必须指向你的服务器 IP。 DNS 生效要时间。

Q2:Timeout during connect (likely firewall problem) 80 端口从公网访问不到。三处都要查:

  1. 本机防火墙(ufw status / firewall-cmd --list-all)
  2. 云控制台安全组
  3. nginx 在监听 80 吗(ss -ltnp | grep :80)

Q3:too many failed authorizations 同一个域名失败太多次,被限流。等 1 小时再试。

Q4:nginx: [emerg] cannot load certificate ... No such file 证书路径错,或者证书还没签发就想启用 443 配置。 先签证书,再启用 443。

Q5:浏览器报 NET::ERR_CERT_AUTHORITY_INVALID 用的是自签证书,或者证书链不完整。

  • 自签 → 正常现象,内网可接受
  • CA 签的 → 检查 ssl_certificate 用的是 fullchain.pem 而不是 cert.pem

这是最常见的配置错误:只配了 cert.pem(站点证书),缺中间证书,部分浏览器会报错。一定用 fullchain.pem。

Q6:HTTPS 页面部分内容加载失败 “混合内容”——页面里引用了 http:// 的资源。改成 https:// 或相对路径。

Q7:80 和 443 都配了,但 HTTP 不跳转 检查 80 的 server 块里有没有:

location / { return 301 https://$host$request_uri; }

Q8:证书续期失败,站点挂了

sudo certbot renew --force-renewal
sudo systemctl reload nginx

看日志:/var/log/letsencrypt/letsencrypt.log

Q9:多域名证书,其中一个域名不想用了 不要 revoke。重新签发时去掉那个域名:

sudo certbot certonly --cert-name 证书名 -d 保留的域名 --webroot -w /var/www/certbot

十一、一句话总结

反向代理:proxy_pass + 四个 set_header(真实 IP 靠它)。

HTTPS:certbot --nginx -d 域名,一条命令。

fullchain.pem 不是 cert.pem。

HSTS 一开始用短 max-age;revoke 会撤整张证书。


← 上一篇 S3|Docker 下一篇 → S5|定时任务:cron 与 systemd timer