告別付費 DDNS!2025 免費DDNS 零成本設定教學(GCP 免費主機 + Cloudflare API 實作 + Draytek )

在現今的網路環境中,無論是架設個人網站、NAS、遠端存取辦公室資源,擁有一個固定的網址來對應我們隨時可能變動的 IP 位址,都顯得至關重要。這篇文章將是您的終極指南,帶您從零開始,利用 Google Cloud Platform (GCP) 的永久免費資源與 Cloudflare 強大的 API,為您的 Draytek 路由器打造一個完全自主、零成本、支援多設備且極度穩定的動態 DNS (DDNS) 伺服器。

目錄

什麼是 DDNS (動態域名系統)?

您可以將網域名稱 (例如 my-office.com) 想像成一個聯絡人的名字,而 IP 位址 (例如 114.33.22.11) 則是他的手機號碼。傳統的 DNS 就像一本靜態的電話簿,您手動將名字對應到號碼。但問題是,大多數家庭或小型辦公室的網路,其 IP 位址是由電信商動態分配的,就像手機號碼會變一樣。

DDNS (Dynamic DNS) 就是一位聰明的、全天候的通訊錄管家。當它發現您的 IP 位址變更時,會自動更新您的「電話簿」,確保別人透過您的名字 (my-office.com),永遠都能找到您最新的號碼 (最新的 IP 位址)。

為何選擇 GCP 免費 VM + Cloudflare?

  • GCP 永久免費 VM (虛擬主機):Google Cloud 提供了一個「Always Free」方案,其中包含一台 e2-micro 等級的虛擬主機,其資源足以 24 小時不間斷地運行我們的輕量級 DDNS 服務,且完全免費。這讓我們擁有了一個穩定的伺服器基礎。

  • Cloudflare DNS API:Cloudflare 不僅是頂級的 CDN 和 DNS 服務商,更提供了強大且免費的 API。我們可以透過程式化呼叫這些 API,來精準、即時地更新我們的 DNS 紀錄,這正是實現 DDNS 的核心。

結合這兩者,我們就能以零成本打造出比許多付費服務更具彈性、更安全且完全在自己掌控之下的 DDNS 系統。

系統架構總覽

由於我們的 GCP 伺服器本身也可能因為重啟而獲得新的動態 IP,所以我們將建立一個雙層的動態更新系統,確保服務永不中斷。

  1. GCP VM 會透過排程任務,定期檢查自己的 IP,並更新 ddns-server.yourdomain.com。
  2. Draytek 路由器 會將 ddns-server.yourdomain.com 作為伺服器位址,將自己的 IP 更新請求發送過去。

前置準備

在開始之前,請確保您已擁有:

  1. 一個 Google 帳號 (用來註冊 GCP)。
  2. 一個 Cloudflare 帳號。
  3. 一個已轉移至 Cloudflare 管理的有效網域名稱 (例如 yourdomain.com)。

第一步:設定 Cloudflare API 權杖

我們需要建立一個專用的 API 權杖,授權我們的程式更新 DNS 紀錄。

Cloudflare API
1.登入 Cloudflare,前往 「設定檔」。
Cloudflare API
2.「API 權杖」>「建立 Token」。
Cloudflare API
3.找到 「編輯區域 DNS」 (Edit zone DNS) 的範本,點擊 「使用範本」。
Cloudflare API
4.權杖名稱:自訂一個易於識別的名稱,例如 : DDNS-Service。區域資源:選擇 包括 - 特定區域 - 您的根網域名稱。
Cloudflare API
5.立刻複製並安全地保存顯示出來的 API 權杖,它只會出現這一次。

第二步:建立與設定 GCP 免費 VM

我們將使用Google Cloud的「Always Free」永久免費方案來架設伺服器。

GCP_Compute Engine
1.選擇一個提供免費方案的美國區域,例如 us-west1 (奧勒岡)。機器類型:選擇 E2 系列中的 e2-micro。
GCP_Compute Engine
2.作業系統:選擇 Debian 或 Ubuntu 的最新穩定版。
GCP_Compute Engine
3.網路服務級別 勾選「標準級(us-west1)」。
GCP_Compute Engine
4.網路標記 欄位,填入 ddns-server。
  • 名稱:自訂一個VM名稱,例如 ddns-vm。
  • 區域 (Region) 與可用區 (Zone):為了符合免費方案資格,請選擇以下其中一個美國區域:
    • us-west1 (奧勒岡)
    • us-central1 (愛荷華)
    • us-east1 (南卡羅來納)
  • 機器設定:
    • 系列:E2
  • 機器類型:e2-micro (這就是免費方案的VM類型)。
  • 開機磁碟:
    • 作業系統請選擇 Debian 或 Ubuntu 的最新版本。
    • 磁碟類型選擇 標準永久磁碟,大小維持預設的10GB即可(免費額度最高到30GB)。
  • 點擊 「建立」,等待幾分鐘,您的免費VM就會啟動了。
GCP_Compute Engine
5.建立防火牆規則。
GCP_Compute Engine
6.標記ddns-server 與VM標籤相同。範圍0.0.0.0/0 等於全部IP,可依安全自行調整範圍。
GCP_Compute Engine
7.Port暫時使用8080。點擊 「建立」。

第三步:部署伺服器自我更新腳本 (Cron Job)

現在我們要連線到VM,並放上核心的Python更新腳本。

GCP_Compute Engine
1.連線到VM:在VM執行個體清單中,找到您的VM,點擊右側的 「SSH」 按鈕。

2.安裝必要軟體:
在SSH終端機中,依序輸入以下指令來更新系統並安裝Python及pip:

				
					sudo apt-get update
sudo apt-get install python3-venv python3-pip -y
				
			

3.為我們的專案建立虛擬環境:

				
					python3 -m venv ddns-env
source ddns-env/bin/activate
pip install requests
				
			

4.建立GCP更新IP腳本檔案:

				
					nano gcp_self_updater.py
				
			
				
					import requests
import sys

# --- (1) 請修改您的設定 ---
CF_API_TOKEN = "貼上您從Cloudflare複製的API權杖"
ZONE_NAME = "domain.com" # <<--- 換成您自己的根網域名稱
SERVER_HOSTNAME = "ddns-server.domain.com" # <<--- 換成您要給伺服器用的網址
# --- 設定結束 ---

API_BASE_URL = "https://api.cloudflare.com/client/v4"
HEADERS = {
    "Authorization": f"Bearer {CF_API_TOKEN}",
    "Content-Type": "application/json"
}

def get_public_ip():
    try:
        response = requests.get('https://api.ipify.org?format=json', timeout=10)
        response.raise_for_status()
        return response.json()['ip']
    except requests.RequestException as e:
        raise Exception(f"無法獲取公網 IP: {e}")

def update_dns():
    print("--- 開始執行伺服器自我 IP 更新 (v5.1) ---")
    try:
        new_ip = get_public_ip()
        print(f"偵測到目前公網 IP: {new_ip}")

        url = f"{API_BASE_URL}/zones"
        response = requests.get(url, headers=HEADERS, params={'name': ZONE_NAME})
        response.raise_for_status()
        zone_id = response.json()['result'][0]['id']
        
        url = f"{API_BASE_URL}/zones/{zone_id}/dns_records"
        response = requests.get(url, headers=HEADERS, params={'name': SERVER_HOSTNAME, 'type': 'A'})
        response.raise_for_status()
        dns_records = response.json()['result']
        
        current_ip = dns_records[0]['content'] if dns_records else None
        
        if new_ip == current_ip:
            print(f"IP 未變更 ({new_ip}),無需更新。")
            return

        print(f"伺服器 IP 已從 {current_ip} 變為 {new_ip},準備更新...")
        
        # [官方文件優化] 明確加入 proxied: false
        dns_record_data = {'type': 'A', 'name': SERVER_HOSTNAME, 'content': new_ip, 'ttl': 120, 'proxied': False}
        
        if dns_records:
            record_id = dns_records[0]['id']
            url = f"{API_BASE_URL}/zones/{zone_id}/dns_records/{record_id}"
            response = requests.put(url, headers=HEADERS, json=dns_record_data)
        else:
            url = f"{API_BASE_URL}/zones/{zone_id}/dns_records"
            response = requests.post(url, headers=HEADERS, json=dns_record_data)
        
        response.raise_for_status()
        if response.json()['success']:
            print(f"***** 伺服器 DNS 紀錄 '{SERVER_HOSTNAME}' 更新成功! *****")
        else:
            raise Exception(f"Cloudflare API 操作失敗: {response.json()['errors']}")

    except Exception as e:
        print(f"!!! 發生錯誤: {e} !!!", file=sys.stderr)

if __name__ == '__main__':
    update_dns()
    print("--- 執行完畢 ---")
				
			

5.修改設定:修改檔案中的 CF_API_TOKEN, ZONE_NAME, SERVER_HOSTNAME

4.設定排程任務:

  • 執行 crontab -e (第一次使用請選 nano 作為編輯器)。
  • 在檔案最下方新增一行 (請將路徑換成您自己的絕對路徑):
				
					0 0 * * * /home/使用者名稱/venv/bin/python3 /home/使用者名稱/gcp_self_updater_requests.py >> /home/clone/gcp_self_updater.log 2>&1
				
			
  • 儲存並離開。

第四步:部署多設備 DDNS 伺服器 (Systemd Service)

這個腳本是核心,它會作為一個網站伺服器,接收來自 Draytek 路由器的請求。

1.建立腳本檔案:

				
					nano ddns_updater.py
				
			
				
					import http.server
import socketserver
from urllib.parse import urlparse, parse_qs
import sys
import requests

# --- (1) 請修改您的設定 ---
CF_API_TOKEN = "貼上您從Cloudflare複製的API權杖"
ZONE_NAME = "domain.com" # <<--- 換成您自己的根網域名稱
ROUTER_SECRETS = {
    'office.domain.com': 'SuperSecretPasswordForOffice',
    'home.domain.com': 'AnotherPasswordForHome',
}
# --- 設定結束 ---

API_BASE_URL = "https://api.cloudflare.com/client/v4"
HEADERS = {
    "Authorization": f"Bearer {CF_API_TOKEN}",
    "Content-Type": "application/json"
}

class DDNSHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        parsed_path = urlparse(self.path)
        params = parse_qs(parsed_path.query)
        hostname = params.get('hostname', [''])[0]
        new_ip = params.get('ip', [''])[0]
        secret = params.get('secret', [''])[0]

        if ROUTER_SECRETS.get(hostname) != secret:
            self.send_error_and_log(401, f"[{hostname}] 認證失敗")
            return
            
        print(f"[{hostname}] 認證成功,收到 IP: {new_ip}")

        try:
            url = f"{API_BASE_URL}/zones"
            response = requests.get(url, headers=HEADERS, params={'name': ZONE_NAME})
            response.raise_for_status()
            zone_id = response.json()['result'][0]['id']

            url = f"{API_BASE_URL}/zones/{zone_id}/dns_records"
            response = requests.get(url, headers=HEADERS, params={'name': hostname, 'type': 'A'})
            response.raise_for_status()
            dns_records = response.json()['result']

            current_ip = dns_records[0]['content'] if dns_records else None
            
            if new_ip == current_ip:
                print(f"[{hostname}] IP 未變更 ({new_ip}),無需更新。")
                self.send_success_response("good", new_ip)
                return

            print(f"[{hostname}] IP 已從 {current_ip} 變為 {new_ip},準備更新...")
            
            # [官方文件優化] 明確加入 proxied: false
            dns_record_data = {'type': 'A', 'name': hostname, 'content': new_ip, 'ttl': 120, 'proxied': False}
            
            if dns_records:
                record_id = dns_records[0]['id']
                url = f"{API_BASE_URL}/zones/{zone_id}/dns_records/{record_id}"
                response = requests.put(url, headers=HEADERS, json=dns_record_data)
            else:
                url = f"{API_BASE_URL}/zones/{zone_id}/dns_records"
                response = requests.post(url, headers=HEADERS, json=dns_record_data)
            
            response.raise_for_status()
            if not response.json()['success']:
                 raise Exception(f"Cloudflare API 操作失敗: {response.json()['errors']}")
            
            print(f"***** [{hostname}] 操作成功!DDNS 更新完成! *****")
            self.send_success_response("good", new_ip)

        except Exception as e:
            self.send_error_and_log(500, f"!!! [{hostname}] 發生嚴重錯誤: {e} !!!")

    def send_success_response(self, status, ip):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(bytes(f"{status} {ip}", "utf-8"))

    def send_error_and_log(self, code, message):
        print(message, file=sys.stderr)
        self.send_response(code)
        self.send_header('Content-type', 'text/plain; charset=utf-8')
        self.end_headers()
        self.wfile.write(message.encode('utf-8'))

    def log_message(self, format, *args):
        return

if __name__ == "__main__":
    with socketserver.TCPServer(("", PORT), DDNSHandler) as httpd:
        print(f"多設備 DDNS 伺服器 (v5.1) 已在通訊埠 {PORT} 啟動...")
        httpd.serve_forever()
				
			

2.修改設定:修改檔案中的 CF_API_TOKEN, ZONE_NAME, ROUTER_SECRETS。

3.設定為系統服務:

  • 建立服務設定檔:
				
					sudo nano /etc/systemd/system/ddns_updater.service
				
			
  • 貼上以下內容 (請將使用者 clone 和路徑換成您自己的):
				
					[Unit]
Description=Cloudflare DDNS Updater Service
After=network.target

[Service]
User=使用者名稱
Group=使用者名稱
WorkingDirectory=/home/使用者名稱/
Environment="PYTHONUNBUFFERED=1"
ExecStart=/home/使用者名稱/ddns-env/bin/python3 /home/使用者名稱/ddns_updater.py
Restart=always

[Install]
WantedBy=multi-user.target
				
			
  • 啟動並設定開機自啟:
				
					sudo systemctl daemon-reload
sudo systemctl start ddns_updater.service
sudo systemctl enable ddns_updater.service
				
			

第五步:設定 Draytek 路由器

Draytek_DDNS

這是將一切串連起來的最後一步。

  1. 登入您的 Draytek 路由器管理介面。
  2. 前往 「 Applications >> Dynamic DNS」,Service Provider 選擇 User-Defined。
  3. Provider Host:填寫您在 gcp_self_updater.py 中為伺服器設定的網域名稱和PORT,例如 ddns-server.domain.com:8080。
  4. Auth Type:Basic。
  5. Service API :依照以下格式填寫:
    /update?hostname=這台設備的A紀錄&ip=###IP###&secret=這台設備的密碼
    • 範例:/update?hostname=office.domain.com&ip=###IP###&secret=SuperSecretPasswordForOffice
  6. 儲存設定,並為您的每一台路由器重複此步驟。

第六步:最終驗證步驟

恭喜您!至此,您已經完成了所有伺服器的部署與設定。現在,讓我們進行最後的驗證,確保整個系統如預期般完美運作。

驗證步驟 (Verification)

1.即時監控服務日誌
首先,在您的 GCP VM SSH 終端機視窗中,執行以下指令來即時監看 DDNS 伺服器的活動日誌:

				
					journalctl -u ddns_updater.service -f
				
			
  • -u ddns_updater.service:指定只看我們建立的這個服務的日誌。
  • -f:代表 “follow”,會持續顯示最新的日誌,視窗會在此等待新訊息。

2.從 Draytek 強制觸發更新
接著,登入您的 Draytek 路由器管理介面,前往 應用 (Applications) >> 動態DNS (Dynamic DNS)。在您設定好的 User-Defined 設定檔那一列,點擊右側的 Force Update 按鈕。

3.觀察日誌輸出
點擊 Force Update 後,立刻回到您第一步的 GCP VM SSH 視窗。如果一切設定正確,您應該會即時看到類似以下的成功訊息:

				
					Jul 20 21:38:00 ddns-server python3[...]: [office.domain.com] 認證成功,收到 IP: 18.163.XX.XX
Jul 20 21:38:04 ddns-server python3[...]: [office.domain.com] IP 已從 1.25.3.204 變為 18.163.XX.XX,準備更新...
Jul 20 21:38:05 ddns-server python3[...]: ***** [office.domain.com] 操作成功!DDNS 更新完成! *****
				
			

看到這些訊息,就代表您的路由器與伺服器之間的通訊與認證已經暢通無阻。

4.在 Cloudflare 確認最終結果
作為最終的驗證,您可以登入您的 Cloudflare 儀表板,進入對應的網域名稱,檢查 DNS 紀錄。您會發現該筆 A 紀錄 (例如 office.domain.com) 的 IP 位址,已經成功更新為您路由器當前的最新 IP。

結語 (Conclusion)

依照本篇教學的完整步驟,您已成功建立一個完全自主、零成本、極度穩定且支援多設備的企業級 DDNS 系統。您不僅擺脫了對第三方付費服務的依賴,更重要的是,將網路的核心控制權牢牢掌握在自己手中。

這套方法的優美之處在於其核心邏輯的通用性。今天我們以 Draytek 為範例,但同樣的伺服器架構完全可以延伸應用到任何支援自訂 DDNS URL 的設備上,例如 Ubiquiti UniFi、Synology/QNAP NAS,甚至是您家中的任何一台個人電腦或 Linux 主機,只需讓它們定時呼叫您伺服器上的那個特定網址即可。

我們未來也計畫推出針對不同品牌設備的設定教學,協助您將這套強大的系統應用到更多的場景中,敬請期待!

技術交給我們,專注你最擅長的事

從網站、網路到資料安全,無論是創業者、工作室或企業,我們都給你最全方位的 IT 支援。