Nginx HTTPS 設定

完整的 Nginx HTTPS 配置指南,包含 Let's Encrypt 憑證取得、安全設定、效能優化與常見問題排解


目錄

  1. 基本 HTTPS 配置
  2. 使用 Let's Encrypt
  3. SSL 配置說明
  4. 安全標頭
  5. 多網域配置
  6. 反向代理配置
  7. 效能優化
  8. 測試與驗證
  9. 常見問題
  10. 總結

基本 HTTPS 配置

最簡單的 HTTPS 設定

server {
    listen 443 ssl;
    server_name example.com;

    # SSL 憑證
    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    # 網站根目錄
    root /var/www/html;
    index index.html index.htm;

    location / {
        try_files $uri $uri/ =404;
    }
}

# HTTP 重定向到 HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

生產環境推薦配置

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

    # SSL 憑證
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # SSL 協定(僅允許 TLS 1.2 和 1.3)
    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;

    # SSL Session
    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/letsencrypt/live/example.com/chain.pem;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    # 安全標頭
    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;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;

    # DH 參數(提升安全性)
    ssl_dhparam /etc/nginx/dhparam.pem;

    # 網站配置
    root /var/www/html;
    index index.html index.htm index.php;

    location / {
        try_files $uri $uri/ =404;
    }

    # PHP 支援(如果需要)
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
    }

    # 日誌
    access_log /var/log/nginx/example.com.access.log;
    error_log /var/log/nginx/example.com.error.log;
}

# HTTP 重定向到 HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # Let's Encrypt 驗證(保留此路徑)
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    # 其他所有請求重定向到 HTTPS
    location / {
        return 301 https://$server_name$request_uri;
    }
}

使用 Let's Encrypt

安裝 Certbot

# Ubuntu/Debian
sudo apt update
sudo apt install certbot python3-certbot-nginx

# CentOS/RHEL
sudo yum install certbot python3-certbot-nginx

# macOS
brew install certbot

取得憑證

# 自動配置 Nginx(推薦)
sudo certbot --nginx -d example.com -d www.example.com

# 僅取得憑證,手動配置
sudo certbot certonly --nginx -d example.com -d www.example.com

# 使用 webroot 方式(伺服器正在運行)
sudo certbot certonly --webroot -w /var/www/html \
  -d example.com -d www.example.com

憑證存放位置

Let's Encrypt 憑證預設位置:

/etc/letsencrypt/live/example.com/
├── fullchain.pem    # 完整憑證鏈(用於 ssl_certificate)
├── privkey.pem      # 私鑰(用於 ssl_certificate_key)
├── chain.pem        # 中繼憑證
└── cert.pem         # 網站憑證

自動續期設定

# 測試續期(不會真的續期)
sudo certbot renew --dry-run

# 手動續期
sudo certbot renew

# 設定自動續期(Cron)
sudo crontab -e
# 每天凌晨 2:00 檢查並續期
0 2 * * * certbot renew --quiet --post-hook "systemctl reload nginx"

# 或使用 systemd timer(Ubuntu 18.04+)
sudo systemctl status certbot.timer

生成 DH 參數

# 生成 2048 位元 DH 參數(需要幾分鐘)
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048

# 或 4096 位元(更安全但更慢)
sudo openssl dhparam -out /etc/nginx/dhparam.pem 4096

SSL 配置說明

SSL 協定版本

協定 狀態 建議
SSLv2 已廢棄 禁用
SSLv3 已廢棄 禁用
TLS 1.0 已過時 禁用
TLS 1.1 已過時 禁用
TLS 1.2 安全 啟用
TLS 1.3 最安全 啟用
# ✅ 推薦:僅允許 TLS 1.2 和 1.3
ssl_protocols TLSv1.2 TLSv1.3;

# ❌ 不安全:不要使用
ssl_protocols SSLv2 SSLv3 TLSv1 TLSv1.1;

加密套件

# 強加密套件(Mozilla Modern)
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';

# 中等強度(Mozilla Intermediate)
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 on;  # TLS 1.2
ssl_prefer_server_ciphers off; # TLS 1.3(推薦)

SSL Session 優化

# Session Cache(減少 TLS 握手次數)
ssl_session_cache shared:SSL:10m;  # 10MB,約 40,000 sessions
ssl_session_timeout 10m;            # Session 有效時間 10 分鐘
ssl_session_tickets off;            # 關閉 session tickets(安全考量)

OCSP Stapling

# 啟用 OCSP Stapling(改善憑證驗證效能)
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/chain.pem;

# DNS 解析器
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

安全標頭

安全標頭總覽

標頭 用途 建議值
Strict-Transport-Security 強制 HTTPS max-age=63072000; includeSubDomains; preload
X-Frame-Options 防止點擊劫持 SAMEORIGIN
X-Content-Type-Options 防止 MIME 嗅探 nosniff
X-XSS-Protection XSS 防護(舊瀏覽器) 1; mode=block
Referrer-Policy 控制 Referer 標頭 no-referrer-when-downgrade
Content-Security-Policy 內容安全策略 依需求設定

HSTS (HTTP Strict Transport Security)

# 強制瀏覽器使用 HTTPS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

# 參數說明:
# max-age=63072000  → 有效期 2 年
# includeSubDomains → 包含所有子網域
# preload           → 加入 HSTS preload list

HSTS Preload 清單

完整安全標頭配置

# 防止點擊劫持 (Clickjacking)
add_header X-Frame-Options "SAMEORIGIN" always;
# DENY - 完全禁止
# SAMEORIGIN - 僅允許同源
# ALLOW-FROM uri - 允許特定網址(已廢棄)

# 防止 MIME 類型嗅探
add_header X-Content-Type-Options "nosniff" always;

# XSS 防護(舊瀏覽器)
add_header X-XSS-Protection "1; mode=block" always;

# Referrer 策略
add_header Referrer-Policy "no-referrer-when-downgrade" always;

# 內容安全策略 (CSP)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;

# 權限策略
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

多網域配置

單一憑證支援多網域

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

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

    # 根據網域名稱分別處理
    if ($host = blog.example.com) {
        root /var/www/blog;
    }

    if ($host ~* ^(www\.)?example\.com$) {
        root /var/www/main;
    }
}

萬用憑證配置

# 取得萬用憑證
sudo certbot certonly --manual --preferred-challenges dns \
  -d example.com -d *.example.com
# 所有子網域共用憑證
server {
    listen 443 ssl http2;
    server_name *.example.com;

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

    # 根據子網域路由
    set $subdomain "";
    if ($host ~* ^([^.]+)\.example\.com$) {
        set $subdomain $1;
    }

    root /var/www/$subdomain;
}

反向代理配置

代理到後端應用

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

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

    # 代理設定
    location / {
        proxy_pass http://localhost: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;

        # WebSocket 支援
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Timeout 設定
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

負載平衡

upstream backend {
    # 負載平衡策略
    least_conn;  # 最少連線(或 ip_hash, random)

    server 192.168.1.101:3000 weight=3;
    server 192.168.1.102:3000 weight=2;
    server 192.168.1.103:3000 backup;  # 備援伺服器
}

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

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

    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;
    }
}

效能優化

HTTP/2 設定

server {
    # 啟用 HTTP/2
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    # HTTP/2 推送(可選)
    location / {
        http2_push /static/style.css;
        http2_push /static/script.js;
    }
}

Gzip 壓縮

# 通常在 nginx.conf 或 http block
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
gzip_disable "msie6";

靜態資源快取

location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

測試與驗證

測試 Nginx 配置

# 檢查配置語法
sudo nginx -t

# 重新載入配置(不中斷服務)
sudo nginx -s reload

# 重啟 Nginx
sudo systemctl restart nginx

# 查看 Nginx 狀態
sudo systemctl status nginx

測試 SSL 配置

# 使用 openssl 測試
openssl s_client -connect example.com:443 -servername example.com

# 查看憑證資訊
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -text

# 測試特定 TLS 版本
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

線上檢測工具

工具 網址 用途
SSL Labs https://www.ssllabs.com/ssltest/ SSL 配置評分(A+ 最佳)
Security Headers https://securityheaders.com/ 安全標頭檢測
HTTP/2 Test https://tools.keycdn.com/http2-test HTTP/2 啟用檢查

常見問題

問題 1:憑證鏈不完整

錯誤:NET::ERR_CERT_AUTHORITY_INVALID

解決:確保使用 fullchain.pem

# ✅ 正確
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;

# ❌ 錯誤:不要使用 cert.pem
ssl_certificate /etc/letsencrypt/live/example.com/cert.pem;

問題 2:混合內容警告

問題:HTTPS 頁面載入 HTTP 資源

解決:設定 CSP 標頭升級不安全請求

add_header Content-Security-Policy "upgrade-insecure-requests" always;

問題 3:憑證過期

# 檢查憑證到期日
sudo certbot certificates

# 手動續期
sudo certbot renew

# 強制續期(即使未到期)
sudo certbot renew --force-renewal

問題 4:Nginx 無法啟動

# 查看錯誤日誌
sudo tail -f /var/log/nginx/error.log

# 常見原因:
# - 80/443 端口被占用
# - 配置檔語法錯誤
# - 憑證檔案路徑錯誤
# - 權限問題

問題 5:SSL Handshake 失敗

# 1. 防火牆是否開放 443 端口
sudo ufw allow 443/tcp

# 2. SELinux 是否阻擋(CentOS/RHEL)
sudo setenforce 0  # 臨時關閉測試

# 3. 檢查 Nginx 錯誤日誌
sudo tail -f /var/log/nginx/error.log

總結

最佳實踐檢查清單

項目 狀態
使用 Let's Encrypt 或商業憑證
啟用 TLS 1.2 和 1.3
禁用 SSL 2.0/3.0 和 TLS 1.0/1.1
使用強加密套件
啟用 HTTP/2
設定 HSTS 標頭
啟用 OCSP Stapling
HTTP 自動重定向到 HTTPS
設定自動續期
配置完整憑證鏈
SSL Labs 測試達到 A 或 A+
設定安全標頭

快速指令參考

操作 指令
取得憑證 sudo certbot --nginx -d example.com
測試續期 sudo certbot renew --dry-run
檢查配置 sudo nginx -t
重載配置 sudo nginx -s reload
查看憑證 sudo certbot certificates
SSL 測試 openssl s_client -connect example.com:443

建立日期:2025-10-28 最後更新:2025-12-04

🔗相關文章