如何在 VPS 自建 WireGuard VPN
在 Linux VPS 部署 WireGuard,涵蓋金鑰、wg0.conf、IP 轉送、NAT、AllowedIPs 與 DNS,並解決握手失敗及容器顯示的錯誤訊息。
您要建置的內容
您在自有伺服器上建置的 WireGuard VPN,大約只需要 40 行設定:一組金鑰對、一個介面檔、一項 sysctl 設定、一條 NAT 規則,以及一個防火牆放行規則。安裝非常簡單,因此本指南大多著重於容易出問題的部分、金鑰權限、AllowedIPs、轉送與 DNS。
WireGuard 是在核心中運作的第 3 層通道,自 Linux 5.6 起已納入主線核心,因此 Ubuntu 24.04 與 Debian 13 內建 WireGuard,不需要外部模組。它不會協商密碼套件,不需要憑證授權中心,也沒有使用者名稱或密碼驗證步驟:對等端由一組公開金鑰,以及該金鑰可使用的 IP 位址組成。封包的 MAC 驗證失敗時會直接丟棄,不會回覆,因此該連接埠不會回應掃描。反之,WireGuard 沒有驗證伺服器,因此若要移除存取權,必須在伺服器上刪除對等端。
先檢查虛擬化類型
WireGuard 需要能載入模組的 kernel;在 KVM VPS 上通常可直接運作。若使用與主機共用 kernel 的容器虛擬化環境,例如 OpenVZ 或 LXC,第一個指令會失敗並顯示 RTNETLINK answers: Operation not supported。此時應改用 wireguard-go userspace 實作。先使用 sudo modprobe wireguard && echo ok 檢查。
產生金鑰,避免外洩
任何人都能讀取的 /etc/wireguard/server.key,等同於完全沒有 VPN。常見的 umask 077 && wg genkey | sudo tee ... 指令並不可靠,因為 sudo 會將自己的 umask 套用到它建立的 tee 檔案。請明確設定檔案模式。
sudo apt update && sudo apt install -y wireguard nftables
sudo install -d -m 700 /etc/wireguard
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/server.key'
sudo sh -c 'wg pubkey < /etc/wireguard/server.key > /etc/wireguard/server.pub'
sudo chmod 600 /etc/wireguard/server.key以相同方式產生用戶端金鑰組。wg genpsk 會額外加入可選的預共享金鑰;請在每個設定檔中各放置一行。
伺服器介面:/etc/wireguard/wg0.conf
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <contents of /etc/wireguard/server.key>
[Peer]
PublicKey = <laptop public key>
PresharedKey = <psk, optional>
AllowedIPs = 10.8.0.2/32chmod 600;啟動時顯示檔案可由所有使用者存取的警告,表示你略過了這項設定。Address 是伺服器在通道內的位址,並包含整個 VPN 子網路的遮罩。請選擇不會在實際網路中遇到的範圍;192.168.1.0/24 會與用戶端所在環境中的許多家用路由器衝突,導致通道無聲地輸給本機路由。
在伺服器端,對等端的 AllowedIPs 是 /32,也就是該用戶端擁有的唯一通道位址。若為兩個對等端指定相同的允許 IP,該位址會移交給最後設定的對等端;第一個對等端會停止接收流量,且任何地方都不會顯示錯誤。請讓 SaveConfig 保持未設定,否則 wg-quick down 會根據即時狀態重寫此檔案。
將主機設為路由器
Linux 伺服器會丟棄目的地不是本機的封包。預設也不會啟用轉送與來源 NAT。
printf 'net.ipv4.ip_forward = 1\nnet.ipv6.conf.all.forwarding = 1\n' \
| sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
sysctl net.ipv4.ip_forward單獨執行 sysctl -w 只會運作到下次重新開機,之後便會無聲停止。NAT 需要使用 egress 介面,也就是連到網際網路的 NIC,而不是 wg0。請勿假設介面名稱是 eth0;應從 ip route show default 取得實際名稱,因為目前的映像檔會使用 enp1s0 或 ens3 這類名稱。
防火牆:連接埠與轉送路徑
單一 nftables 檔案即可同時處理篩選與 NAT。執行 /etc/nftables.conf 時會清除現有規則集,因此已由 ufw 或 Docker 管理的主機不要執行此操作。
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
iif lo accept
tcp dport 22 accept
udp dport 51820 accept
}
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
iifname "wg0" oifname "enp1s0" accept
}
}
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr 10.8.0.0/24 oifname "enp1s0" masquerade
}
}使用 sudo systemctl enable --now nftables 套用設定,並保持第二個 SSH 工作階段開啟:policy drop 加上 SSH 規則中的拼字錯誤,就會將你鎖在自己的伺服器之外。請注意轉送鏈未允許的內容:wg0 到 wg0。對等端可以連上網際網路,但無法互相連線;若要使用點對點 VPN,請加入 iifname "wg0" oifname "wg0" accept。同一條鏈也會控制對等端可存取伺服器本身的哪些內容。當這台主機同時是在 tmux 中執行 Claude Code 的遠端開發主機時,這點尤其重要,因為你可能不希望將該環境公開暴露。
在使用 ufw 的主機上:ufw allow 51820/udp,在 /etc/default/ufw 中執行 DEFAULT_FORWARD_POLICY="ACCEPT",並在 /etc/ufw/before.rules 頂端加入 *nat POSTROUTING MASQUERADE 規則。
在 systemd 下啟動
sudo systemctl enable --now wg-quick@wg0
sudo wg showwg-quick 會建立介面、設定位址,並安裝從 AllowedIPs 取得的路由。enable --now 是關鍵部分:手動執行的 wg-quick up wg0 會在下次重新開機後失效,而核心升級也需要重新開機。如果服務在其中一次重新開機後未能恢復,除非有人嘗試連線,否則不會發出任何訊息。因此,在 wg-quick@wg0 上設定 OnFailure= drop-in,並指向自己的 ntfy 伺服器,是以最低成本在手機上得知問題的方法,而不必等到被鎖在系統外的使用者回報。
用戶端設定,以及大家最容易設定錯誤的項目
[Interface]
PrivateKey = <laptop private key>
Address = 10.8.0.2/32
DNS = 10.8.0.1
[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25AllowedIPs 同時執行兩種不同工作,將兩者混為一談,是大多數 WireGuard 疑問的來源。
對外傳送時,它是路由表。 封包目的地符合某個 peer 的 AllowedIPs 時,會加密並傳送給該 peer。0.0.0.0/0, ::/0 會將所有流量導入通道,形成 full tunnel,並以伺服器作為預設路由。split tunnel 則是較窄的清單:AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 傳送 VPN 流量,以及伺服器後方的一個私有網路;其他流量則維持本機路由。這份較窄的清單可讓服務完全不暴露在公用網際網路上,例如繫結至通道位址的 VPS 上的私有 Nextcloud 執行個體,或在同一台主機上執行的 巢狀虛擬化實驗室 VM,都能讓 peer 存取,同時對其他人保持不可見。
對內接收時,它是存取控制清單。 來自某個 peer 的解密封包,如果來源位址不在該 peer 的 AllowedIPs 中,就會被丟棄。因此,伺服器會為筆記型電腦列出 10.8.0.2/32:若在其中加入 0.0.0.0/0,該用戶端就能冒用通道中的任何位址。
PersistentKeepalive 用於位於 NAT 後方的用戶端。這類路由器只會在封包傳輸期間維持 UDP 對應。對應逾時後,伺服器便無法再連線至用戶端。PersistentKeepalive = 25 會維持該對應,應設定在用戶端,而不是設定在具有公用 IP 的伺服器上。
DNS,以及無人察覺的洩漏
使用 AllowedIPs = 0.0.0.0/0 且沒有 DNS = 行時,客戶端會繼續使用從本機網路取得的 DNS resolver,也就是咖啡店路由器上的 192.168.1.1。這條路由比預設路由更具體,因此 DNS 查詢會以明文經由本機連線送出,其他流量則會通過 tunnel。流量內容受到保護,但查詢的名稱不受保護。
有兩個可行的選項。將 DNS 指向公開 resolver(DNS = 9.9.9.9),查詢就會通過 tunnel,並從伺服器送出,但該 resolver 仍可查看查詢內容。或者執行繫結至 10.8.0.1 的 unbound 或 dnsmasq,設定 DNS = 10.8.0.1,並將 udp dport 53 iifname "wg0" accept 加入 input chain;設定該行後即可不再處理 resolver,但所有名稱解析也會完全失效。
在 Linux 客戶端上,wg-quick 會透過 resolvconf 套用 DNS;若缺少該設定,就會得到 resolvconf: command not found。安裝 openresolv,或在使用 systemd-resolved 的客戶端上設定 PostUp = resolvectl dns %i 10.8.0.1。
不停止通道的情況下新增及移除對等端
重新啟動介面來新增使用者,會中斷所有目前的連線。將 [Peer] 區塊附加至 wg0.conf,然後就地重新載入對等端集合。
sudo bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'wg-quick strip 會輸出不含 wg-quick 專用金鑰的設定(Address、DNS、PostUp),而 syncconf 會在連線工作階段持續運作的情況下套用差異。它只會更新對等端;若變更 Address,仍須完整執行 down/up。使用 sudo wg set wg0 peer <public key> remove 撤銷,然後從檔案中刪除該區塊,否則下次重新載入時會再次加入。
會看到的錯誤模式與訊息
交握始終無法完成。 wg show 會列出對端,但沒有 latest handshake,而且用戶端日誌會顯示:
Handshake for peer 1 (10.0.0.10:51820) did not complete after 5 seconds, retrying (try 2)沒有封包抵達,或沒有封包通過接受處理。請依序確認:VPS 防火牆和供應商網路防火牆是否都開放 UDP 51820;多數控制面板會分別管理這兩項設定。確認 Endpoint 位址與連接埠是否正確,再確認金鑰是否填反。用戶端 [Peer] 區塊中的金鑰必須是伺服器的公開金鑰,反之亦然。貼上私密金鑰或用戶端自己的公開金鑰,都會產生完全相同的症狀。伺服器上的 sudo tcpdump -ni any udp port 51820 可確認是否有封包抵達。核心模組預設不會記錄訊息;只有啟用動態偵錯(echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control)後,WireGuard 訊息才會出現在 dmesg。啟用後,金鑰不符會顯示為無效 MAC 丟棄。
交握成功,但無法連上網際網路。 ping 10.8.0.1 成功,但 ping 1.1.1.1 逾時,表示缺少轉送或 NAT。確認 sysctl net.ipv4.ip_forward 的結果為 1,接著在用戶端 ping 時,使用 sudo nft list ruleset 或 sudo iptables -t nat -L POSTROUTING -n -v 監看計數器。偽裝規則的封包數為 0,表示出口介面名稱錯誤;計數器持續增加但沒有回覆,則表示問題可能出在 forward chain 的政策。
可以連上網際網路,但無法解析名稱。 ping 1.1.1.1 成功,而 curl https://example.com 傳回 Could not resolve host。這表示缺少 DNS 行,或該行指定的解析器無法從通道內連線。
部分 HTTPS 網站會卡住。 SSH 和 ping 都正常,但大型頁面無法完成載入。這是路徑 MTU 問題:通道會增加額外標頭,而中間某個鏈路可能丟棄過大的封包,且沒有 ICMP 訊息返回。降低用戶端 [Interface] 中的 MTU,嘗試 1420,接著嘗試 1380,最後嘗試 1280。如果降低 MTU 能解決卡住問題,但傳輸速率仍不理想,請不要繼續猜測整數值。改為依照 透過二分搜尋找出實際路徑 MTU 並限制 TCP MSS 的步驟處理,也能排除與通道無關的原因。
介面拒絕啟動。 Address already in use 表示另一個程序已占用 UDP 51820。Cannot find device wg0 若出現在失敗的 up 之後,通常表示設定遭到拒絕;請查看 journalctl -u wg-quick@wg0 -n 50。
從 Streisand 或 OpenVPN 遷移
Streisand 已停止維護,且其 repository 已封存。繼續執行建置在已棄用自動化工具上的 VPN,會形成緩慢累積的安全性問題。這不是就地升級,OpenVPN 的 PKI 也無法轉換。WireGuard 沒有憑證、CA 或到期日,因此每個用戶端都必須取得全新的金鑰組。
採用並行方式遷移。WireGuard 使用 UDP 51820,而 OpenVPN 使用 1194,兩者可在同一台主機上共存。先建置 wg0,再逐一遷移用戶端,最後停止舊服務。OpenVPN 的使用者名稱/密碼與撤銷模型無法沿用。如果需要帳戶或稽核軌跡,請在 WireGuard 之上另行加入。
大規模運作下的備份、升級與壓力來源
/etc/wireguard 就是伺服器本身。請備份它(sudo tar czf wg-backup.tgz -C /etc wireguard,權限設為 600,並存放在伺服器之外),即可在幾分鐘內於全新的 VPS 上重建。若遺失伺服器的私密金鑰,就必須重新簽發所有用戶端設定,因為用戶端會固定使用伺服器的公開金鑰。升級就是一般的 apt upgrade,若包含核心更新,再重新開機即可;只要啟用 wg-quick@wg0,它就會自行恢復。
每個對等端的狀態很小,且加密作業在核心中執行,因此效能上限取決於 VPS 的 CPU 與頻寬配額,而不是這份設定中的某項限制。請使用通道兩端的 iperf3 進行測量,不要直接相信已發布的數據。大規模運作時,真正造成壓力的是管理作業。每個對等端都需要唯一的通道 IP,而手動編輯 60 個 [Peer] 區塊,很容易讓重複的 AllowedIPs 混入其中:請使用 script 產生設定。單一伺服器只有一個 UDP endpoint,也只有一個故障點;WireGuard 沒有叢集功能,因此備援代表要使用另一台擁有獨立金鑰的伺服器。金鑰輪替仍須手動處理,因此請記錄每把金鑰由誰持有,以及如何撤銷個別金鑰。當這些管理工作超出純文字檔案可負擔的範圍時,通常會在相同的核心資料平面上加入控制平面,而 自架的 NetBird server 可處理原本需要手動執行的位址配置、對等端分發與 setup keys。如果自行執行該控制平面代表多管理一台伺服器,Tailscale 可以代為託管;其免費方案涵蓋 6 位使用者與不限數量的裝置,對多數個人裝置群組而言已經足夠,通常不必付費。超過這個範圍後,費用取決於人員而非機器,因此家庭或小型團隊實際需要支付的費用取決於擁有登入帳號的人數,而不是你原本需要手動編輯到 wg0.conf 中的對等端數量。在這一端,分割通道的 AllowedIPs 管理會改為由 subnet router 廣播私有網段:從一台 VPS 廣播一次,並在中央核准,不必貼到每個用戶端檔案中。是否值得採用這種取捨,取決於託管控制平面實際能夠觸及的範圍;而它不會持有用來加密網路流量的金鑰,但會決定哪些對等端能互相得知。
以上作業都需要一台由你控制的 Linux 伺服器、一個公開 IP、一個可載入模組的核心,以及一套由你全程管理的防火牆。
FAQ
WireGuard 的握手為何始終無法完成?
wg show 列出沒有 latest handshake 的對等點,表示封包未抵達,或未獲接受。請在 VPS 防火牆與供應商另外提供的網路防火牆上檢查 UDP 51820,確認 Endpoint 主機與連接埠,然後檢查金鑰是否配對錯誤;用戶端的 [Peer] 區塊必須放置伺服器的 public key。伺服器上的 sudo tcpdump -ni any udp port 51820 可顯示封包是否完全抵達;dmesg 只有在啟用動態偵錯(echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control)後,才會回報 WireGuard 的握手失敗。此時,金鑰不符會顯示為 invalid-MAC drop。
通道已連線,但無法連上網際網路。缺少什麼設定?
ping 10.8.0.1 正常運作,但 ping 1.1.1.1 逾時,表示問題可能出在轉送或 NAT。確認 sysctl net.ipv4.ip_forward 讀取 1,並確認已在 /etc/sysctl.d/ 中設定,而不只是使用重新開機後會失效的 sysctl -w。接著檢查 masquerade 規則是否從 ip route show default、enp1s0 或 ens3 取得實際的對外介面名稱,少數情況則是 eth0。
用戶端設定中需要 DNS = 行嗎?
使用完整通道且沒有 DNS = 行時,用戶端會繼續使用從本機網路取得的 resolver。這些查詢會透過本機連線以明文傳送,而其他流量則會經由通道傳送。請將 DNS 指向公開 resolver,或執行繫結至 10.8.0.1 的 unbound/dnsmasq,並在輸入鏈中開放 udp dport 53 iifname "wg0"。
AllowedIPs 實際控制什麼?
它有兩項功能。對外傳送時,它是路由表:符合對等點 AllowedIPs 的流量會加密後傳送至該對等點。對內接收時,它是存取控制清單:解密後來源不在該對等點 AllowedIPs 內的封包會遭到丟棄。因此,伺服器端會為每個用戶端列出一個 /32,而用戶端則可能列出 0.0.0.0/0。
WireGuard 能在任何 VPS 上執行嗎?
在 KVM VPS 上,可使用核心內建模組執行,無須額外設定。在共用主機核心的容器虛擬化環境,例如 OpenVZ 或 LXC,modprobe wireguard 會因 Operation not supported 而失敗;此時可改用 wireguard-go 使用者空間實作。請先執行 sudo modprobe wireguard && echo ok。