如何在 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 ... 指令確實可運作:除非 sudoers 設定了 umask_override(預設為關閉),否則 sudo 會將你的 umask 與其預設值 0022 合併,而且不會降低權限,因此建立的檔案 tee 權限會是 600。會造成金鑰外洩的情況,是在一般 022 umask 下執行沒有 umask 077 的管線,或是在 sudoers 啟用 umask_override 的主機上執行。明確設定權限後,就不必在意這些情況。
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。對等端可以存取伺服器本身的哪些內容,不是這條鏈的職責:目的地是該主機的封包會通過 input hook,不會通過 forward,因此由 input chain 決定。如目前設定所示,對等端只能連線至 SSH,其他服務一律不能連線,甚至無法 ping 10.8.0.1。請在該處加入 iifname "wg0" icmp type echo-request accept,並為每個只允許透過通道存取的服務加入一條 iifname "wg0" 規則。當這台主機同時是使用 tmux 執行 Claude Code 的遠端開發主機時,這項設定尤其重要,因為你可能不希望將該服務公開在網際網路上。
在 ufw 主機上:執行 ufw allow 51820/udp,在 /etc/default/ufw 中加入 DEFAULT_FORWARD_POLICY="ACCEPT",並在 /etc/ufw/before.rules 頂端加入一條 POSTROUTING MASQUERADE 規則 *nat。
在 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,表示出口介面名稱錯誤;計數器持續增加但沒有回覆,則表示轉送鏈的政策有問題。
可以連上網際網路,但無法解析名稱。 ping 1.1.1.1 成功,而 curl https://example.com 回傳 Could not resolve host。可能是缺少 DNS 行,或該行指定的解析器無法從隧道內連線。
部分 HTTPS 網站會停滯。 SSH 與 ping 都正常,但大型頁面載入停滯。這是路徑 MTU 問題:隧道會增加額外負荷,而中間某段連線可能會丟棄過大的封包,且 ICMP 訊息無法傳回。請在用戶端 [Interface] 設定 MTU。在一般 1500 位元組的連線上,wg-quick 已會選擇 1420(1500 減去 80 位元組的額外負荷),因此寫入 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,mode 600,並存放在伺服器外部),這樣就能在幾分鐘內於全新的 VPS 上重建。若遺失伺服器的私密金鑰,就必須重新發佈所有用戶端設定,因為用戶端會固定使用伺服器的公開金鑰。升級通常只需執行一般的 apt upgrade;若更新 kernel,則再重新開機即可。只要啟用 wg-quick@wg0,它就會自行恢復。
每個 peer 的狀態資訊都很少,而且加密作業在 kernel 中執行,因此限制主要來自 VPS 的 CPU 與頻寬額度,而不是這份設定中的任何項目。請在 tunnel 兩端使用 iperf3 測量,不要直接採信公開數據。大規模運作時,真正增加負荷的是維運。每個 peer 都需要唯一的 tunnel IP。手動編輯 60 個 [Peer] 區塊,很容易產生重複的 AllowedIPs;請改用 script 產生設定。單一伺服器代表單一 UDP endpoint,也是單一故障點,而 WireGuard 沒有 clustering 功能。若要實現備援,就需要另一台使用不同金鑰的伺服器。金鑰輪替仍須手動處理,因此請記錄每把金鑰的持有人,以及撤銷金鑰的方法。當這些管理工作超出單一文字檔案所能處理的範圍時,通常會在相同的 kernel data plane 之上加入 control plane,而 自架的 NetBird 伺服器會處理位址配置、peer 分發及原本需要手動處理的 setup keys。若自行執行這個 control plane 又多出一台伺服器,Tailscale 可以代為託管;其免費的 Personal plan 支援 6 位使用者、無限數量的使用者裝置,起始時還可支援最多 50 個已標記的資源,例如伺服器,因此多數個人伺服器環境不必為此付費。超過這個數量後,費用計算對象會從機器轉為使用者。因此,家庭或小型團隊實際需要支付的費用取決於擁有登入帳號的人數,而不是原本需要手動編輯到 wg0.conf 中的 peer 數量。在這種架構下,split-tunnel 的 AllowedIPs 管理會改為由 subnet router 宣告私有網段:只需從一台 VPS 宣告一次,並由中央核准,不必將設定逐一貼入每個用戶端檔案。是否值得採用這種取捨,取決於託管的 control plane 實際能夠存取哪些內容;而它不會持有用來加密網路流量的金鑰,但會決定哪些 peer 能得知彼此的資訊。
所有這些都需要一台由你控制的 Linux 主機、一個公開 IP、一個可載入 module 的 kernel,以及一套由你從頭到尾管理的 firewall。
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。