DraftReviewPublishedArchived

Building a Self-Hosted Proxy from Scratch: An Agent-Driven Full-Stack Network Engineering Battle

Three VPS instances, one Cloudflare Worker, full AI Agent collaboration throughout

The complete record from buying the first VPS to stable internet access. After battling IP bans, Worker rate limits, and protocol detection, the final result is a three-tier failover architecture with AWS Lightsail + Vultr + CF Worker. The entire project was completed in collaboration with AI Agent for code writing, troubleshooting, and architecture design.

By Joker04/13/202615 min

I. Why Self-Host

Let me start with the conclusion: I don't recommend self-hosting for most people. Buy a reliable service for a hundred bucks a year, save yourself the headache. Self-hosting is for these types of people:

  • Privacy purists who don't want their traffic going through someone else's servers
  • Those who want to learn networking and server ops
  • People with special needs (fixed IP, custom rules, low latency)
  • Tinkerers who enjoy the process itself

I'm the last type. After using commercial services for years, while stable, I always felt they weren't transparent enough — where does my traffic go, are logs stored, what if the service disappears one day? So I decided to build my own.

The entire process was done in deep collaboration with AI Agent — from architecture design, code writing, troubleshooting to operations monitoring, the Agent was deeply involved at every stage. This isn't a story of someone tinkering alone in a corner — it's a real-world record of humans and AI collaborating to solve genuine engineering problems.

Turns out, this road was far more winding than I imagined.


II. First VPS: BandwagonHost

Why BandwagonHost

BandwagonHost is well-known in the Chinese community, and the most attractive feature is its CN2 GIA route — China Telecom's premium return route with low latency and minimal packet loss. I chose the Osaka, Japan datacenter.

Setup

Installed xray on the VPS, configured VLESS + Reality protocol. Reality claims to disguise traffic as normal HTTPS, with strong anti-detection capabilities.

# 安装 xray
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install

# 生成 Reality 密钥对
xray x25519

Reality server configuration:

{
  "inbounds": [{
    "port": 443,
    "protocol": "vless",
    "settings": {
      "clients": [{ "id": "your-uuid", "flow": "xtls-rprx-vision" }],
      "decryption": "none"
    },
    "streamSettings": {
      "network": "tcp",
      "security": "reality",
      "realitySettings": {
        "dest": "www.amazon.co.jp:443",
        "serverNames": ["www.amazon.co.jp"],
        "privateKey": "your-private-key",
        "shortIds": ["abcdef"]
      }
    }
  }]
}

Result: IP Blocked by GFW

FATAL — Less than two weeks of operation, the IP was directly blocked by the GFW. Couldn't ping from within China, SSH was dead, all proxy protocols failed.

The GFW identified proxy traffic characteristics through Deep Packet Inspection (DPI) and added the IP to the blacklist. While Reality protocol claims anti-detection, the GFW's DPI capabilities keep upgrading, and subtle characteristics during the TLS handshake phase can still be captured.

Even worse is BandwagonHost's policy: After IP ban, no refunds, no datacenter migration, you can only wait for the GFW to unblock it (usually 1-4 weeks, provided you stop all proxy services).

Lesson: Don't put all your eggs in one basket. If one VPS gets blocked, you're completely cut off. You must have a backup plan.


III. Emergency Backup: Vultr Tokyo

Why Vultr

Vultr charges by the hour, starting at $5/month. The biggest advantage is you can destroy and rebuild at any time — if the IP gets blocked, just spin up a new one in minutes.

Setup

Went straight with Hysteria2 + xray dual protocol. Hysteria2 runs on QUIC (UDP), xray on TCP, dual insurance.

# 安装 Hysteria2
bash <(curl -fsSL https://get.hy2.sh/)

# 生成自签证书
openssl req -x509 -nodes -newkey ec:<(openssl ecparam -name prime256v1) \
  -keyout /etc/hysteria/server.key \
  -out /etc/hysteria/server.crt \
  -subj "/CN=www.bing.com" -days 3650

# 启动
systemctl enable --now hysteria-server

Result: Usable, But Slow

MetricValueVerdict
Latency260msUnacceptable
Google Search~1.0sUsable but laggy
YouTube 1080pOccasional bufferingBarely passable

Key finding: The speed gap is a routing issue, not a protocol issue. Same city (Tokyo), but different cloud providers have vastly different international routing. Later testing revealed AWS Tokyo had only 44ms — same city, 6x difference in latency.


IV. Cloudflare Worker Relay

Since direct connection wasn't ideal, how about routing through CDN? Cloudflare has 300+ nodes globally, and Chinese users typically connect to CF with 30-50ms latency. If we can disguise proxy traffic as normal HTTPS requests and relay through CF CDN to the VPS, we can theoretically both reduce latency and hide the VPS's real IP.

Architecture

mihomo-party :7890 HTTPS Cloudflare CDN + Worker VLESS over WS TCP VPS (xray) VLESS Server google.com youtube.com Client CDN (30-50ms) Tokyo Target

The principle: Cloudflare Worker handles WebSocket connections. The client sends VLESS over WebSocket requests to the CF CDN, and the Worker connects to the backend VPS's xray through the cloudflare:sockets API for transparent relay.

Pit 1: workers.dev Is Blocked in China

CF Worker's default domain is xxx.workers.dev. Deployed, tested — couldn't access.

Reason: The GFW blocks the entire workers.dev domain at the SNI level. During TLS handshake, the ClientHello's SNI contains workers.dev, and the connection is immediately reset.

Solution: Buy a custom domain and bind it to the Worker.

# wrangler.toml
[routes]
pattern = "your-domain.com"
custom_domain = true

SNI becomes the custom domain, and the GFW won't block a random normal domain.

Pit 2: Worker Rate-Limited by Cloudflare (Most Outrageous)

During the debugging phase, frequent wrangler deploy, deletions, and rebuilds triggered CF's internal rate limiting. Worker started returning HTTP 1101 or 522 errors.

The most bizarre part: I waited all night and it didn't recover. Even replacing the code with the simplest hello world, the same Worker name still errored.

// 最小测试代码 -- 在被限速的 Worker 名称下依然返回 1101
export default {
  async fetch() {
    return new Response("hello");
  }
};

After extensive investigation, I found:

CF's rate limiting targets specific Worker names, not the account level! Creating a new Worker with a different name under the same account works perfectly fine. Solution: change wrangler.toml's name, redeploy, delete the old Worker.

Pit 3: Free Tier CPU Limit (10ms/request)

CF Worker free tier only allows 10ms CPU time per request.

I initially assembled the admin panel HTML (~37KB) dynamically in the Worker with 400+ push() calls, which timed out immediately. An even more insidious issue: Single strings over 6000 characters crash the V8 engine.

Solution: Pre-build HTML, store in KV.

// worker.js -- 从 KV 读取 HTML,CPU 时间 < 1ms
if (path === '/admin') {
  const html = await env.CONFIG.get("ADMIN_HTML");
  return new Response(html, {
    headers: { "Content-Type": "text/html;charset=utf-8" }
  });
}

Relay Solution Evaluation

MetricValueVerdict
Latency~50msDecent
Google Search~1.5sSlow
StabilitySubject to Worker rate limitingUnreliable
ProtocolWebSocket, no UDPLimited

V. AWS Lightsail: Finally Found the Answer

Latency Testing

After stepping through all the pitfalls, I ran a systematic latency test:

Cloud ProviderDatacenterLatencyMonthly Cost
AWS LightsailTokyo44ms$5
Cloudflare CDNAnycast39msFree
LinodeTokyo43ms$5
BandwagonHost CN2 GIAOsaka~60-80ms$$
VultrTokyo260ms$5

VPS network experience is 90% determined by routing, 10% by protocol. Choosing the right provider is a hundred times more important than choosing the right protocol.

AWS Lightsail $5/month plan: 0.5GB RAM, 2 vCPU, 20GB SSD, 1TB bandwidth.

Setup

# 安装 Hysteria2
bash <(curl -fsSL https://get.hy2.sh/)

# 生成自签证书
openssl req -x509 -nodes -newkey ec:<(openssl ecparam -name prime256v1) \
  -keyout /etc/hysteria/server.key \
  -out /etc/hysteria/server.crt \
  -subj "/CN=www.bing.com" -days 3650

# 关键!调整内核 UDP 缓冲区
echo "net.core.rmem_max=16777216" >> /etc/sysctl.conf
echo "net.core.wmem_max=16777216" >> /etc/sysctl.conf
sysctl -p

# 启动
systemctl enable --now hysteria-server

Note: AlmaLinux 9 on Lightsail doesn't have firewalld, ports must be managed through AWS's own firewall:

aws lightsail open-instance-public-ports \
  --instance-name "your-instance" \
  --port-info fromPort=443,toPort=443,protocol=UDP

Results

MetricValuevs Vultr
Latency44ms6x faster
Google Search0.32s3x faster
YouTube 4KSmoothQuantum leap

Finally. Google Search in 0.32 seconds — even faster than the commercial VPN I used before (0.36s).


VI. Protocol Choice: Why Hysteria2

ProtocolTransportDisguise MethodResult
VLESS + RealityTCPTLS fingerprint spoofingDetected by GFW DPI
VLESS + WS + TLSTCP (WebSocket)Normal HTTPSUsable but slow
Hysteria2UDP (QUIC)Standard HTTP/3Fastest, stable

Hysteria2 is built on the QUIC protocol — the same protocol stack used by Google and Cloudflare for HTTP/3. The GFW has a hard time distinguishing Hysteria2 traffic from normal HTTP/3 traffic.

Another benefit of QUIC is multiplexing and 0-RTT connections. TCP needs three-way handshake + TLS handshake (2 RTTs), while QUIC can complete in 1 or even 0 RTTs. At 44ms latency, each new connection saves about 88ms.

Critical Tuning: UDP Buffer Size

# 必须设置!默认 UDP 缓冲区太小,QUIC 吞吐量上不去
sysctl -w net.core.rmem_max=16777216   # 16MB 接收
sysctl -w net.core.wmem_max=16777216   # 16MB 发送

Without this change, download speeds can be just one-tenth of theoretical maximum.


VII. Self-Built Subscription System

With multiple VPS instances + multiple protocols, manually maintaining client configs is too tedious. I wrote a subscription distribution system using Cloudflare Worker: clients only need to import one URL, and all node information is automatically delivered.

The Worker exposes two endpoints:

  • /clash?token=xxx — Returns Clash/mihomo format YAML config
  • /base64?token=xxx — Returns Base64-encoded VLESS links

VPS IP addresses are stored in KV, dynamically read. When IP changes, just update KV — no redeployment needed.

// 订阅入口
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const token = url.searchParams.get("token");
    if (token !== SECRET_TOKEN)
      return new Response("Not Found", { status: 404 });

    // 从 KV 动态获取 VPS IP
    const awsIP = await env.CONFIG.get("AWS_IP");
    const vultrIP = await env.CONFIG.get("VULTR_IP");

    if (url.pathname === "/clash")
      return generateClashYAML(awsIP, vultrIP);
    if (url.pathname === "/base64")
      return generateBase64Links(awsIP, vultrIP);
  }
};

Traffic Routing Rules

The subscription config includes complete traffic routing rules, based on community rule sets (Loyalsoldier, blackmatrix7):

  • AI Services (ChatGPT, Claude, Gemini) → Proxy
  • Streaming (YouTube, Netflix, Spotify) → Proxy
  • Social/Dev (Telegram, Twitter, GitHub) → Proxy
  • Chinese Sites (WeChat, Taobao, Bilibili) → Direct
  • Microsoft/Apple → Direct by default, manually switchable
rule-providers:
  OpenAI:
    type: http
    behavior: classical
    url: "https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Clash/OpenAI/OpenAI.yaml"
    interval: 86400

rules:
  - RULE-SET,reject,REJECT
  - RULE-SET,OpenAI,AI服务
  - RULE-SET,YouTube,流媒体
  - RULE-SET,Google,代理
  - RULE-SET,direct-domain,DIRECT
  - MATCH,兜底

VIII. Admin Panel & Monitoring

Three-Domain Architecture

CF Worker vps.example.com 订阅分发 VLESS WS 中转 管理 API CF Pages panel.example.com 管理面板 (纯静态) 节点测试 流量监控 VPS API api.example.com:2053 系统资源监控 网络流量统计 在线用户数 WHY THREE DOMAINS? Worker 被限速 → Pages 面板正常 → VPS API 正常 三个服务完全独立,任何一个挂了不影响其他两个

Why separate them? Because I learned the hard way. Initially the panel was HTML returned by the Worker — when the Worker got rate-limited by CF, the panel went down with it, completely blind.

After separation:

  • CF Pages: Pure static hosting, unaffected by Worker rate limiting
  • VPS API: A lightweight Python script running on the VPS, proxied through CF CDN — even if the Worker dies, you can still check status

VPS Monitoring API

A lightweight Python HTTP service running on the VPS:

# /opt/vps-api/api.py
from http.server import HTTPServer, BaseHTTPRequestHandler
import json

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/status':
            data = {
                'cpu': get_cpu_usage(),
                'memory': get_memory(),
                'disk': get_disk(),
                'network': get_network_stats(),
                'hysteria2_users': get_hy2_online(),
            }
            self.send_json(data)

# 用 systemd 管理,开机自启
HTTPServer(('0.0.0.0', 2053), Handler).serve_forever()

Proxied through CF A record (proxied mode) to the VPS port — the VPS's real IP is never exposed.

Panel Features

The admin panel is a single HTML file deployed on CF Pages:

  • AWS Instance Management: View instance status, bandwidth usage
  • Node Testing: One-click connectivity and latency checks for all nodes
  • Subscription Logs: Record every subscription pull with timestamp, IP, UA
  • VPS Monitoring: Real-time CPU, memory, disk, network data
  • CF IP Management: Custom CDN relay optimized CF IPs

IX. Final Architecture & Cost

Architecture Overview

Client (mihomo-party) AWS Lightsail Hysteria2 直连 44ms | 0.32s Vultr Tokyo Hysteria2 备用 260ms | 1.0s CF Worker VLESS WS 中转 ~50ms | 1.5s PRIMARY BACKUP FALLBACK FAILOVER STRATEGY AWS 正常 → 走 AWS | AWS 故障 → 自动切 Vultr → 再降级 CF 中转

The client uses a fallback strategy group: health checks every 60 seconds, fully automatic switching, transparent to the user.

Monthly Cost

ItemCost
AWS Lightsail Tokyo (Primary)$5/month
Vultr Tokyo (Backup)$5/month
BandwagonHost CN2 GIA (IP blocked)~$5/month (annual prepaid)
Cloudflare Worker + Pages + KV$0 (free tier)
Domain (Namesilo)~$0.4/month ($5/year)
Actual Monthly Spend~$10.4/month

Once BandwagonHost's IP is unblocked, Vultr can be stopped, bringing it down to ~$5.4/month, about ¥38.


X. Complete Pitfall Guide & Lessons Learned

Timeline

  • Week 1 — BandwagonHost IP blocked by GFW. Reality direct connection detected in under two weeks.
  • Week 2 — Vultr emergency backup. Usable but 260ms latency, confirming "routing > protocol."
  • Week 2-3 — CF Worker relay pitfalls. workers.dev blocked → Worker rate-limited → CPU exceeded, one pit after another.
  • Week 3 — AWS Lightsail goes live. 44ms latency, 0.32s Google Search.
  • Week 4 — Management system completed. Three-domain architecture, panel, monitoring, subscriptions all done.

Six Key Lessons

#1 Choosing the Right Route Is 100x More Important Than Choosing the Right Protocol

Different cloud providers in the same city can have 6x latency difference. Test latency first, then buy VPS.

#2 Always Have a Backup Plan

IP blocked, Worker rate-limited, protocol detected — any single point can fail. Have at least two independent paths ready.

#3 Cloudflare Free Tier Has More Limits Than You Think

10ms CPU limit, 100K requests/day, egress IPs may be rejected, workers.dev blocked in China. Free stuff always has a price.

#4 Decouple Your Services

The monitoring panel shouldn't depend on the proxy Worker, the VPS API shouldn't depend on the CDN Worker. When any service goes down, you should still be able to see what's happening.

#5 Don't Run Two Proxy Clients on One Mac

Different proxy clients fight over system proxy settings. One sets 7890, the other sets 1087, they overwrite each other, and neither works.

#6 Hysteria2 Tuning Comes Down to One Thing: UDP Buffer

net.core.rmem_max=16777216 and net.core.wmem_max=16777216. Without this, speeds may be just one-tenth of theoretical maximum.

Agent's Role in This Project

Throughout this project, the AI Agent wasn't just "helping write code" — it was deeply involved at every step:

  • Architecture Design: Analyzing pros and cons of each approach, proposing the three-tier failover architecture
  • Troubleshooting: Systematically testing and eliminating possibilities when the Worker was rate-limited, ultimately pinpointing the "rate limit by name" pattern
  • Code Implementation: All code for the Worker subscription system, VLESS relay, admin panel, and monitoring API
  • Operations Support: Remote SSH VPS configuration, debugging Hysteria2, setting up firewalls and systemd
  • Knowledge Documentation: Structuring all pitfall experiences into reusable knowledge base

This is not a workload one person could complete in a reasonable timeframe. Agent compressed the "zero to usable" cycle to under one month.

Tool Chain Summary

类别 工具 用途 代理协议 Hysteria2, xray (VLESS) VPS 端代理服务 客户端 mihomo-party (Clash Meta) macOS 代理客户端 CDN/Serverless CF Worker, CF Pages, CF KV 中转、面板、存储 VPS AWS Lightsail, Vultr, 搬瓦工 代理服务器 域名 Namesilo + Cloudflare DNS 域名注册与解析 部署 Wrangler CLI CF Worker/Pages 部署 AWS API aws4fetch Worker 中签名调用 AWS API 规则 Loyalsoldier, blackmatrix7 分流规则集 OS AlmaLinux 9 VPS 系统 AI Claude (Agent) 全程协作

Final Thoughts

This project took nearly a month of tinkering. Looking back, if I had chosen AWS Lightsail + Hysteria2 from the start, it could have been done in a day. But precisely because I took the long way around, I figured out the blocking logic, the routing differences between cloud providers, and all the hidden traps in Cloudflare's free tier.

Self-hosting a proxy isn't the most economical choice, but it's the most transparent one. Where does your traffic go, who sees it, what logs are stored — the answers to these questions can only be confirmed when you build it yourself.

And with Agent's help, the barrier to entry dropped significantly. You don't need to be a network engineer, you don't need to master every protocol's details — what you need is a clear goal, patience to solve problems, and a reliable AI partner.


For educational and learning purposes only. Please comply with local laws and regulations, and use technology tools responsibly.

QUEST COMPLETEREWARD: +30 XP, +1 LEGENDARY ITEM
Build Progress100%
No signal
PULSE
0PULSES