Docker 部署 wg-easy:WireGuard 網頁管理介面教學
透過 Docker Compose 快速架設 wg-easy。本文詳解 v15 版本後的設定變更、NET_ADMIN 權限需求、sysctl 參數配置以及如何透過 QR code 輕鬆完成手機端 VPN 連線設定。
您將建置的內容
wg-easy 是一個整合了網頁介面的 WireGuard,以單一 Docker 容器執行。它會為您管理 WireGuard 介面,並提供瀏覽器 UI 以建立用戶端。您建立的每個用戶端都會獲得設定檔與 QR code,因此手機只需透過相機掃描螢幕即可加入 VPN。
通道本身即為標準的 WireGuard。封包由核心模組處理,因此傳輸效能與手動設定相同。您獲得的好處是簡化了用戶端生命週期管理:無需透過 SSH 編輯設定檔,即可新增、停用與刪除節點。您失去的是對該設定檔的直接控制權,這部分內容請參考 手動在 VPS 上設定 WireGuard。
您需要一台具備公開 IPv4 位址的 KVM VPS、安裝 Docker Engine 與 Compose 外掛程式,並擁有 root 權限。共享主機核心的容器虛擬化技術(如 OpenVZ 或 LXC)通常無法載入 WireGuard 模組,這會導致容器無法啟動介面。
第 15 版將設定移出環境變數
您找到的大多數指南都是針對 wg-easy 14 編寫的,當時您需將 WG_HOST 設定為伺服器位址,並將 PASSWORD_HASH 設定為管理員密碼的 bcrypt雜湊值,兩者皆透過環境變數設定。第 15 版經過重新編寫。官方遷移說明明確指出,v15 不再使用與 v14 相同的環境變數,且大多數設定已移至網頁介面的管理面板中。
因此,WG_HOST 與 PASSWORD_HASH 已不再生效。若您複製舊的 compose 檔案,容器會啟動並忽略這些行,隨後要求您在瀏覽器中建立管理員帳號。這並非錯誤,而是新的設定流程。
截至 2026 年 10 月,應鎖定的主要標籤為 15。請鎖定主要版本而非使用 latest,因為主要版本升級會變更磁碟上的設定格式,且無法順利還原。遷移至 15 版後,已將 v14 的 JSON 檔案替換為 SQLite 資料庫。
Compose 檔案
為此堆疊建立一個目錄,並將官方的 compose 檔案寫入其中。這是上游提供的檔案,已移除註解掉的選用行。
sudo mkdir -p /etc/docker/containers/wg-easy
sudo curl -o /etc/docker/containers/wg-easy/docker-compose.yml \
https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.yml內容如下所示:
volumes:
etc_wireguard:
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
networks:
wg:
ipv4_address: 10.42.42.42
ipv6_address: fdcc:ad94:bacf:61a3::2a
volumes:
- etc_wireguard:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51820:51820/udp"
- "51821:51821/tcp"
restart: unless-stopped
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.all.forwarding=1
- net.ipv6.conf.default.forwarding=1
networks:
wg:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 10.42.42.0/24
- subnet: fdcc:ad94:bacf:61a3::/64etc_wireguard 是一個具名儲存卷(named volume),用於存放伺服器金鑰以及您建立的每個客戶端,並儲存在 wg-easy.db 的 SQLite 資料庫中。請務必備份該儲存卷,否則重建時會遺失所有節點資訊。若您偏好將這些檔案存放在主機檔案系統中,可將其替換為綁定掛載(bind mount);在執行此操作前,請先閱讀 綁定掛載與具名儲存卷的差異,因為兩者的權限行為有所不同。
為何需要 NET_ADMIN、SYS_MODULE 與 sysctl 設定
預設情況下,容器不允許存取網路堆疊,而上述每一行設定皆是為了移除特定的限制。
NET_ADMIN 允許容器建立 wg0 介面、指派位址並寫入路由。若缺少此權限,容器啟動後會在嘗試啟用介面時終止,因為 ip link add wg0 type wireguard 會回傳 Operation not permitted。
SYS_MODULE 加上唯讀的 /lib/modules 掛載,讓容器能在宿主機尚未載入 WireGuard 核心模組時自行載入。該模組位於宿主機核心而非映像檔內,因此必須掛載宿主機目錄。在現代核心中,該模組通常已內建,您可透過宿主機上的 sudo modprobe wireguard && echo ok 進行確認。
net.ipv4.ip_forward=1 會讓核心轉發非指向該機台本身的封包。若缺少此設定,客戶端雖能連線並完成握手,但所有發往網際網路的封包都會被丟棄,導致 ping 1.1.1.1 超時,儘管 VPN 看起來處於連線狀態。
net.ipv4.conf.all.src_valid_mark=1 是最容易讓人困惑的設定。WireGuard 會標記其輸出的封包,以避免它們被路由回隧道中。嚴格的反向路徑過濾(reverse path filtering)會偵測到來源位址與預期路由不符的封包並將其丟棄。此 sysctl 設定告知核心接受這些已標記的封包,這是維持完整隧道運作且不自我中斷的關鍵。
啟動服務並建立管理員帳號
cd /etc/docker/containers/wg-easy
sudo docker compose up -d
sudo docker compose logs -f請使用 docker compose up 與 docker compose down,不要使用 start 與 stop。start 僅會重新啟動現有的容器,因此您對 compose 檔案所做的任何變更(包含網路與 sysctl 設定)都不會生效,而 up 則會重新建立已變更的容器。若您希望服務在重新開機後自動啟動,restart: unless-stopped 已包含此功能,而 compose 服務的開機行為 說明了該策略的適用範圍與限制。
網頁介面監聽 TCP 51821 埠。首次存取時會顯示設定頁面,您需在此建立管理員帳號,並確認用戶端連線至伺服器時所使用的宿主位址。該位址會寫入每個用戶端設定檔的 Endpoint 行,因此必須填入 VPS 的公開 IP 或 DNS 名稱。若設定錯誤,手機掃描 QR code 後將無法連線,導致握手失敗。
關於該連接埠還有一點:除非設定 INSECURE=true,否則 wg-easy 15 會拒絕純 HTTP 連線。透過不受信任的憑證以 HTTPS 存取,或是在前端透過反向代理進行 TLS termination 皆可正常運作。若使用預設設定透過 http:// 存取則無法連線。
請勿將 UI 連接埠暴露至網際網路
此 compose 檔案將 51821 埠發布至所有介面。這是一個能路由您網路流量的設備登入頁面,不應對外開放。在 Docker 中發布連接埠會將規則寫入 DOCKER 鏈,該鏈的評估優先於 ufw,因此 ufw 的拒絕規則無法將其關閉。此陷阱值得深入了解,為何 Docker 發布的連接埠會忽略 ufw 一文對此有完整說明。
最簡單的修正方式是將 UI 綁定至 loopback 介面,並透過 SSH tunnel 進行存取:
ports:
- "51820:51820/udp"
- "127.0.0.1:51821:51821/tcp"
environment:
- INSECURE=true接著在您的筆記型電腦上執行:
ssh -L 51821:127.0.0.1:51821 youruser@your.server.address在筆記型電腦的瀏覽器中開啟 http://127.0.0.1:51821。流量會經由 SSH 加密,該連接埠不會回應其他來源,且 INSECURE=true 在此處是安全的,因為明文 HTTP 的傳輸路徑從未離開過 loopback 介面。
開啟 UDP 51820 埠並檢查兩層防火牆
WireGuard 本身需要網際網路能存取 UDP 51820 埠。雖然 Docker 會發布該埠,但許多雲端服務商會在 VPS 前端設置獨立的網路防火牆,而 Docker 無法感知其存在。請務必在兩處皆開啟該埠。若您使用 ufw 管理主機防火牆,參考 VPS 的基礎 ufw 規則 會比手寫 nftables 更為簡便。
請確認容器是否確實正在監聽該埠:
sudo ss -ulnp | grep 51820您應能看到一個監聽中的 UDP socket。若該行無任何顯示,代表容器未能成功啟動介面,此時 sudo docker compose logs wg-easy 會顯示失敗原因。
建立用戶端並使用手機掃描
在 UI 中建立一個用戶端,並為其命名以便日後辨識,例如使用該裝置的名稱。wg-easy 會自動分配下一個可用的通道位址並產生金鑰對。每個用戶端列皆提供 QR code 以及可供下載的 .conf 檔案。
在手機上安裝官方的 WireGuard 應用程式,選擇透過 QR code 新增通道,並將相機對準螢幕上的條碼。通道會以您輸入的名稱顯示。將其開啟後,UI 中的用戶端列便會開始顯示傳輸計數器與最近的交握時間。一旦手機連上通道,即可存取未公開至網際網路的服務;這正是手機如何從任何地方將檔案上傳至 自架相片伺服器 的方式,且該伺服器無需對外開放任何連接埠。同樣的技巧也適用於媒體服務,例如將 Jellyfin 媒體庫重建成 90 年代影音出租店,讓您在飯店房間內瀏覽時,依然能保有與區域網路內相同的隱私性。警報則可透過同一通道反向運作,例如 自架的 ntfy 伺服器 可在備份工作失敗時立即推送訊息至手機,且過程中無需回應來自公網的任何請求。
若用戶端啟用後未顯示交握,代表其無法連線至伺服器。請檢查 UDP 51820 埠,確認問題是否出在供應商防火牆或設定檔中的端點位址。若用戶端顯示已交握但無法連上網際網路,則問題通常在於轉發設定或 DNS。
在桌機上,請下載 .conf 檔案並匯入 WireGuard 用戶端,切勿手動輸入。該檔案中的私鑰僅會產生並顯示一次。請務必以對待 SSH 私鑰的方式妥善保管此檔案。
何時該捨棄 UI
當您的對象僅限於個人與手機時,wg-easy 是合適的工具。UI 的操作速度優於編輯設定檔,且撤銷遺失的手機存取權僅需點擊一次。
當您需要 UI 未提供的功能時,就會觸及它的極限。站對站(Site-to-site)路由通常是第一個門檻,此時對端的 AllowedIPs 需涵蓋整個遠端子網,而非單一 IP 位址。接下來則是針對個別對端的分割通道(Split tunnels)路由規則,或是由您的配置工具所產生的設定。此時,手動設定並不會更困難,只是方式不同;WireGuard 原生指南 展示了如何從 wg0.conf 建立相同的通道。如果您希望完全停止維運控制平面,WireGuard 與 Tailscale 的比較 涵蓋了託管方案。這是否為公平的交易,取決於協調伺服器實際能存取的範圍,在將網路交給它之前,建議先閱讀 Tailscale 的信任模型。成本通常是下一個考量點,Tailscale 免費方案的實際涵蓋範圍 對於家庭或小型團隊來說已綽綽有餘。超過此規模後,計費方式將改為按使用者而非裝置計算,這與您現有的 VPS 固定費用結構不同,因此在遷移團隊前,請務必確認 Tailscale 超出免費方案後的費用。您剛建立的全通道(Full tunnel)在此也有直接的對應方案,因為 將 VPS 宣告為 Tailscale 出口節點 可讓您透過伺服器取得相同的路由,且是在管理控制台中核准,而非寫入每個客戶端的設定檔中。子網限制也有對應解法,從 VPS 宣告整個私有網路 可將該網路提供給 tailnet 中的所有裝置,無須再進行導致您放棄 UI 的個別對端 AllowedIPs 編輯。如果您想要儀表板與自動網狀路由,但不想使用他人的協調伺服器,在 VPS 上執行您自己的 NetBird 伺服器 可將控制平面保留在您擁有的硬體上,代價則是需要處理 wg-easy 不曾要求您的 DNS 與 TLS 設定。
如果上述的 compose 語法對您而言比 WireGuard 本身更陌生,VPS 上的 Docker Compose 基礎 解釋了檔案格式與日常指令。
FAQ
為什麼 wg-easy 忽略了我的 WG_HOST 和 PASSWORD_HASH?
這些變數屬於 wg-easy 14 版本。第 15 版本經過重寫,上游開發者將幾乎所有設定移至網頁介面的管理面板中。容器不會讀取這些變數,因此它會正常啟動,並在您首次存取時要求建立管理員帳號。請改在該設定頁面中指定客戶端連線用的主機位址。
如果我的核心已經有 WireGuard,還需要 SYS_MODULE 嗎?
不需要。SYS_MODULE 和 /lib/modules 掛載的存在是為了讓容器在宿主機未載入模組時能自行載入。在 sudo modprobe wireguard 已經成功的宿主機上,此權限不會被使用。移除它是合理的加固步驟,且無論如何 NET_ADMIN 仍然是必要的。
客戶端已連線但無法存取網際網路,是哪裡出錯了?
若已完成握手但沒有流量,通常是轉發(forwarding)問題。請確認 net.ipv4.ip_forward=1 和 net.ipv4.conf.all.src_valid_mark=1 是否仍存在於 compose 檔案中,手動編輯的副本經常會遺失這些設定。如果轉發已開啟,請檢查客戶端收到的 DNS 伺服器。若通道將所有流量導向 VPN,但指向一個無法觸及的 DNS 伺服器,在瀏覽器中看起來就會像連線中斷。
如何備份我的客戶端設定?
所有資料都存放在 etc_wireguard 命名卷中。在第 15 版本中,這意味著容器內的 /etc/wireguard/wg-easy.db 處有一個 SQLite 資料庫。v15 的文件並未描述備份或匯出功能,因此請直接備份該卷本身:執行 docker compose down,將卷的內容複製到伺服器以外的地方,然後使用 docker compose up -d 重新啟動堆疊。請在任何升級前執行此操作。舊指南提到的 wg0.json 檔案屬於 wg-easy 14。那是 v14 備份按鈕匯出的檔案,在 v15 中,它唯一的用途是在遷移舊安裝時,於設定精靈中上傳該檔案。
我可以將 wg-easy 放在反向代理後方嗎?
可以。將代理伺服器置於 TCP 51821 前方,並在該處終止 TLS,同時在容器上設定 INSECURE=true,使其接受來自代理的純 HTTP 請求。請保持 UDP 51820 直接對外開放,因為 VPN 流量是 UDP,無法通過 HTTP 代理。