SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-26

WireGuard 速度慢?找出真正原因

WireGuard 速度慢通常是 MTU 問題。用二分法找出路徑 MTU、限制 TCP MSS、檢查 steal time,先測量路徑再判斷是否為 tunnel。

WireGuard 的低速實際源自何處

WireGuard 的低速通常可追溯至 4 種原因,而且這 4 種原因的可能性並不相同。第一種是 MTU(maximum transmission unit,最大傳輸單位):通道建立的封包對路徑上的某個連結而言過大,因此大量傳輸會停滯,但小型傳輸看起來正常。第二種是路徑本身;在建立通道前,路徑就已經是速度瓶頸。第三種是小型共用 VPS 的 CPU 資源,因為加密作業會與同一主機上的其他 guest 競爭。第四種是對等端本身的連線。

請依照這個順序檢查。MTU 應先檢查,因為清單中只有它是 WireGuard 本身引入的原因,而且其症狀看起來完全不像速度變慢。錯誤的 MTU 通常會呈現為:通道立即建立、能回應 ping、可以登入 SSH,接著在第一次複製檔案時凍結。

在進行上述檢查前,應先排除一種症狀。如果每個新網站都要等待數秒才開始載入,但開始後能以完整速度傳輸,問題是名稱解析,而不是吞吐量。WireGuard 上的 DNS 有自己的故障模式,修改 MTU 無法修正這些問題。

為什麼 WireGuard 的 MTU 是 1420?

您送入 tunnel 的每個封包都會先加密,再包裝到新的封包中。這層包裝會占用位元組,而可用的 payload 會因此減少。

WireGuard 資料標頭為 32 bytes:包括 4 byte 的類型欄位、4 byte 的接收端索引、8 byte 的計數器,以及 16 byte 的 Poly1305 驗證標籤。外層還有 8 bytes 的 UDP 標頭,以及外層 IP 標頭;IPv4 為 20 bytes,IPv6 為 40 bytes。因此,當您的 Endpoint 是 IPv4 位址時,封裝總大小為 60 bytes;使用 IPv6 時則為 80 bytes。WireGuard protocol page 詳細說明了這些數值所依據的訊息格式。

wg-quick 不會自行猜測。它會讀取通往您的 Endpoint 之介面的 MTU,然後減去 80。在一般 1500 byte 的 Ethernet 路徑上,結果就是 1420,也就是 ip link show wg0 顯示的值。它減去 80 而不是 60,是為了讓同一數值在該 endpoint 日後透過 IPv6 連線時仍然安全,因為 IPv6 的外層標頭多 20 bytes。

ChartMTU arithmetic for common underlays
The data behind this chart
[
  {
    "label": "Ethernet, IPv4 endpoint",
    "path_mtu": 1500,
    "encap_overhead": 60,
    "usable_wg0_mtu": 1440
  },
  {
    "label": "Ethernet, IPv6 endpoint",
    "path_mtu": 1500,
    "encap_overhead": 80,
    "usable_wg0_mtu": 1420
  },
  {
    "label": "PPPoE DSL, IPv4 endpoint",
    "path_mtu": 1492,
    "encap_overhead": 60,
    "usable_wg0_mtu": 1432
  },
  {
    "label": "Extra tunnel in the path",
    "path_mtu": 1400,
    "encap_overhead": 80,
    "usable_wg0_mtu": 1320
  }
]

4 列是算術結果,不是測量值。在乾淨的 1500 byte 路徑上,使用 IPv4 endpoint 時,1440 可以容納封包,因此預設值 1420 會留下 20 bytes 未使用。這個餘裕是刻意保留的,不是問題所在。

最後一列才是造成問題的數值。當路徑上的某個 link 只能承載 1400 bytes,而 tunnel 仍設定為 1420 時,每個滿載 segment 都會產生 1500 byte 的外層封包,比該 link 可接受的大小多 100 bytes。能夠容納的值是 1320

也不要直接套用 1320。您的 path MTU 取決於實際路徑,而確認它的唯一方法就是進行測量。

錯誤的 MTU 會呈現什麼情況

這類故障不是逐漸惡化,而是小封包與大型封包之間出現明確分界。

  • ping 可在通道中以任何正常大小傳輸。
  • SSH 登入可以完成,輸入指令時也很即時。
  • curl -I https://example.com 會立即回傳標頭。
  • curl https://example.com 載入大型頁面時,會在收到最初幾 KB 後停滯。
  • scp 傳輸大型檔案時,會在某個百分比開始後停止。
  • SSH 工作階段會在執行大量輸出內容的指令時立即凍結。

這些情況都是因為 TCP 連線只有在需要傳送大量資料時,才會建立完整大小的區段。交握與第一個請求都能符合路徑上的所有 MTU。停滯會在第一個完整大小的區段出現,因此連線在失效前看起來都很正常。

外層封包大於下一段鏈路的 MTU 時,會有兩種結果。

封包遭到分割。 路由器會將封包分割,遠端再重新組合這些片段。傳輸可以運作,但速度較慢,因為原本一個封包現在需要傳送兩個封包,接收端也必須保留狀態,直到兩個片段都抵達。遺失任何一個片段,都會導致整個原始封包遺失,因此具有 1% 遺失率的路徑,實際表現會差得多。許多防火牆也會依政策丟棄 IP 片段,使結果轉為下一種情況。

封包遭到丟棄,且你可能不會收到通知。 不會進行分割的路由器會將 ICMP(internet control message protocol)「需要分割」訊息傳回傳送端,並附帶可接受的 MTU。若該訊息成功抵達,路徑 MTU 探索便能運作,傳送端會自行降低區段大小。許多網路會過濾 ICMP,因此該訊息經常無法抵達。沒有其他機制會回報這次遺失。這就是黑洞:封包離開後沒有任何回應,兩端的任何日誌都不會出現錯誤,而傳輸會一直停滯,直到某個項目逾時。

如何找出正確的 MTU?

測量路徑後再進行扣除。請測試底層網路,而不是 tunnel。因此,從 client ping server 的公開位址,並禁止分割封包。

ping -M do -s 1472 -c 3 203.0.113.10

-M do 會設定 DF(don't fragment)位元,因此路徑上的 router 都不得分割封包。-s 是 ICMP payload 大小。完整的 IPv4 封包大小為該 payload 加上 8 bytes 的 ICMP header,以及 20 bytes 的 IP header,因此 -s 1472 會在網路上產生剛好 1500 bytes 的封包。

有 3 種結果需要注意。若回應正常,表示 1500 bytes 可通過,問題不在 MTU。若出現本機錯誤,表示本機介面的大小已小於要求的值:

ping: local error: message too long, mtu=1500

若收到中途 router 的回應,該回應會直接提供答案,可以在此停止:

From 203.0.113.1 icmp_seq=1 Frag needed and DF set (mtu = 1492)

若 1472 的測試結果為 100% packet loss,但較小的大小可正常回應,這就是 black hole 情況。沒有 router 會告知問題所在,因此請透過反覆對半切分找出邊界。保留一個確定可用的大小,以及一個確定失敗的大小,測試兩者的中點,再依結果移動相應的邊界。每一輪都會將剩餘範圍縮小一半,因此 5 或 6 輪就足夠。

逐輪執行的二分搜尋範例

每一行都是在 client 上針對 server 公開位址執行的 command。註解記錄收到的結果。

ping -M do -s 1472 -c 3 203.0.113.10   # 100% loss, so 1500 is too big
ping -M do -s 1272 -c 3 203.0.113.10   # replies, so 1300 fits
ping -M do -s 1372 -c 3 203.0.113.10   # replies, so 1400 fits
ping -M do -s 1422 -c 3 203.0.113.10   # 100% loss, so 1450 is too big
ping -M do -s 1397 -c 3 203.0.113.10   # 100% loss, so 1425 is too big
ping -M do -s 1384 -c 3 203.0.113.10   # 100% loss, so 1412 is too big

成功通過的最大 payload 是 1372,因此這條路徑至少可承載 1400 bytes,且少於 1412 bytes。請採用安全端的值。路徑 MTU 為 1400 時,扣除 80 bytes 的封裝開銷後,wg0 的 MTU 為 1320。

tracepath 會自行執行相同的搜尋,在開始二分搜尋前值得先執行一次:

tracepath -n 203.0.113.10

最後一行會回報找到的結果:

     Resume: pmtu 1492 hops 12 back 12

請將這兩個工具視為起點,而不是證明。部分 host 會限制速率,或完全丟棄 ICMP,因此二分搜尋回報的 MTU 可能小於路徑實際可承載的大小。原本失敗的傳輸才是實際測試。

先即時套用該值,因為猜錯時只需執行一個 command 就能還原:

sudo ip link set mtu 1320 dev wg0

重新嘗試原本卡住的傳輸。若傳輸完成,請在 client 的 [Interface] 區塊中將該值設為永久設定:

[Interface]
PrivateKey = <client private key>
Address = 10.8.0.2/32
MTU = 1320
sudo wg-quick down wg0 && sudo wg-quick up wg0
ip link show wg0

此時 ip link show wg0 應顯示 mtu 1320。若仍顯示舊值,表示 wg-quick 沒有讀取你編輯的檔案。請確認你編輯的是 /etc/wireguard/wg0.conf,且 MTU 位於 [Interface] 底下,而不是位於 [Peer] 底下;後者會被忽略。

MTU 是單一介面的屬性,不會在 peer 之間進行協商。在 client 上設定 MTU,只會縮小 client 傳送的封包。server 仍會依其 wg0 MTU 建立封包,因此即使 upload 已恢復正常,download 仍可能進入 black hole。請在兩端設定相同的值,或在 server 上限制 MSS。

為什麼 MSS clamping 能修正 TCP,而無法處理其他流量

如果伺服器會為對等端轉送網路流量,例如使用 NAT(network address translation)的任何標準 WireGuard VPS 設定,只要加入一條規則,就能修正所有對等端的 TCP 問題。這也不必逐一追查每個無法控制的用戶端數值。

MSS(maximum segment size)是 TCP 選項。連線兩端會在 SYN 封包中填入此選項,說明各自願意接收的區段大小。MSS clamping 會在封包傳輸途中改寫此選項,使其符合實際路徑的 MTU。如此一來,兩端會在傳輸任何資料前先同意使用較小的區段。這項方法有效,是因為它發生在交握期間,而且不依賴可能遭路徑過濾的 ICMP 訊息。

使用 nftables 時,將下列 table 加入 /etc/nftables.conf,放在現有 table 下方:

table inet mangle {
  chain forward {
    type filter hook forward priority mangle; policy accept;
    tcp flags syn tcp option maxseg size set rt mtu
  }
}

使用 sudo systemctl reload nftables 重新載入。在使用 iptables 的主機上,相等的設定只有一行:

sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

確認規則位於封包實際經過的路徑上。讓用戶端建立新的連線時,執行 sudo nft list table inet manglesudo iptables -t mangle -L FORWARD -n -v,並觀察計數器是否增加。計數器一直維持 0,表示封包沒有經過該 hook,因此規則沒有作用。

接著說明實際限制。MSS clamping 只處理 TCP,不處理其他協定,而且只處理轉送流量。因此,在 WireGuard 伺服器本身執行的服務不會經過 forward hook,也不會套用 MSS clamping。這項設定也只會影響規則載入後建立的連線;現有工作階段會繼續使用原先協議的 MSS。

UDP 不受影響,因為 UDP 沒有可供改寫的交握程序。大多數 UDP 流量仍能正常運作。QUIC 是 HTTP/3 使用的傳輸協定,會自行探測可用的封包大小,並刻意從較小的大小開始傳送。無法正常運作的是傳送單一大型 datagram 且要求其完整通過的 UDP 流量,例如超過 1400 bytes 的 DNSSEC(DNS security extensions)回應。這些查詢會逾時,接著改用 TCP 重試;對使用者而言,結果是網站載入緩慢,而不是完全無法開啟。

VPS 的 CPU 是限制嗎?

WireGuard 使用 ChaCha20-Poly1305 進行加密,並使用 Curve25519 交換金鑰。資料路徑中完全沒有 AES,因此有一點常被誤解:CPU 中的 AES-NI 指令 對 WireGuard 沒有作用。主機標示支援 AES-NI,不代表能提供 WireGuard 效能功能。選用 ChaCha20 的原因,是它在純軟體環境中速度很快,即使 CPU 完全沒有加密加速功能也一樣。

這不代表 WireGuard 不會消耗資源。在 1 vCPU 的 VPS 上,單一核心除了處理應用程式工作,還要同時處理加密與網路中斷。

在傳輸執行期間進行測量:

sudo apt install -y sysstat
mpstat -P ALL 1

查看三個欄位。%soft 是 softirq 時間,也就是核心處理封包所使用的時間。%steal 是 hypervisor 將 CPU 時間交給其他工作的時間。%idle 是剩餘的 CPU 時間。

如果唯一核心的 %soft 接近 100,表示主機已達到封包處理上限。這是實際的限制,增加核心數可以提高上限。top 會顯示同一時間程序清單頂端的 ksoftirqd/0,這是從另一個角度得到的相同結果。

%steal 高於幾個百分點,表示這不是你能自行修正的限制,因為主機過度超額配置,而你的 vCPU 正在等待實體核心。這在最便宜的共用方案中很常見,而且數值會隨一天中的時段變化。來自繁忙鄰居的 Steal time 需要另外調查,調整 MTU 無法改善這個問題。

另一個因素是用戶端執行的實作。Linux kernel module 是快速路徑,能將單一 peer 的加密工作分散到多個核心。wireguard-go 是 userspace implementation,速度較慢;macOS 與 iOS 用戶端會使用它,因為這些平台不允許應用程式載入 kernel module。

是路徑受限,還是對端本身的連線受限?

在調整任何設定前,請在同一個用戶端於相隔幾分鐘的時間測量兩個數值:未使用通道時的吞吐量,以及使用通道時的吞吐量。沒有這組數值,判斷就只是猜測。

在伺服器上執行 iperf3 -s。直接測試需要讓公用位址可連線至 TCP 5201,因此請在測試期間開放該連接埠,完成後移除規則。請確認該連接埠已再次關閉,不要只是假設它已關閉。

# on the client, outside the tunnel
iperf3 -c 203.0.113.10
# then through the tunnel
iperf3 -c 10.8.0.1

如果兩個數值接近,表示 WireGuard 的額外成本很低,限制來自網路路徑。如果通道測得的數值遠低於直接連線,而 %soft 維持較低,請重新檢查 MTU。封包分段會降低吞吐量,但不會導致連線中斷,因此這裡會呈現為百分比損失,而不是卡住。

請測試兩個方向,因為家用連線通常是不對稱的。iperf3 -c 10.8.0.1 -R會反轉流量方向,讓伺服器傳送資料。使用 500/20 連線的用戶端,透過通道傳送資料的速度不可能超過 20 Mbit 的上傳速度;伺服器端的任何變更都無法改變這點。

接著使用平行串流測試:

iperf3 -c 10.8.0.1 -P 4

如果 4 個串流合計傳輸的資料量遠高於單一串流,表示單一 TCP 連線無法填滿這條路徑。單一串流的吞吐量受接收視窗除以往返時間的結果限制,因此 150 ms 的路徑需要較大的視窗才能傳輸大量資料。查看目前的限制:

sysctl net.ipv4.tcp_rmem net.ipv4.tcp_wmem

每組輸出中的第 3 個數值,是 Linux 可透過自動調整設定的上限。封包遺失也會嚴重限制單一串流,因為 TCP 擁塞控制會根據封包遺失作出反應,而長距離路徑的復原成本較高。從用戶端執行 mtr -rwc 100 203.0.113.10 100 個週期,確認路徑中從哪裡開始出現封包遺失。若封包遺失從某一跳開始,並持續到最後一跳,則屬於實際的封包遺失。若某個中間節點出現封包遺失,但後續節點不再出現,表示該路由器降低了 ICMP 的處理優先順序,這不代表實際的網路流量遺失。

若要取得可重複的伺服器本身效能資料,並與網路因素分開,請依照有文件記錄的方法對 VPS 執行基準測試。如此可在變更後再次執行相同測試,並比較一致的結果。

WireGuard 無法解決的問題

WireGuard 是通道。它的速度不可能超過所使用路徑上最慢的連結,而且加入通道後,路徑一定會稍微變慢。

WireGuard 不會壓縮資料。它沒有 OpenVPN 的 comp-lzo 等效功能,也沒有規劃加入,因為在加密前壓縮資料會洩漏明文資訊。大多數大量資料本來就已經壓縮,因此實務上不會因此付出代價。這是比較 WireGuard 與 OpenVPN 時需要權衡的實際差異之一,也是刻意的設計選擇。

完整通道會改變每個封包採用的路由。原本從用戶端前往附近 CDN(content delivery network)的流量,現在會先從用戶端前往 VPS,再前往 CDN。如果 VPS 位於另一個洲,每個請求都必須經過這段繞路,往返時間也會相應增加。沒有任何設定值能縮短這段路徑。請將 VPS 移到較近的位置,或使用分割通道,讓只有需要 VPN 的流量走較長的路徑。流量應該走哪條路,完全由 AllowedIPs 決定,而 cryptokey routing 說明了這項決策的方式

供應商的限制不在通道內,而且很容易被忽略。具有每月頻寬額度的方案,通常會在額度用完後將連接埠的速度限制到低得多的值,這時通道看起來就像故障。請先檢查供應商控制面板,再花一整晚調整 MTU。

PersistentKeepalive 不會影響吞吐量。它的作用是維持 NAT 對映,使伺服器仍能連線到位於家用路由器後方的用戶端。將它降低到 25 秒以下只會增加封包,無法解決任何問題。

依以下順序測量

  1. 重現問題,並記錄這是卡住還是速度均勻下降。卡住通常表示 MTU 有問題;速度均勻下降則不是。
  2. 使用 ping -M do 從用戶端對伺服器的公開位址進行二分搜尋,並記下路徑 MTU。
  3. 減去 80,將該 MTU 設定在兩端的 wg0,然後重新測試先前失敗的傳輸。
  4. 如果伺服器會轉送 peer 的流量,請在伺服器上加入 MSS clamping。
  5. 傳輸期間執行 mpstat -P ALL 1,並讀取 %soft%steal
  6. 在 tunnel 外部與內部執行 iperf3,測試兩個方向,並分別使用單一串流及 -P 4
  7. 對伺服器執行 mtr -rwc 100,確認遺失是否持續到最後一個 hop。

只有在步驟 5 顯示 CPU 已達上限後,才應變更伺服器方案。步驟 1 到 4 不會產生額外成本,且能解決大多數 tunnel 速度緩慢的問題。

FAQ

為什麼我的 WireGuard 通道 ping 很快,但下載速度很慢?

這種差異通常表示 MTU 有問題。小型封包能通過路徑上的所有連結,因此 ping 和 SSH 登入都能正常運作。大量傳輸會送出完整大小的區段;封裝後的封包比某些連結可接受的大小更大。如果路由器直接丟棄這些封包,且 ICMP 訊息無法傳回,就不會有任何元件回報遺失,傳輸也會停滯。使用 ping -M do 二分搜尋,對伺服器的公開位址找出路徑 MTU,再扣除 80 bytes 的封裝空間,並在兩端將結果設為 wg0 的 MTU。

WireGuard 應設定哪個 MTU?

沒有通用的數值,這正是預設值 1420 在部分環境中失效的原因。1420 是 1500 扣除 80 bytes 的 WireGuard 標頭、UDP 標頭與外層 IPv6 標頭。如果路徑可傳輸的大小少於 1500 bytes,例如 PPPoE DSL,或流量會穿過另一個通道,就需要使用更小的值。請先測量路徑 MTU,再從中扣除 80。

MSS clamping 能取代設定 MTU 嗎?

不能。MSS clamping 會改寫 TCP 交握中的 MSS 選項,讓兩端傳送較小的區段,因此不必修改介面就能修正 TCP。UDP 沒有可供改寫的交握,因此不受影響。MSS clamping 也只會套用到伺服器轉送的流量,因此直接執行於 WireGuard 伺服器上的服務無法受益。兩者應一併使用:在介面上設定正確的 MTU,並使用 MSS clamping 處理無法控制設定的對端。

更快的 VPS 方案會讓 WireGuard 更快嗎?

只有在 CPU 是瓶頸時才會,而一個指令即可確認。傳輸進行期間執行 mpstat -P ALL 1。如果唯一的核心上 %soft 接近 100,表示封包處理已達上限,增加核心數會提高效能。%steal 偏高表示主機過度超額配置,因此應改用其他方案或其他主機。如果兩個數值都很低,但通道仍然很慢,表示 CPU 處於閒置狀態,升級方案不會改善問題。

為什麼我的 Mac 比同一網路上的 Linux 用戶端慢?

Linux 用戶端使用核心內建的 WireGuard 模組,在核心空間處理封包,並將同一對端的加密工作分散到多個 CPU 核心。macOS 與 iOS app 使用 wireguard-go,也就是使用者空間實作,因為這些平台不允許 app 載入核心模組。使用者空間實作必須在核心與應用程式之間複製每個封包,而這些複製作業會降低傳輸量。這種差距是預期現象,沒有任何用戶端設定能消除它。