Set up a Cloudflare tunnel to expose Metro (port 8081) over HTTPS for remote React Native / Expo dev. Covers macOS/Linux (quick + named tunnels via npm scripts) and Windows (system service with SYSTEM profile config). Use when the user wants to connect a phone to Metro over a tunnel, configure a stable hostname, add Metro to an existing cloudflared service, or troubleshoot tunnel routing issues.
Scanned 9/3/2026
Install to Claude Code
npx -y skills add RonenMars/threadbase-mobile --skill setup-cloudflared --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Setup Cloudflared?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/ronenmars-setup-cloudflared-threadbase-mobile)More formats (shields.io, HTML) on the badges page.
---
name: setup-cloudflared
description: Set up a Cloudflare tunnel to expose Metro (port 8081) over HTTPS for remote React Native / Expo dev. Covers macOS/Linux (quick + named tunnels via npm scripts) and Windows (system service with SYSTEM profile config). Use when the user wants to connect a phone to Metro over a tunnel, configure a stable hostname, add Metro to an existing cloudflared service, or troubleshoot tunnel routing issues.
---
# setup-cloudflared
Sets up a Cloudflare tunnel to expose Metro bundler (port 8081) over HTTPS so
a physical device can connect without sharing the same Wi-Fi.
## How it works
`EXPO_PACKAGER_PROXY_URL` is set at Metro startup. Expo advertises that URL
to the app instead of the local LAN IP. The app fetches its JS bundle through
the tunnel — device and dev machine no longer need to share a network.
**Key constraint:** tunnel and Metro must run on the **same machine** (the
tunnel proxies `localhost:8081`).
## Project config files
- `.cloudflared/config.example.yml` — committed template (like `.env.example`)
- `.cloudflared/config.yml` — gitignored, real values for your machine
Copy the example, fill in tunnel ID and hostname, then follow the platform
steps below.
---
## macOS / Linux
### Quick tunnel (no account, temporary URL)
`npm run dev:tunnel` handles everything automatically:
1. Spins up a dummy HTTP listener on 8081 (so cloudflared doesn't exit).
2. Launches cloudflared, waits for the `*.trycloudflare.com` URL.
3. Kills the dummy listener.
4. Starts Metro with `EXPO_PACKAGER_PROXY_URL` set.
```bash
npm run dev:tunnel # JS-only
npm run dev:tunnel -- -c # clear Metro cache
DEVICE_UDID=<udid> npm run dev:tunnel:native # full native rebuild
```
### Named tunnel (stable hostname)
```bash
CLOUDFLARED_TUNNEL_NAME=<name> npm run dev:tunnel
```
One-time DNS setup:
```bash
cloudflared tunnel create <name>
# Use the UUID returned, not the name, to avoid routing to the wrong tunnel:
cloudflared tunnel route dns <TUNNEL-ID> <your-hostname>
```
Place filled-in `config.yml` at `~/.cloudflared/config.yml`.
---
## Windows (PowerShell)
### The critical gotcha: SYSTEM profile config path
The cloudflared Windows service runs as **SYSTEM**. It reads its config from
the SYSTEM profile — **not** your user profile:
```
C:\Windows\system32\config\systemprofile\.cloudflared\config.yml
```
Editing `~\.cloudflared\config.yml` has **no effect** on the service.
Always edit the SYSTEM profile path above.
### Option A — add Metro to existing service (recommended)
Check if cloudflared is already running as a service:
```powershell
sc.exe query cloudflared
```
If it is, add Metro as an ingress route to the SYSTEM profile config:
```yaml
ingress:
# ... existing routes ...
- hostname: <your-metro-hostname>
service: http://localhost:8081
- service: http_status:404 # must stay last
```
Restart from **Administrator** PowerShell:
```powershell
Restart-Service cloudflared
```
Verify the tunnel reaches Metro:
```powershell
(Invoke-WebRequest -Uri "https://<your-metro-hostname>/status" -UseBasicParsing).Content
# Should return: packager-status:running
```
Start Metro:
```powershell
$env:EXPO_PACKAGER_PROXY_URL="https://<your-metro-hostname>"; npx expo start --lan
```
### Option B — install a dedicated service
From **Administrator** PowerShell:
```powershell
cloudflared --config "C:\path\to\repo\.cloudflared\config.example.yml" service install
```
### Option C — run manually (two terminals)
Terminal 1:
```powershell
cloudflared tunnel --config "$env:USERPROFILE\.cloudflared\config.yml" run
```
Terminal 2:
```powershell
$env:EXPO_PACKAGER_PROXY_URL="https://<your-metro-hostname>"; npx expo start --lan
```
Note: when setting env vars, `$env:VAR=true` (unquoted) fails — PowerShell tries to execute `true` as a command. Quote the value.
### Kill Metro on Windows
`npm run kill:metro` uses a bash script and won't work on Windows:
```powershell
$pid = (netstat -ano | Select-String ":8081.*LISTENING" | ForEach-Object { ($_ -split '\s+')[-1] } | Select-Object -First 1)
if ($pid) { taskkill /PID $pid /F }
```
---
## Connecting from the dev-client app
Once Metro is running with `EXPO_PACKAGER_PROXY_URL` set, connect from the
Threadbase dev-client app using the deep link:
```
exp+threadbase://expo-development-client/?url=https%3A%2F%2F<your-hostname>
```
Or tap **Enter URL manually** and enter:
```
https://<your-hostname>
```
> **Note:** The scheme is `exp+threadbase` (from `scheme` in `app.json`), not
> `exp+threadbase-mobile` (the slug). Expo CLI may print the slug-based URL —
> if the app doesn't open, use the scheme-based URL above.
**Initial bundle timing:** the "Could not connect" error can appear if the
phone connects before Metro finishes its first bundle. Wait for Metro to print
`Bundled`, then tap **Reload JS**.
---
## Troubleshooting
| Symptom | Fix |
|---|---|
| Tunnel returns 404 | Check you edited the SYSTEM profile config (not `~\.cloudflared\`), then `Restart-Service cloudflared` |
| `packager-status:running` locally but tunnel 404s | Service didn't restart after config change — run `Restart-Service cloudflared` as Administrator |
| `cloudflared` exits immediately (macOS/Linux) | Port 8081 not open yet; `npm run dev:tunnel` handles this automatically |
| "Failed to get tunnel URL after 30s" | Port 8081 in use — kill Metro first |
| Named tunnel routes to wrong tunnel | `route dns <name>` can silently pick up an existing CNAME; always use the UUID: `cloudflared tunnel route dns <TUNNEL-ID> <hostname>` |
| Service install fails silently | Requires Administrator — re-run from Admin terminal |
| `$env:VAR=true` gives error on PowerShell | Always quote values: `$env:VAR="true"` |
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!