# Connect to local services
URL: /docs/android/local-services
LLM index: /llms.txt
Description: Route an Android app's connections to a mock backend, dev server, or VPN-only API on your machine.

# Connect an Android emulator to local services

`lim android tunnel` sends the connections you declare from the emulator through the machine that runs the tunnel, so the app keeps using the addresses it already has. This page covers Android emulators; the selector and inspection rules are shared with iOS and described once in [Connect an iOS simulator to local services](/docs/ios/local-services).

## Start a tunnel

Start the tunnel before you launch the app. Connections the app opened earlier keep their original route until they close. Declare each destination as a `--selector`:

```bash
lim android tunnel --selector localhost:8080 --id <instance-id>
```

Without `--detach`, the tunnel runs until you stop the command. Add `--detach` to keep it running in the background, and `--verbose` to log every forwarded connection and dial failure; with `--detach` those lines go to the tunnel log file that `tunnel status` points to.

Query or stop the tunnel from another process:

```bash
lim android tunnel status --id <instance-id>
lim android tunnel stop --id <instance-id>
```

From the TypeScript SDK, start the tunnel on a device client that was opened with the instance's `adbUrl` (`status.adbWebSocketUrl`). The caller owns the returned tunnel; disconnecting the device client does not close it.

```ts
import { createInstanceClient } from '@limrun/api';

const client = await createInstanceClient({
  apiUrl: instance.status.apiUrl!,
  adbUrl: instance.status.adbWebSocketUrl!,
  token: instance.status.token!,
});

const tunnel = await client.startTunnel({ selectors: ['localhost:8080', '*.corp.example'] });
const status = await client.getTunnelStatus();
await client.stopTunnel(tunnel.tunnelId);
```

## Choose selectors

Selectors take the same forms as on iOS: `localhost:port`, `IPv4:port`, `[IPv6]:port`, an exact domain, or a `*.` wildcard domain. The domain rules, the caps of ten exact selectors and 64 domain selectors, and the port 53 exclusion are described in [Choose selectors](/docs/ios/local-services#choose-selectors) and [Limits and behavior](/docs/ios/local-services#limits-and-behavior).

Two rules differ on Android:

- **Exact selectors need a port of 1024 or higher.** A lower port fails with `invalid tunnel route port <port>`.
- **Exact selectors are also reachable as `10.0.2.2:<port>`**, following the emulator convention for the host machine. An app configured for `10.0.2.2:8080` reaches `localhost:8080` on your machine.

Apps that resolve DNS themselves over HTTPS (DoH) bypass domain interception; declare the resolved IP instead.

## Inspect tunnel traffic

HTTP and HTTPS through the tunnel are inspected by default, with the same `--[no-]inspect`, `--har`, `--har-body-limit`, `--persist`, and `--ttl` flags as on iOS. See [Inspect tunnel traffic](/docs/ios/local-services#inspect-tunnel-traffic) for what each flag does and how inspection affects apps that pin certificates.

This keeps a persisted network log for seven days:

```bash
lim android tunnel --selector "*.api.example" --persist --ttl 604800 --id <instance-id>
```

## Use adb reverse instead

For exact ports, `adb reverse` over the [ADB tunnel](/docs/android/run-emulator#open-an-adb-tunnel) also works. It forwards a port on the emulator to a port on your machine, without domain interception or traffic inspection:

```bash
adb -s 127.0.0.1:<adb-port> reverse tcp:8080 tcp:8080
```

## Troubleshooting

| Symptom or message | Cause | Fix |
|---|---|---|
| `invalid tunnel route port <port>` | The exact selector uses a port below 1024, or port 53. | Run the local service on a port of 1024 or higher and declare that port. |
| The app ignores the tunnel | The app opened its connections before the tunnel started. | Start the tunnel first, then relaunch the app. |
| A domain selector has no effect | The app resolves DNS over HTTPS, or the wildcard does not cover the bare domain. | Declare the resolved IP as `host:port`, or add the exact domain next to the wildcard. |
| An app with certificate pinning fails through the tunnel | Inspection decodes HTTPS with a certificate the pinning app rejects. | Leave that host out of the selectors, or pass `--no-inspect`. |
| `adbUrl is required to manage a destination tunnel` | The TypeScript device client was created without `adbUrl`. | Pass `adbUrl: instance.status.adbWebSocketUrl` to `createInstanceClient`. |

## Next steps

<Columns cols={2}>
  <Card title="Run an emulator" icon="tablet-smartphone" href="/docs/android/run-emulator">
    Drive the app once it reaches your service: taps, typing, logs, recordings.
  </Card>
  <Card title="iOS local services" icon="network" href="/docs/ios/local-services">
    The shared selector rules, inspection flags, and the Expo and Metro setup.
  </Card>
</Columns>