許多開發者第一次把專案部署到正式環境時,都會遇到同一個問題: 我想讓網站變成 HTTPS,要怎麼做?
好消息是:只要你使用 Docker + Nginx,搭配 Let’s Encrypt + Certbot,其實很快就能讓服務安全上線,而且憑證還會「自動更新」,再也不用擔心過期。
本文我會用最簡單、最實用的方式示範整個流程,並在關鍵環節搭配圖示,幫助你更快理解架構。🎯 目標架構
• 使用 Docker Compose 管理所有服務
• 一個簡單 Frontend(HTML)
• 一個簡單 Backend(API 回傳文字)
• DuckDNS 提供免費子網域
• Nginx 當反向代理
• Let’s Encrypt + Certbot 簽發與自動續期 HTTPS
🧱 全體架構一頁看懂

• Nginx 負責對外提供 HTTP/HTTPS 入口
• Certbot 需要使用 .well-known/acme-challenge 來驗證你的網域
• 憑證產生後,直接放在共用的 volume
• Nginx 就能使用它來啟動 HTTPS
步驟一: 基礎的前後端容器架構
1-1 專案架構
https-demo/
├── backend/ # Flask API 後端
├── frontend/ # 靜態網頁前端
├── nginx/ # Nginx 反向代理
├── certbot/ # SSL 憑證存放
└── compose.yml # Docker Compose 配置
1-2 後端服務
建立 backend/app.py:
from flask import Flask
app = Flask(__name__)
@app.route("/api")
def api():
return "ok my https success"
app.run(host="0.0.0.0", port=5000)
建立 backend/Dockerfile:
FROM python:3.11-slim
WORKDIR /app
RUN pip install flask
COPY app.py .
CMD ["python", "app.py"]
1-3 前端服務
建立 frontend/index.html:
<!DOCTYPE html>
<html>
<body>
<h1>HTTPS Demo</h1>
<p id="result">Loading...</p>
<script>
fetch('/api')
.then(res => res.text())
.then(text => {
document.getElementById('result').innerText = text
})
.catch(() => {
document.getElementById('result').innerText = 'API error'
})
</script>
</body>
</html>
1-4 Nginx 配置(HTTP 版)
建立 nginx/default.conf:
server {
listen 80;
server_name localhost;
# Let's Encrypt 憑證驗證路徑(申請憑證時必需)
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
root /usr/share/nginx/html;
index index.html;
}
location /api {
proxy_pass http://backend:5000;
}
}
建立 nginx/Dockerfile:
FROM nginx:alpine
# 複製 Nginx 設定檔
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
# 複製前端靜態檔案到容器內(避免權限問題)
COPY frontend/ /usr/share/nginx/html/
# 設定正確的權限
RUN chmod -R 755 /usr/share/nginx/html && \
chown -R nginx:nginx /usr/share/nginx/html
1-5 Docker Compose 配置
建立 compose.yml:
services:
backend:
build: ./backend
nginx:
build:
context: .
dockerfile: nginx/Dockerfile
ports:
- "8888:80" # HTTP port
depends_on:
- backend
1-6 啟動服務
# 建置並啟動
docker compose up -d
# 查看狀態
docker compose ps
# 開啟瀏覽器訪問 http://your-server-ip:8888,應該看到前端頁面。
curl http://localhost:8888
步驟二: DuckDNS 提供免費子網域
通常我們將網站架設好之後, 預設都會用「http://201.xx.xx.xx」的方式來存取我們的網站, 但這樣就把我們家的ip暴露到外部了, 對於安全性來講少了一層防護, 因此我們可以藉由DNS網域的方式來避免IP直接暴露, 而這篇主要是為了教學使用免費的 DuckDNS 來示範, 更安全的網站可以使用付費的DNS服務, 功能會更多也更安全。
2-1 註冊並設定
• 使用 GitHub/Google 帳號登入
• 建立一個子域名,例如:myapp.duckdns.org
• 在 current ip 欄位填入您的伺服器公開 IP
• 點擊 update ip 按鈕
2-2 更新 Nginx 配置
server {
listen 80;
server_name myapp.duckdns.org; # 改成你的域名
# Let's Encrypt 憑證驗證路徑(申請憑證時必需)
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
root /usr/share/nginx/html;
index index.html;
}
location /api {
proxy_pass http://backend:5000;
}
}
2-3 重啟服務
docker compose build nginx
docker compose up -d
# 測試訪問 http://myapp.duckdns.org:8888
步驟三:啟用 HTTPS(Let's Encrypt + Certbot)
3-1 HTTPS 運作原理

Let's Encrypt:免費的憑證授權機構(CA),提供 90 天有效期的 SSL 憑證。
Certbot:自動化工具,負責:
• 向 Let's Encrypt 申請憑證
• 驗證您擁有該域名
• 自動續期憑證(每 90 天)
3-2 憑證申請流程

重要觀念:
• HTTP (Port 80/8888) 的作用:Let's Encrypt 必須透過 HTTP 訪問 /.well-known/acme-challenge/ 來驗證域名所有權。
• 為什麼不能只用 HTTPS:在還沒有憑證之前,無法啟用 HTTPS,所以需要先用 HTTP 完成驗證。
• 驗證完成後:HTTP 可以設定為自動重定向到 HTTPS。
3-3 準備 Certbot 目錄
# 建立憑證存放目錄
mkdir -p certbot/conf certbot/www
chmod -R 755 certbot
3-4 更新 Docker Compose(加入 Certbot)
services:
backend:
build: ./backend
nginx:
build:
context: .
dockerfile: nginx/Dockerfile
ports:
- "8888:80" # HTTP(用於 Let's Encrypt 驗證 + 重定向)
- "8889:443" # HTTPS(加密連線)
volumes:
# 掛載憑證目錄(憑證需要持久化儲存)
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
depends_on:
- backend
certbot:
image: certbot/certbot
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
# 每 12 小時檢查憑證是否需要續期
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
🎯 憑證自動續期機制

3-5 一鍵申請憑證腳本
建立 init-letsencrypt.sh:
#!/bin/bash
# 設定您的域名和 Email
DOMAIN="myapp.duckdns.org"
EMAIL="your-email@example.com"
echo "### 啟動 Nginx(HTTP 模式)..."
docker compose up -d nginx
echo "### 等待 Nginx 啟動..."
sleep 5
echo "### 申請 Let's Encrypt 憑證..."
docker compose run --rm certbot certonly --webroot \
--webroot-path=/var/www/certbot \
--email $EMAIL \
--agree-tos \
--no-eff-email \
-d $DOMAIN
if [ $? -eq 0 ]; then
echo "### 憑證申請成功!"
echo "### 切換到 HTTPS 配置..."
# 修改 Dockerfile 使用 https.conf
sed -i 's/default.conf/https.conf/g' nginx/Dockerfile
echo "### 重新建置並啟動 Nginx(HTTPS 模式)..."
docker compose build nginx
docker compose up -d
echo "### 完成!"
echo "### HTTP: http://$DOMAIN:8888 (會自動重定向到 HTTPS)"
echo "### HTTPS: https://$DOMAIN:8889"
else
echo "### 憑證申請失敗,請檢查:"
echo "1. 域名是否正確指向此伺服器 IP"
echo "2. 防火牆是否開放 8888 port"
echo "3. Nginx 是否正常運行"
echo "4. 是否能夠訪問 http://$DOMAIN:8888"
fi
設定執行權限:
chmod +x init-letsencrypt.sh
3-6 更新 Nginx 配置啟用 HTTPS
憑證申請成功後,編輯 nginx/default.conf,將 HTTP server 的 location / 改為重定向,並新增 HTTPS server 區塊:
# HTTP 伺服器:處理 Let's Encrypt 驗證 + 重定向到 HTTPS
server {
listen 80;
server_name myapp.duckdns.org;
# Let's Encrypt 憑證驗證路徑(必須使用 HTTP)
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# 其他所有請求都重定向到 HTTPS
location / {
return 301 https://$host:8889$request_uri;
}
}
# HTTPS 伺服器:處理加密連線
server {
listen 443 ssl http2;
server_name myapp.duckdns.org;
# SSL 憑證檔案位置
ssl_certificate /etc/letsencrypt/live/myapp.duckdns.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.duckdns.org/privkey.pem;
# SSL 安全性設定
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers HIGH:!aNULL:!MD5;
# 前端靜態檔案
location / {
root /usr/share/nginx/html;
index index.html;
}
# 後端 API 反向代理
location /api {
proxy_pass http://backend:5000;
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;
}
}
⚠️ 3-7 暫時更改80 port 再執行 init-letsencrypt.sh 申請憑證 ⚠️
• Let's Encrypt 的 HTTP-01 驗證方式硬性規定使用 port 80
• 無法使用其他 port(如 8888)
• 但只在申請和續期憑證時才需要,平時可以使用自訂 port
建議做法:申請憑證時暫時停止佔用 80 port 的服務
# 1. 停止佔用 port 80 的服務(例如其他 Nginx)
sudo systemctl stop nginx
# 或
docker stop <container-name>
# 2. 修改 compose.yml 使用標準 port 80
ports:
- "80:80" # 申請憑證階段使用標準 port
# 3. 執行腳本申請憑證
./init-letsencrypt.sh
# 4. 憑證申請成功後,改回自訂 port
ports:
- "8888:80"
- "8889:443"
# 5. 重新啟動此專案
docker compose up -d
# 6. 恢復原本的服務
sudo systemctl start nginx
3、8-測試 HTTPS
• 開啟瀏覽器訪問 https://myapp.duckdns.org:8889
• 檢查瀏覽器網址列是否顯示鎖頭圖示
• 點擊鎖頭可查看憑證資訊
🎁 給想直接動手的你:完整範例 Repo
如果你看到這裡,代表你已經真正理解 HTTPS 這一整套在做什麼了 👏
我把這篇文章的內容,整理成一個可直接跑的 GitHub Repo,裡面包含:
• 🐳 完整 compose.yml
• 🔐 Nginx(HTTP → HTTPS)設定範例
• 🤖 Certbot 自動續期配置
• 🧪 可重現的測試流程與指令
• 📁 清楚的專案結構(不是只有一堆檔案)
🔗 GitHub Repo: https://github.com/weihanchen/docker-nginx-letsencrypt-certbot
✍️ 結語:HTTPS 不難,只是以前沒人好好講
HTTPS 真正困難的不是設定檔,而是:
• 不知道驗證在驗什麼
• 不知道為什麼一定要 HTTP
• 不知道為什麼大家一直 renew
希望這篇文章,可以讓你之後看到 HTTPS 時,心裡是:
😌「喔~我懂這一整套在幹嘛了」













