# https://tunnels.io/learn/how-to-test-a-mobile-app-against-localhost

[Home](https://tunnels.io/) / [Learn](https://tunnels.io/learn) / How to test a mobile app against localhost

Learn

# How do you test a mobile app against localhost?

Your phone's localhost is the phone itself, not your laptop, so the URL resolves to the wrong machine. Point the app at your laptop's LAN IP address on the same Wi-Fi, use the emulator's special host address, or run a tunnel that gives your local server a public HTTPS URL any device can reach.

## Why can my phone not reach localhost?

Because `localhost`` means the device you are on. When your app opens `http://localhost:3000``, the request goes to the loopback interface of the handset, and the handset is not running your API. The name is not shared across devices; it resolves to a different computer on every one of them.

Swapping in your laptop's real address fixes the name, and then two other things commonly block the connection. A server bound only to 127.0.0.1 refuses anything that arrives from another machine, so it has to listen on 0.0.0.0 first, and your laptop's firewall may drop the inbound connection before the server sees it. Beyond that is the network itself: NAT on your router means nothing on the internet can open a connection into your laptop, so a phone on cellular data cannot reach it at any address, and guest Wi-Fi with client isolation blocks phone to laptop traffic even when both are on the same access point.

## Option 1: can I just use my machine's LAN IP?

Yes, when the phone is on the same network. Bind the server to 0.0.0.0 so it accepts connections on every interface, read your laptop's Wi-Fi address with `ipconfig getifaddr en0`` on macOS, `ip addr`` on Linux or `ipconfig`` on Windows, then point the app at something like `http://192.168.1.24:3000``.

This is the shortest path and it has three limits worth knowing before you rely on it. The address comes from DHCP, so it changes when you rejoin the network and changes completely at a coffee shop or a client office. It only works while both devices sit on the same LAN, which rules out cellular testing and most guest networks. And it is plain HTTP, which both mobile platforms block by default.

## Option 2: how does a tunnel solve this?

A tunnel client runs on your laptop and opens an outbound connection to a public server. That server publishes an HTTPS URL and relays every request it receives back down the same connection to your local port. Because the phone is talking to an ordinary public hostname, it does not need to be on your Wi-Fi, and the same URL works over cellular, through a corporate network, or on a device in a colleague's hands in another country.

The URL is HTTPS with a publicly trusted certificate, which quietly removes the platform cleartext problem described below. What you pay for that is a round trip through the relay, so latency is higher than a hop across your own LAN, and traffic leaves your machine. [How to expose localhost](https://tunnels.io/learn/how-to-expose-localhost) puts tunnels next to the other approaches with the trade-offs of each.

## Option 3: what are the emulator and simulator addresses?

An emulator is not a phone, and it normally has a reserved address for the host machine. The Android emulator maps `10.0.2.2`` to the host's loopback interface, so `http://10.0.2.2:3000`` reaches a server listening on localhost:3000 on your laptop, while `10.0.2.15`` is the emulated device's own address rather than yours. Genymotion uses `10.0.3.2`` for the same job, and the iOS Simulator shares the Mac's network stack, so plain localhost works there with no change at all.

There is one more trick for a physical Android device attached to adb. `adb reverse tcp:3000 tcp:3000`` forwards the device's own localhost:3000 back to port 3000 on your laptop over the debug connection, so the app can keep using localhost and skip the network entirely. There is no equivalent for a physical iPhone.

## What about HTTPS on iOS and Android?

Both platforms block cleartext HTTP by default, so a plain http:// URL to a LAN address often fails before it reaches the network. Apps targeting Android 9 or newer need a network security configuration that permits cleartext for your development host, and iOS needs an App Transport Security exception, where NSAllowsLocalNetworking covers local addresses without disabling ATS across the whole app. The alternative is serving your own certificate on the LAN address, which means installing a CA on the device and, on iOS, enabling it manually under Certificate Trust Settings.

A tunnel avoids all of that, because the URL is already HTTPS with a certificate the device trusts out of the box. That also matters for anything that will not talk to an insecure origin at all, such as an OAuth redirect or a payment provider callback. [HTTP vs HTTPS tunnels](https://tunnels.io/learn/http-vs-https-tunnels) explains where the TLS is terminated.

## Will certificate pinning still break?

Yes, and it should. Pinning compares the server's certificate or public key against a value compiled into the app, so any host that is not the pinned one fails validation even when its certificate is perfectly valid. A tunnel does not and must not defeat that check. The normal fix is a debug build whose pin set is empty or includes the development host, kept strictly out of the release configuration: on Android that is a `debug-overrides`` block in the network security config, and on iOS it is whatever your pinning library exposes for debug builds.

## Which option should you use?

On an emulator or the simulator, use the host address; nothing else is needed. On a real handset sitting on your own Wi-Fi for quick UI work, the LAN IP is the fastest thing to set up. Once the device is on cellular, in someone else's hands, needs real HTTPS, or has to receive a callback from a third party, a tunnel is the only one of the three that works.

| Approach | What the app points at | Works off your network | Trusted HTTPS |
| --- | --- | --- | --- |
| LAN IP address | `http://192.168.1.24:3000` | No, same Wi-Fi only | No, unless you install your own CA |
| Emulator host address | `http://10.0.2.2:3000` | Not applicable, it runs on your machine | No, unless you install your own CA |
| adb reverse | `http://localhost:3000` | Android only, needs the adb connection | No, unless you install your own CA |
| Tunnel | `https://your-tunnel-url` | Yes, including cellular | Yes, terminated for you |

## Related reading

[Real-device testing Remote testers, cellular-only devices and device farms, where nothing on your own network can help.](https://tunnels.io/use-cases/mobile-testing) [localhost vs 127.0.0.1 Why a hostname and a loopback address are not the same thing, and what binding to 0.0.0.0 actually changes.](https://tunnels.io/learn/localhost-vs-127-0-0-1) [How to expose localhost to the internet Tunnel, SSH forwarding, router port forwarding or deploying it, with the honest trade-offs of each.](https://tunnels.io/learn/how-to-expose-localhost)

Everything else is indexed on [Learn](https://tunnels.io/learn).

## How do you test on a real device with tunnels.io?

Install the client, authenticate, and run one command against the port your API already listens on. You get a public HTTPS URL, and that URL goes into the app's base URL configuration in place of an IP address. The handset can be on cellular, on hotel Wi-Fi, or in a tester's pocket on another continent, and the request still lands on the server running on your laptop. There is no interstitial page on any plan, including the free one, so your app receives your API response rather than a warning screen that a native HTTP client cannot click through.

The free Basic plan is $0, includes 4 GB of transfer a month and one tunnel, and issues a random URL, which is enough to check a build on a real handset. Pro is $4 a month with 128 GB and five tunnels, and adds up to three reserved subdomains, so the base URL baked into your debug build stops changing between runs. Pro also unlocks TCP tunnels for services that are not HTTP; the engine carries HTTP, HTTPS and TCP, and does not carry UDP.

[Get started →](https://tunnels.io/get-started)

## Common questions

### Why does 10.0.2.2 work in the Android emulator but not on my phone?

Because 10.0.2.2 is an alias the emulator itself provides for the host machine's loopback interface. It only exists inside that emulated network. A physical handset has no such alias, so it needs your laptop's LAN IP address, an adb reverse forward, or a tunnel URL instead.

### Can I test against my local API while the phone is on cellular data?

Not with a LAN address, because NAT on your router means nothing on the internet can open a connection into your laptop. A tunnel is the usual answer, since the phone connects to a public hostname and the relay carries traffic back to your machine. For a physical Android device, adb reverse over the debug cable also works no matter which network the phone is using.

### Do I need HTTPS to test a mobile app against localhost?

In practice yes, on both platforms. Android blocks cleartext by default for apps targeting API 28 and above, and iOS App Transport Security blocks it unless you add an exception such as NSAllowsLocalNetworking. You can add those exceptions to a debug build, or point the app at a tunnel URL that is already HTTPS with a publicly trusted certificate.

### Will a tunnel break certificate pinning in my app?

Yes, if the app pins to your production certificate or public key, and that is the pin working correctly. The fix is a debug build variant that relaxes or replaces the pin set for the development host, never a change to the release build. On Android that is a debug-overrides block in the network security config.

### My phone reaches the LAN IP but the connection is refused. What is wrong?

Usually the server is still bound to 127.0.0.1, which refuses connections arriving from any other machine, so bind it to 0.0.0.0 and try again. If that is already correct, check your laptop's firewall for inbound rules on that port, and check whether the Wi-Fi network isolates clients from each other, which is common on guest and public networks.
