没有不值得去解决的问题,也没有不值得去学习的技术!

From 39-Second Freezes to 2-Second Instant Loads: A Two-Phase Deep Tuning Case Study with Wstunnel + WireGuard

[Figure 1: After stage-one tuning, the screen still shows some red errors everywhere, with a load time of 39.42 seconds]

作者:

在

As a developer who relies heavily on ChatGPT Plus for cross-language open-source project research, my recent experience with the web version could only be described as a disaster: the page spun forever, and pressing F12 to open the developer tools revealed a Network panel packed with red errors for rqstr?k=client-sY... (NS_BINDING_ABORTED). A full page load took as long as 39.42 seconds.

My network architecture is 本地 Ubuntu 26.04 -> Clash Verge Rev (TUN 模式) -> Wstunnel Client -> ZgoCloud 洛杉矶 VPS -> Wstunnel Server -> WireGuard Server. This setup is excellent at disguising traffic as TLS to evade DPI blocking, but its “TCP-over-UDP” structure is highly prone to head-of-line blocking on poor networks.

Tackling this pain point took two distinct stages of troubleshooting and tuning. Today I’m writing up a complete retrospective of both.


Stage One: Initial Mitigation and Locating the Bottleneck

In my earlier troubleshooting (see: “A Troubleshooting Record of Slow ChatGPT Web Page Loading”), I had already confirmed that packet loss at the underlying layer was caused by maintenance on the ZgoCloud host, and I made some initial optimizations:

  1. Used Wstunnel --connection-min-idle 10 to enable a long-lived connection pool and cut down on TLS handshake overhead.
  2. Restricted the WireGuard peer’s MTU in Clash’s proxies, and strictly aligned the Clash Verge TUN virtual adapter’s MTU to 1280, eliminating local packet fragmentation.
  3. Used iperf3 to confirm the key fact: bandwidth was sufficient, but TCP retransmissions were extremely high.

Stage-one tuning improved the experience somewhat, but during daytime peak hours the page still froze. This showed that parameter tweaks at the software layer alone could not fully offset the damage inflicted by the underlying TCP-over-TCP architecture.

[Figure 1: After stage-one tuning, the screen still shows some red errors everywhere, with a load time of 39.42 seconds]
[Figure 1: After stage-one tuning, the screen still shows some red errors everywhere, with a load time of 39.42 seconds]

Technical analysis: As soon as the underlying physical link drops a packet, Wstunnel’s TCP retransmissions get amplified further by WireGuard’s UDP retransmissions. The frontend waits for data that never arrives, times out, and cuts the connection to reconnect—producing a screen full of red errors.


Stage Two: “Surgical” Tuning from Server to Client

To eliminate the lag once and for all, I moved into the second stage of deep tuning. This time the focus was on squeezing the server’s network stack and locking in the local startup method.

1. Server Side: Component Upgrade and Kernel Network Stack

First, I upgraded the server-side Wstunnel to v11.0.0. The new version brings major improvements in underlying read throughput and long-lived connection stability.

[Figure 2: Server-side wstunnel-cli 11.0.0 upgraded successfully]
[Figure 2: Server-side wstunnel-cli 11.0.0 upgraded successfully]

I also forced the server-side WireGuard MTU to 1400 to complement the local 1280, leaving headroom for the WebSocket encapsulation overhead.

[Figure 3: MTU in the server-side wg0.conf changed to 1400]
[Figure 3: MTU in the server-side wg0.conf changed to 1400]

To make the Linux kernel more resilient under the high latency and packet loss of cross-border links, I aggressively tuned the kernel network stack via /etc/sysctl.d/:

  • Enabled the BBR congestion control algorithm.
  • Enlarged the TCP receive/send buffers to 4MB (the system auto-expanded them to 16MB).
  • Enabled TCP window scaling and Fast Open.
[Figure 4: Writing BBR and TCP buffer parameters on the server]
[Figure 4: Writing BBR and TCP buffer parameters on the server]

2. Local Side: No More Typing Commands by Hand—Locking It in as a Systemd Service

In stage one I had used nohup to keep Wstunnel running in the background temporarily, which broke on every reboot. This time I wrote the Wstunnel client—with its long-connection-pool parameters—into a startup script and configured it as a Systemd user-level service, achieving enabled auto-start on boot.

[Figure 5: Local nohup startup log with lots of TLS handshake messages]
[Figure 5: Local nohup startup log with lots of TLS handshake messages]
[Figure 7: systemctl --user status wstunnel.service showing active]
[Figure 7: systemctl –user status wstunnel.service showing active]

Real Results: A Qualitative Leap from “Unusable” to “Usable”

After completing all of stage two’s configuration, I restarted the Clash core and opened the ChatGPT web page again. First-paint time dropped straight to 2.38 seconds—the UI rendered instantly and the input box responded in a flash!

[Figure 9: After tuning, the page loads instantly; a few red X marks remain in the Network panel]
[Figure 9: After tuning, the page loads instantly; a few red X marks remain in the Network panel]

About the remaining “red X marks”:
As a developer, I have to record this objectively: at this point the Network panel still shows a small number of NS_BINDING_ABORTED. But this is fundamentally different from the earlier full-screen deadlock:
these are the browser proactively triggering its abort-and-retry mechanism when the ChatGPT frontend encounters tiny delays on non-core endpoints (such as rgstr heartbeat packets). They no longer block the page’s main rendering or the input box’s responsiveness.

Takeaways and Reflections

Looking back across both stages of troubleshooting, I came away with two important realizations:

  1. Bandwidth ≠ stability: For streaming long-lived connections (like ChatGPT), TCP retransmission rate is far deadlier than peak bandwidth. Stage one’s MTU alignment prevented fragmentation, while stage two’s server-side kernel tuning (BBR) made the TCP stack far more composed in the face of packet loss.
  2. The architecture’s ceiling: Wstunnel can disguise traffic as TLS, but the underlying TCP-over-TCP architecture is inherently vulnerable to packet loss. Whenever the network experiences even minor jitter, it still cannot achieve 100% error-free operation.

With this two-stage tuning, the current configuration has pulled the experience back above the passing line of “usable day to day.” If the network deteriorates again during daytime hours in the future, the ultimate solution will be to abandon the TCP tunnel entirely and migrate to Hysteria2, a protocol built on QUIC (UDP) that is inherently packet-loss resistant.

系列导航

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系