适用:有公网 IP 的服务器 + 一个域名 耗时:约 40 分钟 前置:S1(nginx 会配)、S3(懂端口映射)
现在你的服务可能跑在 http://IP:8080。这一篇让它变成 https://你的域名。
两件事:反向代理(把域名转到正确的服务)和 HTTPS(加证书)。
一、先搞清"反向代理"是什么
正向 vs 反向
| 代理谁 | 例子 | |
|---|---|---|
| 正向代理 | 代理客户端 | 科学上网、公司上网代理 |
| 反向代理 | 代理服务端 | nginx 把请求转给后端 |
你只需要理解反向代理:
用户浏览器 → https://example.com → nginx (443)
↓ 转发
后端服务 (127.0.0.1:3000)
用户只知道 example.com,不知道后端跑在哪个端口。
为什么需要它
- 一个域名对应一个服务,但服务器上跑着好几个 —— 靠 nginx 按域名分流
- 证书只配一次 —— 在后端逐个配证书很麻烦
- 后端只监听
127.0.0.1—— 不暴露到公网,安全 - 能加缓存、限流、安全头 —— 都在 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 自动续期。
前置条件(必须都满足)
- 有公网 IP
- 域名 A 记录已经指向这个 IP(
nslookup 你的域名能看到 IP) - 80 端口从公网可访问(ACME 验证要用)
- 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
它会:
- 验证域名指向你这台机器(通过 80 端口)
- 申请证书
- 自动改 nginx 配置(加 443 监听和证书路径)
- 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 端口从公网访问不到。三处都要查:
- 本机防火墙(
ufw status/firewall-cmd --list-all) - 云控制台安全组
- 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