Building a Self-Hosted Proxy from Scratch: An Agent-Driven Full-Stack Network Engineering Battle
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.
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
| Metric | Value | Verdict |
|---|---|---|
| Latency | 260ms | Unacceptable |
| Google Search | ~1.0s | Usable but laggy |
| YouTube 1080p | Occasional buffering | Barely 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
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'sname, 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
| Metric | Value | Verdict |
|---|---|---|
| Latency | ~50ms | Decent |
| Google Search | ~1.5s | Slow |
| Stability | Subject to Worker rate limiting | Unreliable |
| Protocol | WebSocket, no UDP | Limited |
V. AWS Lightsail: Finally Found the Answer
Latency Testing
After stepping through all the pitfalls, I ran a systematic latency test:
| Cloud Provider | Datacenter | Latency | Monthly Cost |
|---|---|---|---|
| AWS Lightsail | Tokyo | 44ms | $5 |
| Cloudflare CDN | Anycast | 39ms | Free |
| Linode | Tokyo | 43ms | $5 |
| BandwagonHost CN2 GIA | Osaka | ~60-80ms | $$ |
| Vultr | Tokyo | 260ms | $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
| Metric | Value | vs Vultr |
|---|---|---|
| Latency | 44ms | 6x faster |
| Google Search | 0.32s | 3x faster |
| YouTube 4K | Smooth | Quantum leap |
Finally. Google Search in 0.32 seconds — even faster than the commercial VPN I used before (0.36s).
VI. Protocol Choice: Why Hysteria2
| Protocol | Transport | Disguise Method | Result |
|---|---|---|---|
| VLESS + Reality | TCP | TLS fingerprint spoofing | Detected by GFW DPI |
| VLESS + WS + TLS | TCP (WebSocket) | Normal HTTPS | Usable but slow |
| Hysteria2 | UDP (QUIC) | Standard HTTP/3 | Fastest, 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
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
The client uses a fallback strategy group: health checks every 60 seconds, fully automatic switching, transparent to the user.
Monthly Cost
| Item | Cost |
|---|---|
| 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
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.