在Nginx上配置HTTPS證書的完整指南
引言
在當今互聯(lián)網(wǎng)環(huán)境中,HTTPS 已經(jīng)成為網(wǎng)站標配。無論是個人博客、企業(yè)官網(wǎng)還是 API 服務(wù),啟用 HTTPS 不僅能提升用戶信任度,還能增強數(shù)據(jù)安全性,甚至影響搜索引擎排名。Nginx 作為高性能的 Web 服務(wù)器和反向代理,是部署 HTTPS 的理想選擇。本文將詳細介紹如何在 Nginx 中配置 HTTPS 證書,涵蓋自簽名證書、Let’s Encrypt 免費證書、商業(yè)證書等多種方案,并提供 Java 應(yīng)用集成示例。
為什么需要 HTTPS?
在深入配置之前,讓我們先理解為什么 HTTPS 如此重要:
- 數(shù)據(jù)加密:防止中間人攻擊,保護用戶隱私
- 身份驗證:確保用戶訪問的是真實的網(wǎng)站而非釣魚網(wǎng)站
- 完整性保護:防止數(shù)據(jù)在傳輸過程中被篡改
- SEO 優(yōu)勢:Google 等搜索引擎優(yōu)先收錄 HTTPS 網(wǎng)站
- 現(xiàn)代瀏覽器要求:Chrome 等瀏覽器對 HTTP 網(wǎng)站標記為"不安全"
// 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 轉(zhuǎn)換為 HTTPS
String url = request.getRequestURL().toString();
return url.replaceFirst("http://", "https://");
}
}
}準備工作
在配置 HTTPS 之前,確保你已準備好以下內(nèi)容:
- 域名:擁有一個已備案的域名(國內(nèi)服務(wù)器需要)
- 服務(wù)器:安裝了 Nginx 的 Linux 服務(wù)器
- SSH 訪問權(quán)限:能夠通過 SSH 連接到服務(wù)器
- 防火墻配置:開放 443 端口
檢查 Nginx 是否已安裝:
nginx -v
如果未安裝,可以使用以下命令安裝:
# Ubuntu/Debian sudo apt update sudo apt install nginx # CentOS/RHEL sudo yum install epel-release sudo yum install nginx
方案一:自簽名證書(開發(fā)測試環(huán)境)
自簽名證書適合開發(fā)和測試環(huán)境,但在生產(chǎn)環(huán)境中瀏覽器會顯示安全警告。
生成自簽名證書
# 創(chuàng)建證書目錄
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 安全設(shè)置
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
# 創(chuàng)建符號鏈接 sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/ # 測試配置 sudo nginx -t # 重啟 Nginx sudo systemctl restart nginx
// Java 示例:Spring Boot 應(yīng)用中強制 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 免費證書(生產(chǎn)環(huán)境推薦)
Let’s Encrypt 提供免費的 SSL/TLS 證書,有效期 90 天,支持自動續(xù)期。
安裝 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 相關(guān)配置。
手動配置 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;
}自動續(xù)期配置
Let’s Encrypt 證書每 90 天需要續(xù)期一次。Certbot 可以自動處理:
# 測試續(xù)期(不會實際執(zhí)行) sudo certbot renew --dry-run # 設(shè)置定時任務(wù)自動續(xù)期 sudo crontab -e
添加以下行(每天凌晨 2 點檢查是否需要續(xù)期):
0 2 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"
// Java 示例:Spring Boot 應(yīng)用中的 HTTPS 相關(guān)配置
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);
}
};
}
}方案三:商業(yè)證書(企業(yè)級應(yīng)用)
對于企業(yè)級應(yīng)用,可能需要購買商業(yè) SSL 證書,如 DigiCert、GeoTrust、Symantec 等。
申請商業(yè)證書流程
- 生成 CSR(Certificate Signing Request)
# 生成私鑰 openssl genrsa -out example.com.key 2048 # 生成 CSR openssl req -new -key example.com.key -out example.com.csr
- 提交 CSR 到證書頒發(fā)機構(gòu)
- 驗證域名所有權(quán)
- 下載證書文件
配置商業(yè)證書
商業(yè)證書通常包含多個文件:
- 主證書:
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;
# 商業(yè)證書配置
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;
}
# 靜態(tài)資源緩存
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;
}
# 方法二:保留原始請求路徑和參數(shù)
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 / {
# 繼續(xù)使用 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"))) {
// 構(gòu)建 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 性能優(yōu)化

啟用 HTTP/2
HTTP/2 可以顯著提升 HTTPS 網(wǎng)站性能:
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 緩沖區(qū)優(yōu)化
# SSL 緩沖區(qū)大小 ssl_buffer_size 4k; # SSL 預讀 ssl_preread on;
安全加固配置
SSL 協(xié)議和加密套件
# 推薦的安全配置 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)
# 嚴格的內(nèi)容安全策略 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 環(huán)境下的 HTTPS 配置
在 Docker 環(huán)境中配置 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 應(yīng)用配置
// Java 示例:Docker 環(huán)境中的 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...
}
}證書續(xù)期和監(jiān)控
自動化續(xù)期腳本
#!/bin/bash
# ssl-renew.sh
LOG_FILE="/var/log/ssl-renew.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$DATE] 開始檢查 SSL 證書續(xù)期..." >> $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 證書續(xù)期成功" >> $LOG_FILE
else
echo "[$DATE] Let's Encrypt 證書續(xù)期失敗" >> $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 天,發(fā)送警告
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證書監(jiān)控 Java 類
// Java 示例:證書到期監(jiān)控
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狀態(tài): %s\n有效期: %s 至 %s\n剩余天數(shù): %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("---");
}
}
}高級配置:多域名和通配符證書
多域名證書配置
# 多域名服務(wù)器塊
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;
# 動態(tài)根目錄基于子域名
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 示例:基于子域名的動態(tài)路由
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 "博客內(nèi)容";
case "shop":
return "商店內(nèi)容";
case "api":
return "API 文檔";
default:
return "主頁內(nèi)容";
}
}
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 應(yīng)用與 HTTPS 集成
Spring Boot HTTPS 配置
// Java 示例:Spring Boot 內(nèi)置 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 {
// 創(chuàng)建信任所有證書的 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());
// 創(chuàng)建 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);
}
// 生產(chǎn)環(huán)境安全的 REST 客戶端
@Bean
public RestTemplate secureRestTemplate() {
// 使用系統(tǒng)默認的信任庫
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
return new RestTemplate(requestFactory);
}
}性能監(jiān)控和日志分析
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 監(jiān)控類
// Java 示例:HTTPS 性能監(jiān)控
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();
// 記錄請求總數(shù)
totalRequests.incrementAndGet();
// 記錄 HTTPS 請求數(shù)
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" +
"總請求數(shù): %d\n" +
"HTTPS 請求數(shù): %d (%.2f%%)\n" +
"端點數(shù)量: %d\n" +
"平均響應(yīng)時間: %.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 握手失敗
# 調(diào)試 SSL 配置 ssl_buffer_size 16k; ssl_session_tickets off; # 更寬松的加密套件(僅用于調(diào)試) ssl_ciphers HIGH:!aNULL:!MD5;
Java 應(yīng)用 HTTPS 問題排查
// Java 示例:SSL/TLS 調(diào)試工具
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 調(diào)試
System.setProperty("javax.net.debug", "ssl:handshake:verbose");
// 創(chuàng)建信任所有證書的 SSL 上下文(僅用于調(diào)試?。?
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());
// 創(chuàng)建所有主機都信任的主機名驗證器
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("響應(yīng)碼: " + responseCode);
System.out.println("響應(yīng)消息: " + connection.getResponseMessage());
SSLSession session = connection.getSSLSession();
if (session != null) {
System.out.println("SSL 協(xié)議: " + session.getProtocol());
System.out.println("SSL 密碼套件: " + session.getCipherSuite());
System.out.println("對等證書數(shù)量: " + session.getPeerCertificates().length);
}
connection.disconnect();
} catch (IOException e) {
System.err.println("連接失敗: " + e.getMessage());
e.printStackTrace();
}
}
public static void main(String[] args) {
// 啟用調(diào)試(生產(chǎn)環(huán)境不要使用!)
enableSSLDebugging();
// 測試連接
testConnection("https://example.com");
}
}最佳實踐總結(jié)
- 始終使用 HTTPS:即使是內(nèi)部應(yīng)用也應(yīng)該使用 HTTPS
- 定期更新證書:設(shè)置提醒和自動化續(xù)期
- 使用強加密:禁用舊的 SSL/TLS 版本和弱加密套件
- 啟用 HSTS:強制瀏覽器使用 HTTPS
- 監(jiān)控證書到期:提前 30 天收到續(xù)期提醒
- 備份私鑰:安全存儲私鑰,避免丟失
- 測試配置:使用在線工具測試 SSL 配置安全性

結(jié)語
配置 Nginx HTTPS 證書看似復雜,但按照本文的步驟操作,你可以輕松為你的網(wǎng)站或應(yīng)用啟用安全的 HTTPS 連接。記住,安全不是一次性的工作,而是需要持續(xù)維護的過程。定期檢查證書狀態(tài),更新加密配置,監(jiān)控性能指標,才能確保你的應(yīng)用始終保持安全可靠。
無論你是使用免費的 Let’s Encrypt 證書,還是購買商業(yè)證書,正確的配置都能為你的用戶提供安全、快速的瀏覽體驗。結(jié)合 Java 應(yīng)用的適當配置,你可以構(gòu)建一個完整的端到端安全解決方案。
現(xiàn)在就開始為你的網(wǎng)站啟用 HTTPS 吧!
以上就是在Nginx上配置HTTPS證書的完整指南的詳細內(nèi)容,更多關(guān)于Nginx配置HTTPS證書的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
nginx 499錯誤處理及nginx的配置參數(shù)小結(jié)
在項目容器化改造中,修改Nginx超時設(shè)置可解決499錯誤,本文就來介紹一下nginx 499錯誤處理及nginx的配置參數(shù)小結(jié),感興趣的可以了解一下2024-09-09
詳解ngx_http_session_log_module的使用
本文主要介紹了詳解ngx_http_session_log_module的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2025-05-05
Nginx服務(wù)器的location指令匹配規(guī)則詳解
這篇文章主要介紹了Nginx服務(wù)器的location指令匹配規(guī)則,文中介紹了一種動靜態(tài)地址分離的方法示例,需要的朋友可以參考下2015-12-12
服務(wù)器使用Nginx部署Springboot項目的詳細教程(jar包)
這篇文章主要介紹了服務(wù)器使用Nginx部署Springboot項目的詳細教程(jar包),本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07

