从 HTTP 到 HTTPS:Spring Boot + Nginx 生产级 TLS 实战指南
你的 API 还在裸奔吗?是时候给 Spring Boot 穿上“防弹衣”了。
你好,我是老张。今天我们不聊虚的,直接上一套能跑在生产环境、能过 SSL Labs A+ 评级的 HTTPS 全家桶方案。
网上讲 HTTPS 的文章一抓一大把,但大部分要么只讲 Nginx 配置,要么只讲 Spring Boot 代码,很少有文章把“从证书申请到 Nginx 安全加固,再到 Spring Boot 正确识别 HTTPS”这条链路完整串起来。
这篇文章的目标只有一个:让你照着抄,就能跑通一套生产级的 HTTPS 架构。
📌 版本声明:本文基于 Nginx 1.24+、Spring Boot 3.x、OpenSSL 3.x,acme.sh 版本 v3.0+。
📑 本文导航
text
一、为什么 HTTPS 是“必选项”而非“可选项” 二、HTTP vs HTTPS:一张表看懂差距 三、架构解析:SSL 终止模型与通信链路 四、证书自动化:acme.sh 三步搞定免费证书 五、生产级 Nginx 配置(安全加固 + 性能优化) 六、Spring Boot 后端配置(解决反向代理后的协议感知痛点) 七、进阶:mTLS 双向认证 八、故障排查与自动化运维 九、总结与互动
一、为什么 HTTPS 是“必选项”而非“可选项”
2026 年,如果你的服务还在跑 HTTP,基本等于在大街上裸奔。
三大驱动力让你别无选择:
-
浏览器强制干预:从 Chrome 开始,所有主流浏览器已将 HTTP 页面标记为“不安全”,地址栏直接显示醒目的“⚠️ 不安全”警告。
-
SEO 排名降权:Google 明确将 HTTPS 作为搜索排名信号,HTTP 站点在同等内容下排名更低。
-
合规与数据安全:《数据安全法》《个人信息保护法》均要求传输过程中的个人信息需采取加密措施,HTTP 明文传输在法律层面即构成违规。
💡 一句话总结:HTTPS 不是“锦上添花”,而是现代 Web 服务的准入门槛。
二、HTTP vs HTTPS:一张表看懂差距
| 对比维度 | HTTP | HTTPS |
|---|---|---|
| 传输加密 | ❌ 明文传输,中间人可完整窃听 | ✅ TLS 加密,防窃听、防篡改 |
| 数据完整性 | ❌ 数据可被中间人篡改 | ✅ 加密+签名,篡改即失效 |
| 身份认证 | ❌ 无法确认服务端身份 | ✅ 证书体系验证服务端真实身份 |
| 浏览器标识 | ⚠️ 显示“不安全” | ✅ 显示安全锁(DV)或企业名称(OV/EV) |
| SEO 影响 | ⚠️ 排名权重低于 HTTPS | ✅ Google 明确将 HTTPS 作为排名信号 |
| HTTP/2 支持 | ❌ 不支持(浏览器强制要求 HTTPS) | ✅ 原生支持,性能提升 30%~50% |
| HSTS 预加载 | ❌ 无法提交 | ✅ 可提交至 HSTS Preload List |
| 现代 Web API | ❌ 无法使用 Geolocation、Service Worker 等 | ✅ 全部可用 |
参考标准:Mozilla 基金会维护的 SSL Configuration Generator 是业界公认的 TLS 配置权威指南。本文配置即基于 Mozilla Intermediate 配置文件——兼容最近 5+ 年所有客户端,同时保持高强度安全标准。
三、架构解析:SSL 终止模型与通信链路
在典型的微服务架构中,我们采用 SSL 终止(SSL Termination) 模型:
text
[ 浏览器 ]
│
▼ HTTPS (TLS 1.2/1.3, 端口 443)
│
[ Nginx 反向代理 ] ← TLS 在此处解密
│
▼ HTTP (明文, 端口 8080) 内网传输
│
[ Spring Boot 应用 ]
为什么不让 Spring Boot 直接暴露 HTTPS?
| 考量 | Nginx 终止 SSL | Spring Boot 原生 HTTPS |
|---|---|---|
| 性能 | Nginx 的 TLS 处理性能远超 Tomcat | Tomcat 处理 TLS 有额外开销 |
| 证书管理 | 统一管理,证书更新只需 reload Nginx | 每个实例都要管理证书 |
| 负载均衡 | Nginx 天然支持负载均衡 | 需要额外方案 |
| 运维复杂度 | 低(一个入口统一配置) | 高(每个实例独立配置) |
核心通信模型:
-
客户端与 Nginx 之间:HTTPS(TLS 加密)
-
Nginx 与 Spring Boot 之间:HTTP(内网明文,性能最优)
-
Nginx 通过
X-Forwarded-*头部将原始请求信息传递给后端
四、证书生态与自动化实战
4.1 免费证书 vs 商业证书
| 对比维度 | Let's Encrypt(免费) | 商业证书(DigiCert/GlobalSign 等) |
|---|---|---|
| 价格 | 免费 | 每年数百至数万美元 |
| 有效期 | 90 天 | 1-2 年 |
| 验证级别 | DV(仅验证域名) | OV(企业验证)/ EV(扩展验证) |
| 适用场景 | 个人站点、中小型企业、测试环境 | 金融机构、电商、政府网站 |
| 自动化 | ✅ 原生支持 ACME 协议 | ⚠️ 部分支持,通常需要手动 |
| 信任度 | 浏览器信任,但地址栏仅显示安全锁 | OV/EV 证书显示企业名称,信任度更高 |
💡 建议:90% 的互联网业务用 Let's Encrypt 完全够用。本文采用 acme.sh ——纯 Shell 实现、零依赖、内置数十家 DNS API 支持,是目前最流行的 ACME 客户端。
4.2 acme.sh 安装与证书申请(三步法)
环境准备:
bash
# 确保 curl 和 cron 已安装 which curl || apt-get install -y curl which crontab || apt-get install -y cron systemctl enable cron && systemctl start cron
第一步:安装 acme.sh
bash
# 安装 acme.sh(自动注册 cron 定时任务) curl https://get.acme.sh | sh -s email=your-email@example.com source ~/.bashrc # 验证安装 acme.sh --version
⚠️ 注意:acme.sh 默认 CA 是 ZeroSSL,免费额度有限。建议显式切换为 Let's Encrypt:
bash
acme.sh --set-default-ca --server letsencrypt
第二步:申请证书(以阿里云 DNS 为例)
对于需要通配符证书(*.example.com)或无法开放 80 端口的场景,推荐 DNS-01 验证方式:
bash
# 设置阿里云 DNS API 密钥(推荐用配置文件方式,更安全) export Ali_Key="你的 AccessKey ID" export Ali_Secret="你的 AccessKey Secret" # 申请泛域名证书 acme.sh --issue --dns dns_ali -d example.com -d '*.example.com'
支持的 DNS 服务商:Cloudflare、阿里云、DNSPod、AWS Route53 等数十家。
第三步:安装证书到 Nginx
bash
acme.sh --install-cert -d example.com \ --key-file /etc/nginx/ssl/example.com.key \ --fullchain-file /etc/nginx/ssl/example.com.crt \ --reloadcmd "service nginx force-reload"
⚠️ 关键:
--reloadcmd中的force-reload会强制重新加载证书,普通的reload可能不会重新读取证书文件。
五、生产级 Nginx 配置(安全加固 + 性能优化)
5.1 核心安全指令速查表
| 指令 | 推荐值 | 作用 |
|---|---|---|
ssl_protocols |
TLSv1.2 TLSv1.3 |
禁用 SSLv2/SSLv3/TLSv1.0/TLSv1.1 |
ssl_ciphers |
见下方配置 | 仅启用 AEAD + ECDHE 强套件 |
ssl_prefer_server_ciphers |
off(TLS 1.3 不适用) |
让客户端选择最优套件 |
ssl_session_cache |
shared:SSL:10m |
会话缓存,提升握手性能约 30% |
ssl_session_timeout |
1d |
会话超时时间 |
ssl_session_tickets |
off |
禁用 Session Ticket(更安全) |
ssl_stapling |
on |
启用 OCSP Stapling,提升 TLS 握手速度 |
add_header Strict-Transport-Security |
max-age=63072000 |
强制浏览器 2 年内仅使用 HTTPS |
5.2 完整 Nginx Server Block 配置
nginx
# /etc/nginx/sites-available/example.com
# 基于 Mozilla Intermediate 配置文件[reference:17]
server {
# ---------- 监听配置 ----------
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
# ---------- 证书路径 ----------
ssl_certificate /etc/nginx/ssl/example.com.crt; # 完整证书链
ssl_certificate_key /etc/nginx/ssl/example.com.key; # 私钥(权限 600)
# ---------- TLS 协议版本 ----------
# 仅启用 TLS 1.2 和 1.3,禁用所有老旧协议
ssl_protocols TLSv1.2 TLSv1.3;
# ---------- 加密套件 ----------
# TLS 1.3 套件前置,全部为 AEAD + ECDHE(支持前向保密 PFS)
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
# TLS 1.3 下此指令无效;TLS 1.2 下让客户端选择最优套件
ssl_prefer_server_ciphers off;
# ---------- 会话缓存(性能优化) ----------
ssl_session_cache shared:SSL:10m; # 10MB 共享缓存,约可存储 40000 个会话
ssl_session_timeout 1d; # 会话有效期 1 天
ssl_session_tickets off; # 禁用 Session Ticket
# ---------- OCSP Stapling(性能优化) ----------
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# ---------- HSTS(强制 HTTPS) ----------
# max-age=63072000(2年),includeSubDomains 覆盖所有子域名
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# ---------- 其他安全响应头 ----------
add_header X-Frame-Options "SAMEORIGIN" always; # 防点击劫持
add_header X-Content-Type-Options "nosniff" always; # 防 MIME 类型嗅探
add_header Content-Security-Policy "default-src 'self'" always; # CSP 基础策略
# ---------- 反向代理到 Spring Boot ----------
location / {
proxy_pass http://127.0.0.1:8080;
# 转发原始请求信息(关键!后端需要这些头识别 HTTPS)
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 X-Forwarded-Host $host; # 告知后端原始域名
proxy_set_header X-Forwarded-Port $server_port;
# HTTP 连接优化
proxy_http_version 1.1;
proxy_set_header Connection "";
# 超时配置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
# ---------- HTTP 强制跳转 HTTPS ----------
server {
listen 80;
listen [::]:80;
server_name example.com;
# 301 永久重定向到 HTTPS
return 301 https://$server_name$request_uri;
}
⚠️ 重要提醒:
私钥文件权限必须为
600:chmod 600 /etc/nginx/ssl/example.com.key配置完成后执行
nginx -t检查语法,再systemctl reload nginx
六、前后端打通:Spring Boot 如何感知真实协议?
6.1 错误做法(典型痛点)
很多开发者在 Nginx 配置好 HTTPS 后,发现 Spring Boot 生成的重定向 URL 仍然是 http://,导致 混合内容(Mixed Content) 警告——页面通过 HTTPS 加载,但其中的资源链接却是 HTTP,浏览器会直接阻止加载。
根本原因:Nginx 转发给 Spring Boot 的是 HTTP 请求(proxy_pass http://127.0.0.1:8080),Spring Boot 默认认为所有请求都是 HTTP。
6.2 正确配置(两步解决)
第一步:Nginx 已配置转发头部(上一步已完成):
nginx
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host;
第二步:Spring Boot 配置(application.yml) :
yaml
# src/main/resources/application.yml server: port: 8080 # 关键配置:启用转发头部处理 # FRAMEWORK:由 Spring Boot 自身处理(推荐 Spring Boot 3.x) # NATIVE:由底层 Servlet 容器(Tomcat)处理 forward-headers-strategy: FRAMEWORK
或使用 application.properties:
properties
server.port=8080 server.forward-headers-strategy=FRAMEWORK
📌 原理:当
server.forward-headers-strategy=FRAMEWORK时,Spring Boot 会自动检测X-Forwarded-Proto头部。如果值为https,则HttpServletRequest.isSecure()返回true,request.getScheme()返回https。
6.3 验证代码(Java Controller)
java
// src/main/java/com/example/demo/controller/SslController.java
package com.example.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestController
public class SslController {
@GetMapping("/api/ssl-info")
public Map<String, Object> getSslInfo(HttpServletRequest request) {
Map<String, Object> info = new HashMap<>();
// 这些值在正确配置后将反映真实的 HTTPS 信息
info.put("scheme", request.getScheme()); // 应为 "https"
info.put("isSecure", request.isSecure()); // 应为 true
info.put("serverName", request.getServerName()); // 原始域名
info.put("serverPort", request.getServerPort()); // 443
info.put("remoteAddr", request.getRemoteAddr()); // 真实客户端 IP
info.put("protocolHeader", request.getHeader("X-Forwarded-Proto"));
return info;
}
}
验证请求与响应:
bash
# 发起请求
curl -k https://example.com/api/ssl-info
# 预期响应
{
"scheme": "https",
"isSecure": true,
"serverName": "example.com",
"serverPort": 443,
"remoteAddr": "192.168.1.100",
"protocolHeader": "https"
}
6.4 Spring Security 强制 HTTPS
如果你的应用使用 Spring Security,可以强制所有请求必须通过 HTTPS 访问:
java
// src/main/java/com/example/demo/config/SecurityConfig.java
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.requiresChannel(channel -> channel
.anyRequest().requiresSecure() // 所有请求必须 HTTPS
)
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()
);
return http.build();
}
}
七、进阶安全场景:mTLS 双向认证
mTLS(Mutual TLS)不仅服务端向客户端出示证书,客户端也需向服务端出示证书,适用于微服务间认证、IoT 设备接入等场景。
7.1 Nginx 开启客户端证书验证
nginx
server {
listen 443 ssl http2;
server_name api.internal.example.com;
# 服务端证书(同上)
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# ---------- 客户端证书验证(mTLS) ----------
# CA 证书:用于验证客户端证书的签名
ssl_client_certificate /etc/nginx/ssl/ca.crt;
ssl_verify_client on; # 强制验证客户端证书
ssl_verify_depth 2; # 证书链验证深度
location / {
proxy_pass http://127.0.0.1:8080;
# 将客户端证书信息传递给后端
proxy_set_header X-Client-Cert $ssl_client_cert;
proxy_set_header X-Client-Verify $ssl_client_verify;
proxy_set_header X-Client-Subject $ssl_client_s_dn;
# 其他 proxy_set_header 同前...
}
}
7.2 Spring Boot 解析客户端证书
java
// src/main/java/com/example/demo/controller/MtlsController.java
package com.example.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
@RestController
public class MtlsController {
@GetMapping("/api/client-info")
public Map<String, Object> getClientInfo(HttpServletRequest request) {
Map<String, Object> info = new HashMap<>();
// 从请求头获取 Nginx 传递的证书信息
info.put("clientCert", request.getHeader("X-Client-Cert"));
info.put("clientVerify", request.getHeader("X-Client-Verify"));
info.put("clientSubject", request.getHeader("X-Client-Subject"));
// 或者直接从 Tomcat 的 attribute 中获取证书对象
X509Certificate[] certs = (X509Certificate[]) request
.getAttribute("javax.servlet.request.X509Certificate");
if (certs != null && certs.length > 0) {
info.put("certSubjectDN", certs[0].getSubjectDN().toString());
info.put("certIssuerDN", certs[0].getIssuerDN().toString());
info.put("certSerialNumber", certs[0].getSerialNumber().toString());
}
return info;
}
}
八、故障排查与自动化运维
8.1 三大常见报错及解决方案
| 报错信息 | 原因 | 解决方案 |
|---|---|---|
SSL_ERROR_BAD_CERT_DOMAIN |
证书域名与访问域名不匹配 | 检查证书是否包含正确域名(SAN 字段) |
NET::ERR_CERT_DATE_INVALID |
证书过期 | 执行 acme.sh --renew -d example.com 续期 |
ERR_SSL_PROTOCOL_ERROR |
TLS 版本或密码套件不兼容 | 检查 ssl_protocols 是否包含 TLSv1.2/TLSv1.3 |
8.2 证书无人值守轮换(cron 定时任务)
acme.sh 安装时会自动添加 cron 任务,每天检查证书有效期,到期前自动续期。
验证定时任务:
bash
crontab -l # 应看到类似: # 0 0 * * * /root/.acme.sh/acme.sh --cron --home /root/.acme.sh > /dev/null
手动触发续期测试:
bash
acme.sh --cron --home /root/.acme.sh
systemd timer 方案(可选,更现代化):
bash
# /etc/systemd/system/acme-renew.service [Service] Type=oneshot ExecStart=/root/.acme.sh/acme.sh --cron --home /root/.acme.sh ExecStartPost=/usr/sbin/nginx -t && /usr/sbin/nginx -s reload # /etc/systemd/system/acme-renew.timer [Timer] OnCalendar=daily Persistent=true [Install] WantedBy=timers.target
bash
systemctl enable acme-renew.timer systemctl start acme-renew.timer
九、总结与技术升华
我们从头到尾走完了一条完整的 HTTPS 落地链路:
-
证书申请:acme.sh + DNS API → 自动化获取 Let's Encrypt 证书
-
Nginx 安全加固:TLS 1.2/1.3 + 强密码套件 + HSTS + OCSP Stapling
-
反向代理:SSL 终止模型 + 转发
X-Forwarded-*头部 -
Spring Boot 适配:
forward-headers-strategy: FRAMEWORK→ 正确感知 HTTPS -
进阶 mTLS:双向认证 + 证书信息传递
安全是无声的承诺。 用户访问你的网站时看不到 TLS 握手、看不到证书验证、看不到加密传输——但他们能感受到“这个网站是可信的”。
这套配置能让你的网站在 SSL Labs 测试中获得 A+ 评级,同时保持优秀的性能表现。
十、粉丝福利与互动
免责声明:本文配置及脚本均基于公开技术文档及个人生产环境经验总结,仅供参考。使用前请务必在测试环境验证,因配置不当导致的任何损失,本文作者不承担责任。部分工具(如 acme.sh、Nginx)版权归各自所有者。
💬 互动话题
你在生产环境中踩过哪些 HTTPS 的坑?
是证书过期导致业务中断?还是反向代理后重定向 URL 一直是 HTTP?又或者是 mTLS 配置时证书链验证失败?
欢迎在评论区分享你的经历,每一条我都会认真回复。点赞、收藏、转发三连,让更多朋友告别“裸奔”!
🎁 全套 Spring Boot + Nginx HTTPS 生产级资料包
我帮你整理了一套 “从零到 A+ 评级”的 HTTPS 全家桶资料,评论区回复 “HTTPS” 即可免费领取:
① Nginx + Spring Boot 生产级配置文件模板(含本文完整配置,开箱即用)
② acme.sh 一键申请 + 自动续期脚本(支持阿里云/腾讯云/DNSPod/Cloudflare)
③ SSL Labs A+ 评级 Nginx 配置速查表(PDF 打印版)
④ Spring Boot 3.x 转发头部正确配置 YAML/Properties 示例
⑤ mTLS 双向认证完整代码示例(Nginx 配置 + Java Controller 解析)
⑥ 生产环境 HTTPS 故障排查命令速查手册
⑦ HSTS Preload List 提交指南与域名安全评分自检表
⑧ Let's Encrypt vs 商业证书选型决策树
⑨ 2026 年 Mozilla SSL 配置指南中文翻译版(含 Intermediate/Modern 配置)
👇 领取方式

更多推荐




所有评论(0)