在Nginx上配置HTTPS证书的完整指南
引言
在当今互联网环境中,HTTPS 已经成为网站标配。无论是个人博客、企业官网还是 API 服务,启用 HTTPS 不仅能提升用户信任度,还能增强数据安全性,甚至影响搜索引擎排名。Nginx 作为高性能的 Web 服务器和反向代理,是部署 HTTPS 的理想选择。本文将详细介绍如何在 Nginx 中配置 HTTPS 证书,涵盖自签名证书、Let’s Encrypt 免费证书、商业证书等多种方案,并提供 Java 应用集成示例。
为什么需要 HTTPS?
在深入配置之前,让我们先理解为什么 HTTPS 如此重要:
- 数据加密:防止中间人攻击,保护用户隐私
- 身份验证:确保用户访问的是真实的网站而非钓鱼网站
- 完整性保护:防止数据在传输过程中被篡改
- SEO 优势:Google 等搜索引擎优先收录 HTTPS 网站
- 现代浏览器要求:Chrome 等浏览器对 HTTP 网站标记为"不安全"
// Java 示例:检测 HTTP/HTTPS 请求
import javax.servlet.http.HttpServletRequest;
public class SecurityUtils {
public static boolean isSecureRequest(HttpServletRequest request) {
// 检查请求是否通过 HTTPS
if ("https".equals(request.getScheme())) {
return true;
}
// 检查 X-Forwarded-Proto 头(当使用反向代理时)
String forwardedProto = request.getHeader("X-Forwarded-Proto");
return "https".equals(forwardedProto);
}
public static String getSecureUrl(HttpServletRequest request) {
if (isSecureRequest(request)) {
return request.getRequestURL().toString();
} else {
// 将 HTTP URL 转换为 HTTPS
String url = request.getRequestURL().toString();
return url.replaceFirst("http://", "https://");
}
}
}准备工作
在配置 HTTPS 之前,确保你已准备好以下内容:
- 域名:拥有一个已备案的域名(国内服务器需要)
- 服务器:安装了 Nginx 的 Linux 服务器
- SSH 访问权限:能够通过 SSH 连接到服务器
- 防火墙配置:开放 443 端口
检查 Nginx 是否已安装:
nginx -v
如果未安装,可以使用以下命令安装:
# Ubuntu/Debian sudo apt update sudo apt install nginx # CentOS/RHEL sudo yum install epel-release sudo yum install nginx
方案一:自签名证书(开发测试环境)
自签名证书适合开发和测试环境,但在生产环境中浏览器会显示安全警告。
生成自签名证书
# 创建证书目录
sudo mkdir -p /etc/nginx/ssl
# 生成私钥和自签名证书
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/selfsigned.key \
-out /etc/nginx/ssl/selfsigned.crt \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=example.com"配置 Nginx 使用自签名证书
编辑 Nginx 配置文件:
sudo nano /etc/nginx/sites-available/example.com
server {
listen 443 ssl;
server_name example.com www.example.com;
# SSL 配置
ssl_certificate /etc/nginx/ssl/selfsigned.crt;
ssl_certificate_key /etc/nginx/ssl/selfsigned.key;
# SSL 安全设置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 根目录和索引文件
root /var/www/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
# HTTP 到 HTTPS 重定向
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}启用配置并重启 Nginx
# 创建符号链接 sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/ # 测试配置 sudo nginx -t # 重启 Nginx sudo systemctl restart nginx
// Java 示例:Spring Boot 应用中强制 HTTPS
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requiresChannel()
.requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null)
.requiresSecure()
.and()
.authorizeRequests()
.anyRequest().permitAll();
}
}方案二:Let’s Encrypt 免费证书(生产环境推荐)
Let’s Encrypt 提供免费的 SSL/TLS 证书,有效期 90 天,支持自动续期。
安装 Certbot
# Ubuntu/Debian sudo apt update sudo apt install certbot python3-certbot-nginx # CentOS/RHEL 7 sudo yum install epel-release sudo yum install certbot python3-certbot-nginx # CentOS/RHEL 8+ sudo dnf install certbot python3-certbot-nginx
获取证书
# 自动获取并配置证书 sudo certbot --nginx -d example.com -d www.example.com # 或者仅获取证书(手动配置) sudo certbot certonly --nginx -d example.com -d www.example.com
Certbot 会自动修改你的 Nginx 配置文件,添加 SSL 相关配置。
手动配置 Let’s Encrypt 证书
如果你选择手动配置,证书文件通常位于:
- 证书文件:
/etc/letsencrypt/live/example.com/fullchain.pem - 私钥文件:
/etc/letsencrypt/live/example.com/privkey.pem
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
# Let's Encrypt 证书
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# SSL 安全强化配置
ssl_protocols TLSv1.2 TLSv1.3;
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;
ssl_prefer_server_ciphers off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# HSTS (HTTP Strict Transport Security)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# 其他安全头
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
# 根目录
root /var/www/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
# HTTP 到 HTTPS 重定向
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}自动续期配置
Let’s Encrypt 证书每 90 天需要续期一次。Certbot 可以自动处理:
# 测试续期(不会实际执行) sudo certbot renew --dry-run # 设置定时任务自动续期 sudo crontab -e
添加以下行(每天凌晨 2 点检查是否需要续期):
0 2 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"
// Java 示例:Spring Boot 应用中的 HTTPS 相关配置
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
@Configuration
public class HttpsConfig {
@Value("${server.ssl.enabled:false}")
private boolean sslEnabled;
@Value("${server.ssl.key-store:}")
private String keyStore;
@Value("${server.ssl.key-store-password:}")
private String keyStorePassword;
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> containerCustomizer() {
return factory -> {
if (sslEnabled && !keyStore.isEmpty()) {
Ssl ssl = new Ssl();
ssl.setKeyStore(keyStore);
ssl.setKeyStorePassword(keyStorePassword);
ssl.setKeyAlias("tomcat");
factory.setSsl(ssl);
}
};
}
}方案三:商业证书(企业级应用)
对于企业级应用,可能需要购买商业 SSL 证书,如 DigiCert、GeoTrust、Symantec 等。
申请商业证书流程
- 生成 CSR(Certificate Signing Request)
# 生成私钥 openssl genrsa -out example.com.key 2048 # 生成 CSR openssl req -new -key example.com.key -out example.com.csr
- 提交 CSR 到证书颁发机构
- 验证域名所有权
- 下载证书文件
配置商业证书
商业证书通常包含多个文件:
- 主证书:
example.com.crt - 中间证书:
intermediate.crt - 根证书:
root.crt
需要将它们合并成一个文件:
cat example.com.crt intermediate.crt root.crt > fullchain.crt
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# 商业证书配置
ssl_certificate /etc/nginx/ssl/fullchain.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
# 高级 SSL 配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers off;
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/fullchain.crt;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# HSTS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# 安全头
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'none';";
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# 根目录
root /var/www/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}HTTP 到 HTTPS 重定向的最佳实践
确保所有 HTTP 请求都被重定向到 HTTPS:
# 方法一:简单的 301 重定向
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
# 方法二:保留原始请求路径和参数
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
# 方法三:针对特定路径的重定向
server {
listen 80;
server_name example.com www.example.com;
location /admin {
return 301 https://$server_name$request_uri;
}
location /api {
return 301 https://$server_name$request_uri;
}
# 其他路径可以保持 HTTP(不推荐)
location / {
# 继续使用 HTTP
root /var/www/html;
index index.html;
}
}// Java 示例:Servlet Filter 强制 HTTPS
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class HttpsEnforcementFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 检查是否为 HTTPS 请求
if (!"https".equals(httpRequest.getScheme()) &&
!"https".equals(httpRequest.getHeader("X-Forwarded-Proto"))) {
// 构建 HTTPS URL
StringBuilder httpsUrl = new StringBuilder("https://");
httpsUrl.append(httpRequest.getServerName());
// 添加端口(如果不是默认的 443)
int port = httpRequest.getServerPort();
if (port != 80 && port != 443) {
httpsUrl.append(":").append(port);
}
httpsUrl.append(httpRequest.getRequestURI());
// 添加查询字符串
String queryString = httpRequest.getQueryString();
if (queryString != null) {
httpsUrl.append("?").append(queryString);
}
// 重定向到 HTTPS
httpResponse.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
httpResponse.setHeader("Location", httpsUrl.toString());
return;
}
chain.doFilter(request, response);
}
}SSL/TLS 性能优化

启用 HTTP/2
HTTP/2 可以显著提升 HTTPS 网站性能:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
# ... 其他配置
}Session 复用配置
# SSL Session 缓存 ssl_session_cache shared:SSL:10m; ssl_session_timeout 1h; ssl_session_tickets on; ssl_session_ticket_key /etc/nginx/ssl/ticket.key; # 生成 ticket key openssl rand 48 > /etc/nginx/ssl/ticket.key
SSL 缓冲区优化
# SSL 缓冲区大小 ssl_buffer_size 4k; # SSL 预读 ssl_preread on;
安全加固配置
SSL 协议和加密套件
# 推荐的安全配置 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256: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'; ssl_prefer_server_ciphers off;
HSTS (HTTP Strict Transport Security)
# 严格的 HSTS 配置 add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
CSP (Content Security Policy)
# 严格的内容安全策略 add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.example.com; style-src 'self' 'unsafe-inline' https://cdn.example.com; img-src 'self' data: https://*.example.com; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.example.com; frame-src 'none'; object-src 'none';" always;
// Java 示例:Spring Security 中的 HTTPS 配置
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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.requiresChannel(channel ->
channel.anyRequest().requiresSecure()
)
.headers(headers ->
headers
.httpStrictTransportSecurity(hsts ->
hsts
.maxAgeInSeconds(63072000)
.includeSubdomains(true)
.preload(true)
)
.frameOptions(frame -> frame.deny())
.contentTypeOptions(contentType -> contentType.disable())
.xssProtection(xss -> xss.block(false))
)
.authorizeHttpRequests(authz ->
authz.anyRequest().permitAll()
);
return http.build();
}
}Docker 环境下的 HTTPS 配置
在 Docker 环境中配置 HTTPS 有其特殊性:
Docker Compose 配置
version: '3.8'
services:
nginx:
image: nginx:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
- ./html:/usr/share/nginx/html
restart: unless-stopped
app:
image: my-java-app:latest
expose:
- "8080"
environment:
- SERVER_SSL_ENABLED=true
- SERVER_PORT=8080
restart: unless-stoppedNginx 配置文件
events {
worker_connections 1024;
}
http {
upstream backend {
server app:8080;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://backend;
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;
}
}
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
}Java 应用配置
// Java 示例:Docker 环境中的 HTTPS 检测
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@SpringBootApplication
public class DockerHttpsApplication {
public static void main(String[] args) {
SpringApplication.run(DockerHttpsApplication.class, args);
}
}
@RestController
class HealthController {
@GetMapping("/health")
public HealthStatus healthCheck(HttpServletRequest request) {
return new HealthStatus(
"UP",
request.getScheme(),
request.getServerPort(),
request.getHeader("X-Forwarded-Proto"),
System.getenv("SERVER_SSL_ENABLED")
);
}
static class HealthStatus {
private final String status;
private final String scheme;
private final int port;
private final String forwardedProto;
private final String sslEnabled;
public HealthStatus(String status, String scheme, int port, String forwardedProto, String sslEnabled) {
this.status = status;
this.scheme = scheme;
this.port = port;
this.forwardedProto = forwardedProto;
this.sslEnabled = sslEnabled;
}
// getters...
}
}证书续期和监控
自动化续期脚本
#!/bin/bash
# ssl-renew.sh
LOG_FILE="/var/log/ssl-renew.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$DATE] 开始检查 SSL 证书续期..." >> $LOG_FILE
# 检查 Let's Encrypt 证书
if command -v certbot &> /dev/null; then
echo "[$DATE] 检查 Let's Encrypt 证书..." >> $LOG_FILE
certbot renew --quiet --post-hook "systemctl reload nginx" >> $LOG_FILE 2>&1
if [ $? -eq 0 ]; then
echo "[$DATE] Let's Encrypt 证书续期成功" >> $LOG_FILE
else
echo "[$DATE] Let's Encrypt 证书续期失败" >> $LOG_FILE
fi
fi
# 检查证书到期时间
for cert in /etc/nginx/ssl/*.crt /etc/letsencrypt/live/*/fullchain.pem; do
if [ -f "$cert" ]; then
EXPIRY_DATE=$(openssl x509 -enddate -noout -in "$cert" | cut -d= -f2)
DAYS_LEFT=$(echo "$EXPIRY_DATE" | xargs -I {} date -d {} +%s | xargs -I {} echo $(( ({} - $(date +%s)) / 86400 )))
echo "[$DATE] 证书 $cert 到期时间: $EXPIRY_DATE (剩余 $DAYS_LEFT 天)" >> $LOG_FILE
# 如果少于 30 天,发送警告
if [ $DAYS_LEFT -lt 30 ] && [ $DAYS_LEFT -gt 0 ]; then
echo "[$DATE] 警告: 证书 $cert 即将在 $DAYS_LEFT 天后过期!" >> $LOG_FILE
# 这里可以添加邮件或短信通知逻辑
fi
fi
done
echo "[$DATE] SSL 证书检查完成" >> $LOG_FILE
echo "=========================================" >> $LOG_FILE证书监控 Java 类
// Java 示例:证书到期监控
import java.io.File;
import java.io.FileInputStream;
import java.math.BigInteger;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class CertificateMonitor {
private static final DateTimeFormatter DATE_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());
public static CertificateInfo checkCertificate(String certPath) {
try {
File certFile = new File(certPath);
if (!certFile.exists()) {
return new CertificateInfo(certPath, false, "文件不存在", 0, null, null);
}
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(new FileInputStream(certFile));
Date notBefore = cert.getNotBefore();
Date notAfter = cert.getNotAfter();
long daysLeft = (notAfter.getTime() - System.currentTimeMillis()) / (24 * 60 * 60 * 1000);
boolean isValid = daysLeft > 0;
String status = isValid ? "有效" : "已过期";
if (isValid && daysLeft < 30) {
status = "即将过期 (" + daysLeft + "天)";
}
return new CertificateInfo(
certPath,
isValid,
status,
daysLeft,
DATE_FORMATTER.format(notBefore.toInstant()),
DATE_FORMATTER.format(notAfter.toInstant())
);
} catch (Exception e) {
return new CertificateInfo(certPath, false, "解析错误: " + e.getMessage(), 0, null, null);
}
}
public static class CertificateInfo {
private final String path;
private final boolean valid;
private final String status;
private final long daysLeft;
private final String notBefore;
private final String notAfter;
public CertificateInfo(String path, boolean valid, String status, long daysLeft, String notBefore, String notAfter) {
this.path = path;
this.valid = valid;
this.status = status;
this.daysLeft = daysLeft;
this.notBefore = notBefore;
this.notAfter = notAfter;
}
// Getters...
public String getPath() { return path; }
public boolean isValid() { return valid; }
public String getStatus() { return status; }
public long getDaysLeft() { return daysLeft; }
public String getNotBefore() { return notBefore; }
public String getNotAfter() { return notAfter; }
@Override
public String toString() {
return String.format(
"证书: %s\n状态: %s\n有效期: %s 至 %s\n剩余天数: %d",
path, status, notBefore, notAfter, daysLeft
);
}
}
// 使用示例
public static void main(String[] args) {
String[] certPaths = {
"/etc/letsencrypt/live/example.com/fullchain.pem",
"/etc/nginx/ssl/selfsigned.crt"
};
for (String certPath : certPaths) {
CertificateInfo info = checkCertificate(certPath);
System.out.println(info);
System.out.println("---");
}
}
}高级配置:多域名和通配符证书
多域名证书配置
# 多域名服务器块
server {
listen 443 ssl http2;
server_name example.com www.example.com blog.example.com shop.example.com;
ssl_certificate /etc/nginx/ssl/multidomain.crt;
ssl_certificate_key /etc/nginx/ssl/multidomain.key;
# ... 其他 SSL 配置
location / {
root /var/www/example;
index index.html;
}
}
# 为不同子域名单独配置
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/api.crt;
ssl_certificate_key /etc/nginx/ssl/api.key;
# API 特定配置
client_max_body_size 50M;
proxy_read_timeout 300s;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}通配符证书配置
# 使用 Certbot 获取通配符证书 sudo certbot certonly --manual --preferred-challenges=dns -d *.example.com -d example.com
# 通配符证书配置
server {
listen 443 ssl http2;
server_name *.example.com example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# 动态根目录基于子域名
set $root_path "/var/www/default";
if ($host ~* ^([a-z0-9-]+)\.example\.com$) {
set $subdomain $1;
set $root_path "/var/www/$subdomain";
}
root $root_path;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}// Java 示例:基于子域名的动态路由
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
public class SubdomainController {
@GetMapping("/")
public SubdomainResponse handleSubdomainRequest(HttpServletRequest request) {
String host = request.getHeader("Host");
String subdomain = extractSubdomain(host);
return new SubdomainResponse(
host,
subdomain,
getSiteContent(subdomain),
request.getScheme(),
request.isSecure()
);
}
private String extractSubdomain(String host) {
if (host == null) return "unknown";
// 移除端口号
if (host.contains(":")) {
host = host.substring(0, host.indexOf(":"));
}
// 提取子域名
String domain = "example.com"; // 你的主域名
if (host.endsWith("." + domain)) {
return host.substring(0, host.length() - domain.length() - 1);
}
return "www"; // 默认子域名
}
private String getSiteContent(String subdomain) {
switch (subdomain.toLowerCase()) {
case "blog":
return "博客内容";
case "shop":
return "商店内容";
case "api":
return "API 文档";
default:
return "主页内容";
}
}
static class SubdomainResponse {
private final String host;
private final String subdomain;
private final String content;
private final String scheme;
private final boolean secure;
public SubdomainResponse(String host, String subdomain, String content, String scheme, boolean secure) {
this.host = host;
this.subdomain = subdomain;
this.content = content;
this.scheme = scheme;
this.secure = secure;
}
// Getters...
}
}Java 应用与 HTTPS 集成
Spring Boot HTTPS 配置
// Java 示例:Spring Boot 内置 HTTPS
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.Ssl;
@SpringBootApplication
public class SecureApplication {
public static void main(String[] args) {
SpringApplication.run(SecureApplication.class, args);
}
@Bean
public TomcatServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
// 配置 SSL
Ssl ssl = new Ssl();
ssl.setKeyStore("classpath:keystore.p12");
ssl.setKeyStorePassword("changeit");
ssl.setKeyStoreType("PKCS12");
ssl.setKeyAlias("tomcat");
ssl.setEnabled(true);
tomcat.setSsl(ssl);
tomcat.setPort(8443);
return tomcat;
}
}application.properties 配置
# HTTPS 配置 server.port=8443 server.ssl.enabled=true server.ssl.key-store=classpath:keystore.p12 server.ssl.key-store-password=changeit server.ssl.key-store-type=PKCS12 server.ssl.key-alias=tomcat # HTTP 重定向到 HTTPS server.http.port=8080 # 安全头配置 server.servlet.session.cookie.secure=true server.servlet.session.cookie.http-only=true server.servlet.session.cookie.same-site=strict
REST API HTTPS 客户端配置
// Java 示例:HTTPS REST 客户端
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.*;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
@Configuration
public class RestClientConfig {
@Bean
public RestTemplate restTemplate() throws KeyManagementException, NoSuchAlgorithmException {
// 创建信任所有证书的 SSL 上下文(仅用于测试!)
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// 创建 HttpClient
org.apache.http.impl.client.CloseableHttpClient httpClient =
org.apache.http.impl.client.HttpClients.custom()
.setSSLContext(sslContext)
.build();
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
return new RestTemplate(requestFactory);
}
// 生产环境安全的 REST 客户端
@Bean
public RestTemplate secureRestTemplate() {
// 使用系统默认的信任库
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
return new RestTemplate(requestFactory);
}
}性能监控和日志分析
Nginx 日志配置
# HTTPS 访问日志格式
log_format ssl_log '$time_local | $scheme | $status | $request_method | '
'"$request" | $body_bytes_sent | '
'"$http_referer" | "$http_user_agent" | '
'$ssl_protocol | $ssl_cipher | $ssl_session_reused';
server {
listen 443 ssl http2;
server_name example.com;
# SSL 专用访问日志
access_log /var/log/nginx/ssl-access.log ssl_log;
error_log /var/log/nginx/ssl-error.log;
# ... 其他配置
}Java 监控类
// Java 示例:HTTPS 性能监控
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Component
public class HttpsPerformanceFilter implements Filter {
private final ConcurrentHashMap<String, RequestStats> statsMap = new ConcurrentHashMap<>();
private final AtomicLong totalRequests = new AtomicLong(0);
private final AtomicLong httpsRequests = new AtomicLong(0);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String requestKey = httpRequest.getMethod() + " " + httpRequest.getRequestURI();
Instant startTime = Instant.now();
// 记录请求总数
totalRequests.incrementAndGet();
// 记录 HTTPS 请求数
if ("https".equals(httpRequest.getScheme()) ||
"https".equals(httpRequest.getHeader("X-Forwarded-Proto"))) {
httpsRequests.incrementAndGet();
}
try {
chain.doFilter(request, response);
} finally {
Duration duration = Duration.between(startTime, Instant.now());
updateStats(requestKey, duration.toMillis(), httpResponse.getStatus());
}
}
private void updateStats(String key, long durationMs, int statusCode) {
statsMap.compute(key, (k, v) -> {
if (v == null) {
return new RequestStats(durationMs, statusCode);
} else {
v.addRequest(durationMs, statusCode);
return v;
}
});
}
public PerformanceReport getPerformanceReport() {
return new PerformanceReport(
totalRequests.get(),
httpsRequests.get(),
statsMap.size(),
calculateAverageResponseTime(),
calculateHttpsPercentage()
);
}
private double calculateAverageResponseTime() {
long totalDuration = statsMap.values().stream()
.mapToLong(RequestStats::getTotalDuration)
.sum();
long totalCount = statsMap.values().stream()
.mapToLong(RequestStats::getRequestCount)
.sum();
return totalCount > 0 ? (double) totalDuration / totalCount : 0.0;
}
private double calculateHttpsPercentage() {
long total = totalRequests.get();
return total > 0 ? (double) httpsRequests.get() / total * 100 : 0.0;
}
static class RequestStats {
private long totalDuration;
private long requestCount;
private int successCount;
private long maxDuration;
private long minDuration = Long.MAX_VALUE;
public RequestStats(long duration, int statusCode) {
this.totalDuration = duration;
this.requestCount = 1;
this.successCount = statusCode >= 200 && statusCode < 300 ? 1 : 0;
this.maxDuration = duration;
this.minDuration = duration;
}
public void addRequest(long duration, int statusCode) {
this.totalDuration += duration;
this.requestCount++;
if (statusCode >= 200 && statusCode < 300) {
this.successCount++;
}
this.maxDuration = Math.max(this.maxDuration, duration);
this.minDuration = Math.min(this.minDuration, duration);
}
// Getters...
public long getTotalDuration() { return totalDuration; }
public long getRequestCount() { return requestCount; }
public int getSuccessCount() { return successCount; }
public long getMaxDuration() { return maxDuration; }
public long getMinDuration() { return minDuration; }
public double getAverageDuration() {
return requestCount > 0 ? (double) totalDuration / requestCount : 0.0;
}
public double getSuccessRate() {
return requestCount > 0 ? (double) successCount / requestCount * 100 : 0.0;
}
}
public static class PerformanceReport {
private final long totalRequests;
private final long httpsRequests;
private final int endpointCount;
private final double averageResponseTime;
private final double httpsPercentage;
public PerformanceReport(long totalRequests, long httpsRequests, int endpointCount,
double averageResponseTime, double httpsPercentage) {
this.totalRequests = totalRequests;
this.httpsRequests = httpsRequests;
this.endpointCount = endpointCount;
this.averageResponseTime = averageResponseTime;
this.httpsPercentage = httpsPercentage;
}
// Getters...
public long getTotalRequests() { return totalRequests; }
public long getHttpsRequests() { return httpsRequests; }
public int getEndpointCount() { return endpointCount; }
public double getAverageResponseTime() { return averageResponseTime; }
public double getHttpsPercentage() { return httpsPercentage; }
@Override
public String toString() {
return String.format(
"性能报告:\n" +
"总请求数: %d\n" +
"HTTPS 请求数: %d (%.2f%%)\n" +
"端点数量: %d\n" +
"平均响应时间: %.2fms\n",
totalRequests, httpsRequests, httpsPercentage,
endpointCount, averageResponseTime
);
}
}
}故障排除和常见问题
证书链问题
# 检查证书链 openssl s_client -connect example.com:443 -showcerts # 验证证书链完整性 openssl verify -CAfile /path/to/ca-bundle.crt /path/to/your/certificate.crt
SSL/TLS 握手失败
# 调试 SSL 配置 ssl_buffer_size 16k; ssl_session_tickets off; # 更宽松的加密套件(仅用于调试) ssl_ciphers HIGH:!aNULL:!MD5;
Java 应用 HTTPS 问题排查
// Java 示例:SSL/TLS 调试工具
import javax.net.ssl.*;
import java.io.IOException;
import java.net.URL;
import java.security.cert.X509Certificate;
public class SSLDebugTool {
public static void enableSSLDebugging() {
// 启用 SSL 调试
System.setProperty("javax.net.debug", "ssl:handshake:verbose");
// 创建信任所有证书的 SSL 上下文(仅用于调试!)
try {
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return null; }
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
};
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// 创建所有主机都信任的主机名验证器
HostnameVerifier allHostsValid = (hostname, session) -> true;
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void testConnection(String urlString) {
try {
URL url = new URL(urlString);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
System.out.println("正在测试连接: " + urlString);
int responseCode = connection.getResponseCode();
System.out.println("响应码: " + responseCode);
System.out.println("响应消息: " + connection.getResponseMessage());
SSLSession session = connection.getSSLSession();
if (session != null) {
System.out.println("SSL 协议: " + session.getProtocol());
System.out.println("SSL 密码套件: " + session.getCipherSuite());
System.out.println("对等证书数量: " + session.getPeerCertificates().length);
}
connection.disconnect();
} catch (IOException e) {
System.err.println("连接失败: " + e.getMessage());
e.printStackTrace();
}
}
public static void main(String[] args) {
// 启用调试(生产环境不要使用!)
enableSSLDebugging();
// 测试连接
testConnection("https://example.com");
}
}最佳实践总结
- 始终使用 HTTPS:即使是内部应用也应该使用 HTTPS
- 定期更新证书:设置提醒和自动化续期
- 使用强加密:禁用旧的 SSL/TLS 版本和弱加密套件
- 启用 HSTS:强制浏览器使用 HTTPS
- 监控证书到期:提前 30 天收到续期提醒
- 备份私钥:安全存储私钥,避免丢失
- 测试配置:使用在线工具测试 SSL 配置安全性

结语
配置 Nginx HTTPS 证书看似复杂,但按照本文的步骤操作,你可以轻松为你的网站或应用启用安全的 HTTPS 连接。记住,安全不是一次性的工作,而是需要持续维护的过程。定期检查证书状态,更新加密配置,监控性能指标,才能确保你的应用始终保持安全可靠。
无论你是使用免费的 Let’s Encrypt 证书,还是购买商业证书,正确的配置都能为你的用户提供安全、快速的浏览体验。结合 Java 应用的适当配置,你可以构建一个完整的端到端安全解决方案。
现在就开始为你的网站启用 HTTPS 吧!
以上就是在Nginx上配置HTTPS证书的完整指南的详细内容,更多关于Nginx配置HTTPS证书的资料请关注脚本之家其它相关文章!
相关文章
nginx could not build the server_names_hash 解决方法
服务器名字的hash表是由指令 server_names_hash_max_size 和 server_names_hash_bucket_size所控制的。2011-03-03


最新评论