﻿# 如何在 Ubuntu 22.04.5 LTS 上安裝和配置 Cloudflare Tunnel 服務

最近幫同事建立了一台測試機，我使用 [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) 提供客戶測試網址，這樣就不需要處理防火牆的種種瑣事，不但減少了許多管理上的麻煩，透過 Cloudflare 的平臺檔在前面也相對安全許多。這篇文章會記錄一下我在 Ubuntu 22.04.5 LTS 上安裝和配置 Cloudflare Tunnel 的過程。

![Cloudflare Tunnel and Ubuntu](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/8ae0e0b9bf4e200c9df5-559ecbfd-19e4-4d36-9882-76cdb60bf0ad.webp)

## 安裝設定 Cloudflare Tunnel 步驟

1.  安裝 Cloudflare Tunnel 命令列工具
    
    ```bash
    wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
    sudo dpkg -i cloudflared-linux-amd64.deb
    ```
    
2.  登入 Cloudflare 帳號
    
    這邊我假設我的 Ubuntu 登入帳號為 `test1`，並且已經在 Cloudflare 上建立了一個**帳號**(account)，如果你還沒有 Cloudflare 帳號，可以到[這裡](https://dash.cloudflare.com/sign-up)建立一個帳號。
    
    ```bash
    cloudflared tunnel login
    ```
    
    接著出現以下的提示，請點擊網址開啟瀏覽器並使用你的 Cloudflare 帳號登入：
    
    ```
    Please open the following URL and log in with your Cloudflare account:
    
    https://dash.cloudflare.com/argotunnel?aud=&callback=https%3A%2F%2Flogin.cloudflareaccess.org%2FxxxxGASfxuYgpAxxxxGA3y4Y3QHKATnxxxxxKjQeUE%3D
    
    Leave cloudflared running to download the cert automatically.
    ```
    
    登入後會出現 `Authorize Cloudflare Tunnel` 的網頁，並顯示所有你有權限管理的 Domain Name 清單，請選擇你要使用的網域，然後點擊 `Authorize` 按鈕。
    
    登入並授權之後，它會建立一把代表你 Cloudflare 帳號下某一個「域名」的憑證檔: `/home/test1/.cloudflared/cert.pem`
    
3.  建立 Cloudflare Tunnel
    
    ```bash
    tunnel_name="dev-test1"
    
    # 建立通道
    cloudflared tunnel create $tunnel_name
    
    # 建立 DNS 記錄
    cloudflared tunnel route dns $tunnel_name $tunnel_name-80
    cloudflared tunnel route dns $tunnel_name $tunnel_name-443
    cloudflared tunnel route dns $tunnel_name $tunnel_name-3000
    
    # 取得通道 ID
    tunnel_id=$(cloudflared tunnel info $tunnel_name | grep -oP 'Your tunnel \K([a-z0-9-]+)')
    
    # 建立 cloudflared 設定檔
    cat <<EOF > ~/.cloudflared/config.yml
    tunnel: $tunnel_name
    credentials-file: /home/test1/.cloudflared/$tunnel_id.json
    
    ingress:
      - hostname: $tunnel_name-80.miniasp.com
        service: http://localhost:80
      - hostname: $tunnel_name-443.miniasp.com
        service: http://localhost:443
      - hostname: $tunnel_name-3000.miniasp.com
        service: http://localhost:3000
      - service: http_status:404
    EOF
    ```
    
    💡 上述程式碼最重要的一段，就是結尾的 **\- service: http\_status:404** 一定要加上，否則執行時就會出現 **The last ingress rule must match all URLs** 錯誤！🔥
    
    上述命令可以直接建立一個通道、三個 DNS 記錄 (CNAME)，並且用三個域名對應到本機的三個 Ports
    
    ```
    Tunnel credentials written to /home/test1/.cloudflared/a010e7d5-c830-4baa-ac8a-9e67843586c7.json. cloudflared chose this file based on where your origin certificate was found. Keep this file secret. To revoke these credentials, delete the tunnel.
    
    Created tunnel dev-test1 with id a010e7d5-c830-4baa-ac8a-9e67843586c7
    2025-04-09T06:12:18Z INF Added CNAME dev-test1-80.miniasp.com which will route to this tunnel tunnelID=a010e7d5-c830-4baa-ac8a-9e67843586c7
    2025-04-09T06:12:21Z INF Added CNAME dev-test1-443.miniasp.com which will route to this tunnel tunnelID=a010e7d5-c830-4baa-ac8a-9e67843586c7
    2025-04-09T06:12:23Z INF Added CNAME dev-test1-3000.miniasp.com which will route to this tunnel tunnelID=a010e7d5-c830-4baa-ac8a-9e67843586c7
    ```
    
4.  啟動 Cloudflare Tunnel
    
    由於我是用一般使用者身分執行，若要持續運行 Cloudflare Tunnel，要使用 `nohup` 指令來執行，這樣就不會因為關閉終端機而中斷連線。
    
    ```bash
    nohup cloudflared tunnel run &
    ```
    
    如果你想跑在背景服務，請先 `sudo` 成 `root` 身分，然後執行以下命令：
    
    ```ini
    cat << EOF > /etc/systemd/system/cloudflared.service
    [Unit]
    Description=Cloudflared Tunnel Service
    After=network.target
    
    [Service]
    ExecStart=/usr/local/bin/cloudflared tunnel run
    User=test1
    Group=test1
    Restart=always
    RestartSec=5
    
    [Install]
    WantedBy=multi-user.target
    EOF
    
    # 設定服務文件的權限
    chmod 644 /etc/systemd/system/cloudflared.service
    
    # 重新載入 systemd 配置
    systemctl daemon-reload
    
    # 啟動服務
    systemctl start cloudflared
    
    # 啟用服務（開機自動啟動）
    systemctl enable cloudflared
    
    # 查看服務狀態
    systemctl status cloudflared
    ```
    
5.  如果你還沒有網頁可以連線，我會用以下方法快速提供一個網頁供測試之用！
    
    ```bash
    sudo npm i -g rimraf serve
    
    # 建立一個測試用的網頁
    cat <<EOF > index.html
    <h1>Hi</h1>
    EOF
    
    # 建立本機 HTTP 伺服器
    serve -l 3000 &> /dev/null &
    curl -v http://localhost:30016
    
    # 查看正在運行的 cloudflared
    ps -ef | grep cloudflared
    
    # 從 Cloudflare 網路測試連線
    curl -v https://dev-test1-3000.miniasp.com/
    ```
    

### 刪除所有 Cloudflare Tunnel 的步驟

1.  列出所有已經建立的 Tunnel 清單
    
    ```bash
    cloudflared tunnel list
    ```
    
    假設你的 Tunnel 名稱為 `dev-test1` 的話，可以用以下命令查看目前是否有任何既有的**通道連線**：
    
    ```bash
    cloudflared tunnel info dev-test1
    ```
    
    只要有顯示資料，就代表目前的 Tunnel 正在運作中，顯示結果大概會長這樣：
    
    ```
    test1@myvm:~$ cloudflared tunnel info dev-test1
    NAME:     dev-test1
    ID:       a010e7d5-c830-4baa-ac8a-9e67843586c7
    CREATED:  2025-04-08 18:52:40.152082 +0000 UTC
    
    CONNECTOR ID                         CREATED              ARCHITECTURE VERSION  ORIGIN IP     EDGE
    788009df-fae0-49d0-b43b-be58a968df29 2025-04-09T01:32:57Z linux_amd64  2025.4.0 xxx.xxx.x.xxx 2xkhh01, 2xtpe01
    ```
    
    我寫了一支 Bash Script 來顯示所有 Tunnel 的狀態，這樣就可以很方便的查看目前所有 Tunnel 的狀態：
    
    ```bash
    #!/bin/bash
    
    # 取得所有 tunnel ID 和名稱
    tunnels=$(cloudflared tunnel list | tail -n +3 | awk '{print $1 "," $2}')
    
    echo "===== 所有 Cloudflared Tunnel 的連線資訊 ====="
    echo ""
    
    # 對每個 tunnel 執行 info 命令
    while IFS= read -r tunnel_info; do
        tunnel_id=$(echo $tunnel_info | cut -d',' -f1)
        tunnel_name=$(echo $tunnel_info | cut -d',' -f2)
    
        echo "===== 通道名稱: $tunnel_name (ID: $tunnel_id) ====="
        cloudflared tunnel info $tunnel_name
        echo ""
        echo "------------------------------------------------"
        echo ""
    done <<< "$tunnels"
    ```
    
2.  刪除 Tunnel
    
    假設你的 Tunnel 名稱為 `dev-test1` 的話，可以用以下命令刪除：
    
    ```bash
    cloudflared tunnel cleanup dev-test1
    cloudflared tunnel delete dev-test1
    ```
    
3.  刪除之前建立的路由(域名)
    
    目前 cloudflared 並沒有支援透過 CLI 刪除路由 (DNS)，建議自己連到 [https://dash.cloudflare.com/](https://dash.cloudflare.com/) 並手動刪除所有之前自動建立的 CNAME 記錄。
    
    不過，我在 Cloudflare 的 DNS 管理很多子域名，要找出哪些 CNAME 是 Cloudflare Tunnel 自動建立的，可以透過以下方式檢查。
    
    第一種方法，是直接在 Cloudflare 的 DNS 管理頁面中，拿 `.cfargotunnel.com` 這個關鍵字去搜尋，可以找出過往曾經透過 Cloudflare Tunnel 建立的 CNAME 記錄。
    
    第二種方法，則是用 `cloudflared tunnel list` 找出 Tunnel 的 ID，再拿 ID 去搜尋也可以找到。
    

### 關於 Cloudflare Tunnel 的觀念整理

-   瞭解「域名憑證」的位置
    
    每次透過 `cloudflared tunnel login` 登入會取得「域名憑證」，預設檔案會置於 `~/.cloudflared/cert.pem`。
    
    如果在執行 `cloudflared tunnel list` 命令時，要指定「域名帳戶」來列出所有已建立的 Tunnel 可以執行以下命令：
    
    ```bash
    cloudflare tunnel --origincert '~/.cloudflared/cert.pem' list
    ```
    
    將憑證檔更名後，就可以再登入一次，以取得另一個「域名」的憑證，這樣就可以用多個域名在同一台主機操作：
    
    ```bash
    cloudflare tunnel --origincert '~/.cloudflared/cert-domain1.pem' list
    cloudflare tunnel --origincert '~/.cloudflared/cert-domain2.pem' list
    ```
    
-   瞭解「通道」的概念
    
    建立通道時，會產生一個 GUID 格式的 `ID`，這個 ID 會用來識別這個通道，並且會產生一個以 `Tunnel ID` 為名的 JSON 檔案，這是代表一個通道的密鑰。預設檔名與路徑如下：
    
    ```
    ~/.cloudflared/{ID}.json
    ```
    
    一個 Cloudflare **域名** (domain) 有多個**通道** (tunnel)，每個**通道**可以有最多 `100` 個**連線**(connection)。
    
    > [Tunnel availability and failover](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/configure-tunnels/tunnel-availability/)
    
    Cloudflare Tunnel 可已建立多個副本 ([cloudflared replicas](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/configure-tunnels/tunnel-availability/#cloudflared-replicas))，主要用來作為**可用性**和**故障轉移**等情境，不是用來作為**負載平衡**之用。如有**負載平衡**需求，需改用 [Cloudflare Load Balancers](https://developers.cloudflare.com/load-balancing/)。
    
-   關於 Cloudflare Tunnel 的設定檔
    
    Cloudflare Tunnel 的設定檔預設路徑為 `~/.cloudflared/config.yml`，這個檔案在建立 Tunnel 時**不會**自動產生。
    
    如果沒有這個設定檔，就無法直接用 `cloudflared tunnel run` 啟動通道執行，必須指定通道名稱或通道 ID。
    
    ```bash
    cloudflared tunnel run {tunnel_name}
    ```
    
    如果手動建立該檔案，可以定義 `tunnel:` 為預設 Tunnel ID，例如：
    
    ```yaml
    tunnel: {tunnel_id}
    ```
    
    這樣就可以直接建立連線：
    
    ```bash
    cloudflared tunnel run
    ```
    
    不過，建立了通道，依然外面是無法連線的，因為你並沒有設定**路由** (route) (ingress rules)。
    
    通道通常需要一個**路由** (route) 來讓網路建立連線，而每個**路由**通常會綁定一個**域名** (dns)。
    
    ```bash
    cloudflared tunnel route dns {tunnel_name} {hostname}
    ```
    
    > 這裡的 `{hostname}` 指的是你的子域名名稱，如果你授權登入的域名是 `miniasp.com` 的話，這裡 `hostname` 設定為 `test-1`，那麼你就可以透過 `test-1.miniasp.com` 來連線。
    
    直接透過命令列工具建立連線的方式是：
    
    ```bash
    cloudflared tunnel run --url http://localhost:3000
    ```
    
    > 建立好通道連線後，就可以用 `{hostname}.miniasp.com` 來連到本機的 `http://localhost:3000` 位址。
    
    如果想要連接多個本地網路服務，可以在 `~/.cloudflared/config.yml` 設定檔中，加入多個 `ingress` 規則，這樣就可以透過不同的子域名來連接不同的本地網路服務。
    
    ```yaml
    tunnel: {tunnel_id}
    
    ingress:
      - hostname: test-1.miniasp.com
        service: http://localhost:80
      - hostname: test-2.miniasp.com
        service: http://localhost:443
      - hostname: test-3.miniasp.com
        service: http://localhost:3000
      - service: http_status:404
    ```
    
    這樣就可以直接用以下命令使用預設的通道建立連線：
    
    ```bash
    cloudflared tunnel run
    ```
    
    > 這幾個域名記得都要先透過 `cloudflared tunnel route dns {tunnel_name} {hostname}` 註冊域名，否則一樣連不上
    
-   我覺得 `cloudflared` 有幾個設計不良的地方
    
    首先，執行 `cloudflared tunnel run` 時所呈現的 Log 完全看不出路由資訊，對於不熟悉 Cloudflare Tunnel 的人來說，會不知道應該給別人什麼網址連線！然後，目前的 `cloudflared` 也完全沒有方法可以取得之前建立的路由資訊，真的要「回憶」起你當初到底建立哪一個，必須要透過以下步驟查看。
    
    1.  透過 `cloudflared tunnel list` 取得 Tunnel ID (你總該先知道正在用哪個 Tunnel 吧)
        
    2.  然後用瀏覽器登入 Cloudflare 帳號，進入到 `DNS` 頁面，然後搜尋 `Tunnel ID` 才能找到之前替這個 Tunnel 建立的 CNAME 記錄，這才是你可以連上這個 Tunnel 的域名。
        
    
    再者，另一個不方便的地方，就是你完全找不到你正準備連線的 Tunnel 到底是綁定哪個域名。
    
    我們需透過 `cloudflared tunnel login` 登入會取得「域名憑證」，這個憑證其實是綁定一個「域名」的，預設檔案會置於 `~/.cloudflared/cert.pem`。但是，該檔案看不出到底當初授權的是哪個域名，這真的很討厭啊，我域名很多，換來換去的，我自己都會忘記當初到底是建立哪一個域名的憑證。
    
    解決方法，就是不要用「預設憑證」路徑，登入後就改名，這樣可能比較不會忘記。
    
    例如: `~/.cloudflared/cert.pem` 產生後，就立刻複製一份，叫做 `~/.cloudflared/cert-miniasp.com.pem`，這樣下次看檔名就知道是什麼域名了！
    

### 使用心得

我覺得雖然 Cloudflare Tunnel 很強大，功能也非常多，但是使用體驗真的不太好，心理負擔太多，特別是在設定和管理上，讓人感到困惑。

我目前為止，還是覺得 [ngrok](https://ngrok.com/) 最為簡單易用，使用上完全無負擔，一個命令就可以開通連線！

```bash
ngrok http 3000
```

或是

```bash
ngrok http http://localhost:8080
```

雖然 ngrok 的免費版有一些限制，但是它的使用體驗真的比 Cloudflare Tunnel 好太多了。

### 相關連結

-   [Cloudflare Tunnel · Cloudflare Zero Trust docs](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/)
