Docker 部署 wg-easy:WireGuard 網頁管理介面教學
透過 Docker Compose 快速架設 wg-easy。本文詳解 v15 版本後的設定變更、NET_ADMIN 權限需求、sysctl 參數配置以及如何透過 QR code 輕鬆完成手機端 VPN 連線設定。
你要建置的內容
wg-easy 是搭配 Web 介面的 WireGuard,會以單一 Docker 容器執行。它會代為管理 WireGuard 介面,並提供瀏覽器介面來建立用戶端。每個建立的用戶端都會取得設定檔與 QR code,因此手機只要將相機對準螢幕即可加入 VPN。
通道本身是標準的 WireGuard。封包由 kernel module 傳送,因此傳輸效能與手動撰寫的設定相同。你取得的是用戶端生命週期管理功能:不用透過 SSH 編輯設定檔,就能新增、停用及刪除 peer。你放棄的是直接控制該設定檔的能力;相關內容請參閱 VPS 上的手動 WireGuard 設定。
你需要一台可自行管理的 Linux 主機,例如 KVM VPS;該主機必須具備 public IP address,或有指向該主機的 domain name、x86_64 或 arm64 CPU、附有 Compose plugin 的 Docker Engine,以及 root 存取權。使用 host kernel 的容器虛擬化環境,例如 OpenVZ 或 LXC,通常無法載入 WireGuard module,導致容器無法啟用介面。
第 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 則會重新建立有變更的項目。若要在重新開機後恢復整個 stack,restart: unless-stopped 已涵蓋此需求;compose 服務的開機行為 會說明該原則的作用與限制。
Web UI 會監聽 TCP 51821。首次造訪時,頁面會要求你建立管理員帳戶,並確認用戶端用來連線至伺服器的主機位址。該主機位址會寫入每個用戶端設定檔的 Endpoint 行,因此必須填入 VPS 的公開 IP 或 DNS 名稱。若填寫錯誤,提供給手機的 QR code 會指向無法連線的位置,握手也永遠無法完成。
關於這個連接埠,還有一點需要注意:wg-easy 不會自行提供 TLS,也不會拒絕純 HTTP。透過 http:// 存取時,UI 仍會載入並顯示警告橫幅。預設設定的差異在於工作階段 cookie 會帶有 Secure 旗標,因此瀏覽器若透過公開位址上的純 HTTP 存取 UI,會捨棄該 cookie,導致登入狀態無法保留。INSECURE=true 只會移除這個旗標,不會進行其他變更。在前方放置負責 TLS termination 的反向代理時,即使使用自簽憑證,也完全不需要修改 wg-easy。
不要將 UI 連接埠公開至網際網路
compose 檔案會在所有介面上公開 51821。這是可路由你網路流量之主機的登入頁面,不應對全世界開放。在 Docker 中公開連接埠會將規則寫入 DOCKER chain;該 chain 的套用順序早於 ufw,因此 ufw deny 規則無法關閉此連接埠。這個陷阱值得單獨理解,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 只會從工作階段 cookie 移除 Secure 旗標,讓瀏覽器在這個純 HTTP 位址上保留登入狀態;由於純 HTTP hop 從未離開 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 私鑰的方式妥善保管此檔案。
何時應超越 Web 介面
當 peer 是人員使用的手機時,wg-easy 是合適的工具。Web 介面比編輯設定檔更快,而撤銷遺失的手機只需按一下。
當你需要的功能無法由 Web 介面表示時,就會遇到它的限制。站對站路由通常是第一個瓶頸;在這種情況下,peer 的 AllowedIPs涵蓋整個遠端子網路,而不是單一位址。具有每個 peer 路由規則的分割通道,或由佈建工具產生的設定,則是下一個瓶頸。到了這個階段,手動撰寫設定並沒有比較困難,只是方式不同;純 WireGuard 指南會示範如何以 wg0.conf建立相同的通道。如果你想完全停止執行控制平面,WireGuard 與 Tailscale 的比較說明了託管式方案。這是否值得取捨,取決於協調伺服器實際能連線到哪些資源;在將網路交給它之前,建議閱讀Tailscale 的信任模型。費用通常是下一個問題;Tailscale 免費方案實際涵蓋的內容是最多 6 位使用者免費,且僅限非商業用途。因此,家庭使用不需付費,但小型企業團隊即使人數很少也不符合資格。超過這個範圍後,計費依使用者而非裝置計算,與你已經付費使用的 VPS 具有不同的計費方式。因此,在將團隊遷移之前,應先確認超過免費方案後 Tailscale 的費用。你剛才建置的完整通道在該方案中也有直接對應的功能:將 VPS 宣告為 Tailscale exit node即可讓流量透過伺服器對外傳送,並改在管理主控台核准,而不是寫入每個用戶端設定檔。子網路限制也有對應功能,因為從 VPS 宣告整個私有網路會將該網路提供給 tailnet 中的每台裝置,不需要逐一編輯 peer 的 AllowedIPs,也就不必因此離開 Web 介面。如果你想要該儀表板與自動 mesh routing,但不想使用他人的協調伺服器,在 VPS 上執行自己的 NetBird server可讓控制平面保留在自有硬體上,但你必須自行處理 wg-easy 不要求的 DNS 與 TLS 設定。
如果上方的 Compose 語法才是你不熟悉的部分,而不是 WireGuard,VPS 上的 Docker Compose 基礎會說明檔案格式與日常使用的指令。
FAQ
為什麼 wg-easy 會忽略我的 WG_HOST 和 PASSWORD_HASH?
這些變數屬於 wg-easy 14。版本 15 是重新撰寫的版本,上游已將幾乎所有設定移至 Web UI 的管理面板。容器不會讀取這兩個變數,因此會正常啟動,接著在首次造訪時要求你建立管理員帳戶。請改在該設定頁面設定用戶端使用的主機位址。
如果我的 kernel 已經有 WireGuard,還需要 SYS_MODULE 嗎?
不需要。SYS_MODULE 和 /lib/modules mount 的用途,是在主機尚未具備該 module 時讓容器載入它。在 sudo modprobe wireguard 已經成功的主機上,這項 capability 不會使用。移除它是合理的 hardening 步驟,而無論是否移除,仍然需要 NET_ADMIN。
用戶端可以連線,但無法存取網際網路。問題在哪裡?
有握手但沒有流量,幾乎總是代表 forwarding 有問題。確認 compose 檔案中仍有 net.ipv4.ip_forward=1 和 net.ipv4.conf.all.src_valid_mark=1,因為手動編輯的副本經常會遺漏這些設定。如果 forwarding 已啟用,請檢查用戶端取得的 DNS server。若 tunnel 將所有流量送入 VPN,卻指向用戶端已無法到達的 DNS server,在瀏覽器中看起來會完全像是連線失效。
如何備份用戶端?
所有資料都位於 etc_wireguard named volume。在版本 15 中,這代表容器內 /etc/wireguard/wg-easy.db 的 SQLite database。v15 文件沒有說明備份或匯出功能,因此請直接備份該 volume:執行 docker compose down,將 volume 的內容複製到伺服器以外的位置,然後使用 docker compose up -d 重新啟動 stack。任何升級前都應執行此操作。較舊指南提到的 wg0.json 檔案屬於 wg-easy 14。該檔案是 v14 備份按鈕匯出的內容;在 v15 中,它唯一的用途是在遷移舊安裝時,於設定精靈中上傳該檔案。
可以在反向代理後方執行 wg-easy 嗎?
可以。將代理放在 Web UI 連接埠前方,並在代理處理 TLS termination,同時不要設定 INSECURE。容器會直接接受來自代理的純 HTTP hop,而官方的 Caddy 和 Traefik 範例完全不設定 INSECURE 變數。由於瀏覽器會透過 HTTPS 與代理通訊,你需要讓工作階段 cookie 保留 Secure 旗標;INSECURE=true 唯一會移除的就是這個旗標。請直接公開 UDP 51820,因為 VPN 流量使用 UDP,不會經過 HTTP proxy。