Full guide for the mhrv-rs Android app: install, first-run setup, troubleshooting, known limits.
- Overview
- Requirements
- 1. Install the APK
- 2. Deploy the Apps Script
- 3. Enter your config in the app
- 4. Run the SNI tester
- 5. Install the MITM certificate
- 6. Start the tunnel
- UI quick reference
- Known limitations
- Troubleshooting
- Uninstall
The Android app is the exact same mhrv-rs Rust crate that powers the desktop build, wrapped in a Compose UI and fed a TUN file descriptor via VpnService + tun2proxy. Every app on the device is routed through the proxy — no per-app setup.
Any app on the device
│
▼
VpnService TUN ──► tun2proxy (in-process)
│
▼
Local SOCKS5 listener ──► mhrv-rs dispatcher
│
┌───────────────────────┤
▼ ▼
sni-rewrite tunnel Apps Script relay
(Google-owned hosts (everything else,
direct to google_ip) via your /exec URL)
Setup time: ~10 minutes if your Apps Script deployment already exists, ~15 min if you're deploying fresh.
| Android version | 7.0 (API 24) or later |
| Device architecture | Any. The APK is universal: arm64-v8a, armeabi-v7a, x86_64, x86 |
| Google account | Yes — you'll deploy the Apps Script under it. A throwaway Gmail works |
| Screen lock | PIN, pattern, password, or biometric + fallback. Required by Android for user-CA install. Can be removed after install; the cert stays trusted |
| Data usage | ~5 MB for the APK, then ~2 MB overhead per GB of browsing (base64 + JSON wrapping) |
Scope note. mhrv-rs relays through Apps Script. That's what makes it cheap and DPI-resilient, but it's also what imposes the known limitations below. If you're evaluating against a real VPN (WireGuard/Tailscale/OpenVPN), skim that section first.
- On your phone, open the browser and go to https://github.com/therealaleph/MasterHttpRelayVPN-RUST/releases/latest.
- Download
mhrv-rs-android-universal-v*.apk. - Tap the download to open the installer.
- When Android asks "Allow this source to install apps?":
- Tap Settings
- Toggle Allow from this source
- Tap ← Back → Install
- Tap Open once install finishes.
If Android refuses with "App not installed": an old build signed with a different key is still present.
Settings → Apps → mhrv-rs → Uninstall, then try again. (From v1.0.2 onward this is a one-time thing — updates are signed with a stable key.)
Skip this step if you already have a working /exec URL.
Do this on a laptop — it's a browser-heavy flow that's painful on a phone.
-
Go to https://script.google.com → New project.
-
Copy the full contents of
assets/apps_script/Code.gsfrom this repo. -
In the script editor, select the default
function myFunction() {}and paste over it. -
Find the line near the top:
const AUTH_KEY = "CHANGE_ME_TO_A_STRONG_SECRET";
Replace the placeholder with a strong random secret (20+ chars, letters + digits). Save this value — you'll paste it into the app too.
-
File → Save (⌘S / Ctrl+S). Name the project something like
mhrv-relay. -
Deploy → New deployment.
-
Click the gear icon → Web app. Fill in:
Field Value Description mhrv-relay v1(or whatever)Execute as Me Who has access Anyone -
Click Deploy. First time only: Google asks for permissions.
- Click Authorize access → pick your account
- On "Google hasn't verified this app" → Advanced → Go to <project name> (unsafe) → Allow
-
Copy the Web app URL. It looks like
https://script.google.com/macros/s/AKfyc.../exec.
What the script does
It receives POST { method, url, headers, body_base64 } from our proxy, calls UrlFetchApp.fetch(url, ...) inside Google's datacenter, and returns { status, headers, body_base64 }. DPI bypass comes from us connecting to script.google.com using a different TLS SNI than the HTTP Host header — the ISP sees www.google.com, Google's edge routes by the Host header inside the encrypted stream.
Back on the phone:
| Field | What to enter |
|---|---|
| Deployment URL(s) or script ID(s) | The /exec URL you copied. You can paste multiple — one per line — and the proxy will round-robin between them (useful when you hit the 20k/day per-script quota) |
| auth_key | The exact string you put in AUTH_KEY inside Code.gs |
| google_ip | Leave the default. The next step will auto-populate it |
| front_domain | Leave at www.google.com |
Tap anywhere outside the text fields to dismiss the keyboard.
Before starting the tunnel, verify the outbound leg works. Expand SNI pool + tester and tap Test all.
| Result | Meaning | Action |
|---|---|---|
✅ Green check + NNN ms |
google_ip is reachable + accepts the SNI |
Proceed |
❌ connect timeout on every row |
Configured google_ip is unreachable |
Tap Auto-detect google_ip under the Network card, then Test all again |
❌ connect timeout on some rows |
Those specific SNIs are DPI-filtered on your network | Leave them unchecked; rotation pool uses only ticked boxes |
❌ dns: ... |
Device can't resolve www.google.com at all |
Fix Wi-Fi / airplane mode |
If you tap Auto-detect and it still fails on every row, your network is blocking Google's edge entirely — mhrv-rs can't help there.
The proxy terminates TLS locally (re-encrypts before routing through Apps Script), so your phone needs to trust a cert we minted on first run.
-
In the app, tap Install MITM certificate.
-
The confirmation dialog shows the certificate fingerprint. Tap Install.
-
The app:
- saves a PEM copy to
Downloads/mhrv-ca.crt - opens the Android Settings app
- saves a PEM copy to
-
If you don't have a screen lock — Android will prompt you to set one now. You have to. User CAs require it. You can remove it after install; the cert stays trusted.
-
In Settings, tap the search bar at the top and type
CA certificate. Open the result labelled "CA certificate" (or "Install CA certificate" on some OEMs).Don't pick "VPN & app user certificate" or "Wi-Fi certificate" — wrong category, won't work.
Searching is more reliable than navigating menus: Pixel/Samsung/Xiaomi all bury CA install under different paths, but all of them index it under "CA certificate" in search.
-
Android warns "Your network may be monitored by an unknown third party". That's us. Tap Install anyway.
-
Pick Downloads → tap mhrv-ca.crt. Give it a friendly name (or accept the default). Tap OK.
-
Switch back to the mhrv-rs app. A snackbar confirms Certificate installed ✓ — the app verifies by fingerprint against
AndroidCAStore.If it says "not yet installed", repeat step 5.
Why can't the app install the cert directly?
Android 11 removed the inline KeyChain.createInstallIntent flow. That intent used to open a category picker directly inside the app. On current Android it opens a dead-end dialog with just a Close button — Google wants CA installs to be deliberate. We do the grunt work (save file, open Settings, verify afterwards), but the manual navigation step is unavoidable.
- Tap Start.
- Android shows the VPN-permission dialog: "mhrv-rs wants to set up a VPN connection...". Tap OK.
- A key icon appears in the status bar. That's your VPN indicator.
- Open Chrome. Try
https://www.cloudflare.com,https://yahoo.com,https://discord.comas stress tests — all should render normally.
Expand Live logs to watch the traffic flow:
| Log line | What it means |
|---|---|
SOCKS5 CONNECT -> <host>:443 |
Browser opened a TCP flow; TUN captured it |
dispatch <host>:443 -> MITM + Apps Script relay |
Routing decision |
MITM TLS -> <host>:443 (sni=<host>) |
Our leaf cert was accepted by the browser |
relay GET https://<host>/... |
Forwarded to Apps Script |
preflight 204 <url> |
CORS preflight we answered ourselves (normal, don't worry about these) |
| Control | Location | Notes |
|---|---|---|
| Deployment URL(s) or script ID(s) | Apps Script relay section | One per line; round-robin dispatch |
| auth_key | Apps Script relay section | Must match AUTH_KEY in Code.gs |
| google_ip / front_domain | Network section | Auto-detect button fills google_ip via DNS |
| Auto-detect google_ip | Under the Network row | Re-resolves www.google.com + repairs front_domain if corrupted to an IP |
| SNI pool + tester | Collapsible | Checkboxes for rotation; per-row Test + Test all |
| Advanced | Collapsible | verify_ssl, log_level, parallel_relay, upstream_socks5 |
| Start / Stop | Bottom row | 2-second debounce between taps |
| Install MITM certificate | Below Start/Stop | Save PEM → open Settings → search "CA certificate" |
| Live logs | Collapsible (below the Install button) | 500ms poll of the proxy's log ring buffer |
| v1.0.x (version badge) | Top bar, right | Tap to check GitHub for a newer release |
Read this before reporting a bug — most "it doesn't work" reports fall into one of these.
On Cloudflare-protected sites that challenge every request, you'll solve the Turnstile, reach the page, then get challenged again on the next click. This is inherent to the Apps Script relay model:
| Factor | Normal browser | Apps Script relay |
|---|---|---|
| Egress IP | Stable (your ISP) | Rotates across Google's datacenter pool per request |
| User-Agent | Chrome's | Fixed Google-Apps-Script (locked by Google; we can't override) |
| TLS JA3/JA4 | Chrome's | Google-datacenter's |
Cloudflare's cf_clearance cookie is bound to the (IP, UA, JA3) tuple the challenge was solved against. Different IP next request → re-challenge.
Sites that only gate the first page load (most of CF's Bot Fight Mode customers) work fine after one solve. Sites that challenge every request (crypto exchanges, adult, some forums) fundamentally can't hold a session through this architecture — use a different tunnel for those.
The SOCKS5 listener only handles CONNECT, not UDP ASSOCIATE. Chrome tries HTTP/3 first and falls back to HTTP/2 over TCP, which works fine. Effect: slightly slower first connect, everything else normal.
The TUN only routes IPv4 (addRoute 0.0.0.0/0). IPv6 goes out your normal interface, including WebRTC. If you're using mhrv-rs for privacy rather than DPI bypass, disable IPv6 on your Wi-Fi network entirely.
Each /exec has a daily execution limit (20k/day for consumer Google accounts, higher for Workspace). Heavy streaming or infinite-scroll sites burn through it. Mitigation: deploy 2–3 scripts, paste all their /exec URLs into the app, one per line — the proxy round-robins.
By default, Android apps opt out of trusting user-installed CAs (Android 7+ Network Security Config default). Banking apps, Netflix, Spotify, most messengers — they'll fail with cert errors through mhrv-rs. The TUN routes their traffic to us; they just refuse our leaf. Only apps that explicitly opt in (browsers, curl, some developer tools) will work. This is a general MITM-proxy limitation.
| Symptom | Likely cause | Fix |
|---|---|---|
504 Relay timeout in Chrome |
Apps Script deployment not responding | Re-check the /exec URL (must end in /exec, not /dev). Watch Live logs for Relay timeout vs connect: errors |
NET::ERR_CERT_AUTHORITY_INVALID |
MITM CA not installed / not found | Redo step 5. Make sure you picked "CA certificate" in Settings, not VPN or Wi-Fi |
NET::ERR_CERT_COMMON_NAME_INVALID on Cloudflare sites |
Pre-v1.0 bug | Upgrade to v1.0.0 or later |
| JS parts of a site don't load | Pre-v1.0 OPTIONS rejection | Upgrade to v1.0.0+. If still present: Live logs → grep for Relay failed, report |
| All SNIs time out in the tester | google_ip is stale (Google rotated the A record) |
Tap Auto-detect google_ip |
| SNI tester red on some rows only | Those SNIs are DPI-filtered on your network | Uncheck the failing ones in the rotation pool |
| App closes when tapping Stop | Was a v1.0.0/1.0.1 race bug | Upgrade to v1.0.2. If still present on v1.0.2+: adb logcat -s MhrvVpnService mhrv-crash mhrv_rs and report |
INSTALL_FAILED_UPDATE_INCOMPATIBLE when upgrading |
Old APK signed with a different key (pre-v1.0.2) | Uninstall first, then install the new APK. Only a one-time thing — v1.0.2 onward has a stable signature |
| Chrome white-pages with no error | Often a rendering bug on the emulator with software GPU | Test on real hardware. Check Live logs to verify the relay is actually making requests |
| Cloudflare Turnstile loop | Known limitation | No fix inside this architecture |
| Banking/streaming apps show cert errors | Known limitation | No fix — app chose not to trust user CAs |
If you need to report a bug:
adb logcat -c # clear
# reproduce the issue in the app
adb logcat -d | grep -E "MhrvVpnService|mhrv_rs|mhrv-crash|tun2proxy" > mhrv.logAttach mhrv.log to your issue. Also include:
- Android version (Settings → About phone → Android version)
- OEM (Pixel / Samsung / Xiaomi / …)
- App version (tap the version badge in the top bar)
- What you did, what you expected, what happened
Settings → Apps → mhrv-rs → Uninstall.- Optional: remove the MITM CA —
Settings → Security → Encryption & credentials → User credentials → mhrv-rs MITM CA → Remove. (On OEMs where that path is buried, search Settings foruser credentials.) - The VPN profile is auto-revoked on uninstall — nothing to clean up there.