Use a Horizon tunnel with Expo
Point an Expo or React Native app on a phone at a local API through a Horizon tunnel, with an EXPO_PUBLIC_ environment variable.
Call your local API from an Expo app running on a real phone, with an HTTPS URL that reaches your machine.
Before you begin
- Node.js 18 or later
- A Horizon account and the CLI (see Getting started)
- A reserved subdomain for
-s. Reserve one on the Subdomains page. - An Expo app, and a local API that listens on port 3000
How this differs from expo start --tunnel
The two tools tunnel different servers.
npx expo start --tunnelis for Expo's development server (Metro, port 8081). Expo says it uses ngrok and needs@expo/ngrok. It lets a device load your app bundle from anywhere.- A Horizon tunnel points at any local HTTP server. This page points it at your API, so the app can call that API from a phone.
You can use both: --tunnel to load the app, Horizon for the API. This page doesn't cover tunneling Metro with Horizon.
Start your API
Start your API on port 3000. Any HTTP server works.
Start a tunnel
Use -s with a subdomain you reserved. Reserved subdomains are a paid feature, see Pricing.
hrzn tunnel http://localhost:3000 -s my-apiHORIZON: Tunnel connected
URL https://my-api.hrzn.run (reserved)
Forwarding http://localhost:3000
Request log https://hrzn.run/dashboard/tunnels/my-apiKeep this terminal open.
Point the app at the tunnel
Expo loads variables with the EXPO_PUBLIC_ prefix from .env files and inlines them into your JavaScript.
EXPO_PUBLIC_API_URL=https://my-api.hrzn.runRead it where you call the API.
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
export async function getHealth() {
const response = await fetch(`${apiUrl}/health`);
return response.json();
}Don't put secrets in EXPO_PUBLIC_ variables. Expo says they are visible in plain text in your compiled app. After you edit .env, do a full reload in Expo Go or your development build.
Check it works
Trigger the call in the app. The Horizon terminal prints a line for it:
GET 200 /healthTroubleshooting
Network request failed
React Native's fetch prints Network request failed when it can't reach the URL. Open https://my-api.hrzn.run/health in the phone's browser. If that fails, check that the Horizon terminal is running and that EXPO_PUBLIC_API_URL has the right subdomain.
The app still calls the old URL
Expo inlines EXPO_PUBLIC_ values into your code. Do a full reload in Expo Go or your development build after you change .env.
The URL changed after a restart
You started the tunnel without -s, so Horizon gave you a new random subdomain. Restart with -s my-api and the .env value stays valid.
Next steps
- Read Expo's guide to environment variables and the Expo CLI reference.
Expose a local Express app
Expose a local Express app through a Horizon tunnel, and make req.hostname and req.protocol use the public URL with trust proxy.
Share a Django dev server
Share a Django dev server with a Horizon tunnel, and fix the "CSRF verification failed" error on POST forms with CSRF_TRUSTED_ORIGINS.