Turning an Idle Android Phone into a High-Performance Proxy Node: A Termux & Xray Adventure
Turning an Idle Android Phone into a High-Performance Proxy Node
In my quest to build the ultimate High-Availability (HA) home proxy network, I realized I had a powerful, 24/7 connected ARM device gathering dust: my old Android phone.
By integrating it as a secondary primary node alongside my Mac, I achieved a “Triple-Active” setup: Mac + Android -> VPS Backup. Here is a technical deep dive into how I turned an Android phone into a “Dumb Node” proxy using Termux, and the specific networking pitfalls I solved along the way.
📱 The Concept: The “Dumb Node”
Unlike my VPS or Mac which run full X-UI panels with fancy interfaces, an Android phone is resource-constrained (battery/heat). I didn’t want to run a heavy web panel + database.
I opted for a Stateless “Dumb Node” Architecture:
- No UI: Runs raw
Xray-corebinary. - Hardcoded Config: UUID matches the VPS exactly.
- Tunnel:
frpcexposes the local port to the VPS.
To the Load Balancer (Nginx), it looks identical to my Mac. To the phone, it’s just two lightweight background processes.
🛠 The Stack: Termux is All You Need
Forget rooting. Termux provides a sufficient user-space Linux environment.
1 | pkg install frp # Note: Package name is 'frp', not 'frpc' |
For the proxy core, I fetched the standard ARM64 (aarch64) binary of Xray-core.
🕳 The Pitfalls (And How I Fixed Them)
This was not plug-and-play. Android’s networking stack is… unique.
1. The Package Name Trap
If you try to install frpc in Termux, it fails.
Correction: The package is named frp, which installs both frpc and frps.
2. The “Connection Refused” Ghost
Initially, Xray logs were clean, but traffic failed. netstat on Android requires root to show useful output, making debugging blind.
Cause: Xray wasn’t actually running or listening on the expected interface.
Fix: I wrote a start.sh wrapper to manage process lifecycles properly, dumping logs to stdout for debugging instead of /dev/null.
3. The DNS Black Hole ([::1]:53)
This was the nastiest bug. Xray logs showed:dial tcp: lookup www.google.com on [::1]:53: read: connection refused
Why?
Termux doesn’t have a standard /etc/resolv.conf pointed to a system resolver in the way Go (Xray’s language) expects. It tried to use the local loopback IPv6 address for DNS, which had no listener.
The Fix:
Hardcode DNS in Xray’s config.json. Don’t rely on the system.
1 | "dns": { |
4. The IPv6 Phantom
Even with DNS fixed, connections timed out. Android prefers IPv6, but in many mobile/WiFi configurations, the IPv6 route is flaky or guarded.
The Fix:
Force Xray to use IPv4 for outgoing connections.
1 | "outbounds": [ |
UseIP forces Xray to resolve the hostname to an IP (using our hardcoded 8.8.8.8) and connect to that IP, bypassing system routing quotas.
🔋 Optimization: Battery vs. Performance
Running a proxy 24/7 on a battery device requires balance.
- Wakelock: Essential. Termux’s “Acquire Wakelock” notification prevents the CPU from deep sleeping. Without this, the proxy dies 5 minutes after screen off.
- Network Choice:
- 5G: Fast, low latency, but high heat and battery drain. Good for temporary backup when home internet dies.
- Wi-Fi: Much lower power consumption. Recommended for permanent installation.
- Process Priority: Android aggressively kills background apps. I had to lock Termux in the “Recent Apps” view and disable battery optimization for it.
🚀 Final Impact
I now have a redundant proxy node that consumes negligible power, requires zero maintenance (stateless), and is portable.
If my Mac crashes? Traffic hits the Android.
If my home fiber cut? Traffic hits the Android (on 5G).
If everything dies? Traffic hits the VPS.
Resilience is not about never failing; it’s about failing unnoticed.