Intro
React Native networking problems often masquerade as app bugs when the real culprits are DNS, ports, routing, firewalls, proxies, or TLS. The fastest path to a fix is a disciplined, measurable approach: start with a narrow health check, verify from the device runtime you actually use (Android emulator, iOS simulator, or physical device), then expand only after you have a green baseline.
This guide is practitioner-focused. You will inventory versions and topology, apply safe and reversible configuration, run concrete diagnostics with expected results, map common failures to fixes, and use a checklist you can repeat across projects. The same playbook works whether you build with bare React Native or Expo, and whether your backend is REST, GraphQL, or another API.
Version and Environment Inventory
Before you change settings, capture the exact environment. This avoids chasing differences between machines and makes issues reproducible.
Record app and tooling versions
node -v
npm -v
# or if using yarn or pnpm
yarn -v
pnpm -v
npx react-native info
adb version
emulator -version
xcodebuild -version
xcrun simctl list devices
Capture device/runtime context
- Are you using Android Emulator, iOS Simulator, Expo Go, or a physical device?
- Is the device on the same LAN as your development machine?
- Is a VPN or corporate proxy enabled?
Capture backend endpoints and ports
- Exact URLs including scheme and port, for example:
https://api.example.local:8443/healthzhttp://192.168.1.50:3000/ping- Any DNS search domains or split-horizon DNS you rely on.
Snapshot network configuration on your development machine
macOS:
scutil --dns
networksetup -getwebproxy Wi-Fi
ifconfig
Linux:
resolvectl status || systemd-resolve --status
ip addr
ip route
Windows (PowerShell):
Get-DnsClientServerAddress
Get-NetIPConfiguration
netsh winhttp show proxy
Pick one simple health endpoint and one runtime (for example, Android emulator). Aim to get a successful 200 OK HEAD or GET request first. Expand to other runtimes only after that is verified. A narrow, measurable pilot is easier to inspect locally and avoids compounding variables.
Choose a minimal pilot target
Safe Configuration Path
Use the correct hostnames and reversible settings. The table below lists the right way to reach your development machine from different runtimes.
| Runtime | Host to reach your dev machine | Optional port mapping | Notes |
|---|---|---|---|
| Android Emulator (AVD) | http://10.0.2.2 | adb reverse tcp:3000 tcp:3000 | 10.0.2.2 maps to host loopback. Reverse mapping lets a physical device hit host ports. |
| Android Device (USB) | http://127.0.0.1 with adb reverse, or http://<LAN_IP> | adb reverse tcp:3000 tcp:3000 | Without reverse, use your machine LAN IP on same network. |
| iOS Simulator | http://localhost | Not required | Simulator shares host network stack. |
| iOS Device (Wi-Fi) | http://<LAN_IP> | Not available | Ensure device and machine are on same LAN. |
| Expo Go (Android) | http://10.0.2.2 (emulator) or http://<LAN_IP> (device) | adb reverse optional | Expo Go uses device networking; the same rules apply. |
Key safe patterns (development only)
Android dev port mapping with adb reverse
adb reverse tcp:3000 tcp:3000
# remove mappings when done
adb reverse --remove-all
If your dev API is http (not https), add a debug-only network security config and reference it from debug manifest. Example: android/app/src/debug/res/xml/network_security_config.xml
Dev-only cleartext allowance (Android)
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">192.168.1.50</domain>
</domain-config>
</network-security-config>
In android/app/src/debug/AndroidManifest.xml:
<application
android:networkSecurityConfig="@xml/network_security_config"
...>
</application>
Remove for release builds.
For http endpoints during development only, add to ios/<App>/Info.plist under a debug configuration:
Dev-only ATS relaxation (iOS)
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Remove before shipping. Prefer HTTPS end to end.
Prefer trusting a local CA certificate at the OS level. On Android 7+, if you need the app to trust user-installed CAs during dev, declare a debug-only network security config that trusts user certificates. On iOS, install the CA profile and mark it as trusted for SSL.
Self-signed TLS in development
CORS is a browser security model; React Native network stacks do not enforce it the same way. If your server blocks requests due to Origin or custom headers, it is a server-side policy, not a client runtime limitation.
Do not assume CORS in React Native
Some mobile networks prefer IPv6. Use explicit hostnames and ensure your DNS returns records your service supports. If connecting by literal IP, prefer IPv4 addresses when your service is IPv4-only.
IPv4 vs IPv6
Verification and Diagnostics
Follow these steps in order. Stop when you get a green result; then widen scope to the next runtime or endpoint.
1. Confirm app-level connectivity with NetInfo
import NetInfo from "@react-native-community/netinfo\);n
NetInfo.fetch().then(state => {
console.log("isConnected:" state.isConnected);
console.log("isInternetReachable:" state.isInternetReachable);
});
Expected result: isConnected true and isInternetReachable true on a working network. If false, resolve device connectivity (Wi-Fi, cellular, captive portal) before continuing.
2. Test a simple HEAD request from the app
Use a tiny endpoint like /healthz that does no heavy work.
// Example: robust fetch with timeout via AbortController
const withTimeout = (ms, controller) => setTimeout(() => controller.abort(), ms);
async function checkHealth(url) {
const controller = new AbortController();
const timer = withTimeout(5000, controller);
try {
const res = await fetch(url, { method: 'HEAD', signal: controller.signal });
console.log('status', res.status);
return res.ok;
} catch (e) {
console.log('error', String(e));
return false;
} finally {
clearTimeout(timer);
}
}
// Choose the right host: see table above
checkHealth('http://10.0.2.2:3000/healthz');
Expected result: status 200. If error includes TypeError: Network request failed, continue below.
3. Isolate DNS vs transport
From your development machine, resolve and connect to the exact hostname used in the app.
macOS/Linux:
dig +short api.example.local
nc -vz api.example.local 3000
Windows (PowerShell):
Resolve-DnsName api.example.local
Test-NetConnection api.example.local -Port 3000
Expected result: DNS returns at least one address; port test succeeds. If DNS fails but IP works, update your app config to use a reachable host (LAN IP for dev) or fix DNS split-horizon rules.
4. Verify port reachability from device runtime
- Android emulator inherits host reachability to 10.0.2.2. If you connect to a LAN IP instead, ensure the host firewall allows inbound on that port.
- Physical Android device: prefer
adb reversefor local development, or open firewall to your LAN IP and test viahttp://<LAN_IP>. - iOS simulator uses host networking; localhost works.
- Physical iOS device: ensure same Wi-Fi and open firewall.
Host firewall checks
macOS (PF):
sudo pfctl -s rules
lsof -iTCP -sTCP:LISTEN -P | grep 3000
Linux (UFW or iptables):
sudo ufw status verbose
sudo ss -ltnp | grep :3000
Windows:
Get-NetFirewallProfile
Get-NetFirewallRule | Select-String -Pattern 3000
netstat -ano | findstr :3000
Expected result: your server process is listening, and firewall permits inbound on the correct interface.
5. Check routing and proxies
If a system proxy is configured, mobile runtimes may honor it.
macOS:
networksetup -getwebproxy Wi-Fi
Windows:
netsh winhttp show proxy
Linux:
env | egrep 'https?_proxy|HTTPS?_PROXY'
Expected result: either no unintended proxy, or proxy rules that allow your requests.
6. Observe logs on failures
Android:
adb logcat | egrep -i 'okhttp|ssl|connect|react|fatal'
Look for SSLHandshakeException, UnknownHostException, or ConnectException.
iOS: open Console.app, filter on your process, or view Xcode logs. Look for NSURLErrorDomain codes such as -1003 (cannot find host), -1200 (TLS failure), -1004 (cannot connect to host), -1001 (timeout).
7. TLS verification
Use the full https URL in the app and confirm the certificate chain is valid for the device. If using a local CA for dev, ensure the CA is trusted by the device and, on Android, allowed by your debug network security config.
8. Retry and backoff (client resilience)
Add bounded retries for transient network issues.
async function retry(fn, retries = 2, base = 300) {
let attempt = 0;
while (true) {
try { return await fn(); }
catch (e) {
if (attempt++ >= retries) throw e;
const delay = base * Math.pow(2, attempt - 1);
await new Promise(r => setTimeout(r, delay));
}
}
}
await retry(() => checkHealth('http://10.0.2.2:3000/healthz'));
Expected result: transient failures recover within a few attempts. Persistent failures should still surface clearly.
Failure Modes and Recovery
Use this mapping to move quickly from symptom to fix.
| Symptom or error | Probable cause | Targeted fix |
|---|---|---|
ERR_NAME_NOT_RESOLVED, UnknownHostException, NSURLErrorDomain -1003 | DNS does not resolve from device runtime | Use the correct host for runtime (10.0.2.2, localhost, or LAN IP). Fix DNS or use LAN IP in dev. |
ECONNREFUSED, cannot connect, connection refused | Port closed or server not listening | Start server, verify listen address 0.0.0.0, open firewall, confirm port with netstat/ss. |
Timeout or NSURLErrorDomain -1001 | Firewall, proxy, or routing drop; server slow | Bypass proxy, open firewall, test nc/Test-NetConnection, add server health endpoint. |
SSLHandshakeException, NSURLErrorDomain -1200 | Certificate invalid or untrusted CA | Use HTTPS with valid cert, trust local CA on device, add debug-only trust config on Android. |
| Works in simulator, fails on device | Wrong host (localhost) or LAN isolation | Use LAN IP on devices; ensure same Wi-Fi; consider adb reverse for Android USB. |
| IPv6-only network issues | Service only reachable via IPv4 | Ensure DNS returns AAAA or use dual-stack; avoid hardcoded IPv4-literal URLs when on IPv6-only networks. |
| POST/PUT fail, GET works | Server rejects headers/body size | Inspect server logs; adjust Content-Type, body, and server limits. |
| Belief that CORS blocks requests | RN not enforcing browser CORS | Fix server policy if it blocks Origin; it is not a React Native client restriction. |
Recovery and rollback
- Remove adb reverse mappings when done:
adb reverse --remove-all
- Revert debug-only network relaxations (Android manifest, iOS ATS) before any release build.
- If you changed OS firewall rules, document and revert to the team baseline once development testing is complete.
- If you installed a local CA on devices for dev, remove it from devices not used for development.
Practical examples
Switch localhost to the right host per runtime
Android emulator:
const API = 'http://10.0.2.2:3000';
iOS simulator:
const API = 'http://localhost:3000';
Physical device on LAN (replace with actual IP):
const API = 'http://192.168.1.50:3000';
File upload with explicit timeout and error surface
async function uploadImage(uri) {
const data = new FormData();
data.append('file', { uri, type: 'image/jpeg', name: 'photo.jpg' });
const controller = new AbortController();
const t = setTimeout(() => controller.abort(), 15000);
try {
const res = await fetch(`${API}/upload`, {
method: 'POST',
body: data,
headers: { 'Accept': 'application/json' },
signal: controller.signal,
});
if (!res.ok) throw new Error(`Upload failed ${res.status}`);
return await res.json();
} finally {
clearTimeout(t);
}
}
Expected result: 200 or 201 response with JSON body; on abort, surface a clear error so users can retry.
Connectivity guardrail before calling APIs
async function guardedFetch(url, init) {
const s = await NetInfo.fetch();
if (!s.isConnected || !s.isInternetReachable) {
throw new Error('No internet connection');
}
return fetch(url, init);
}
This avoids spamming your backend when the device is offline.
Quick command library
Use these commands to test DNS and port reachability from your development machine.
| OS | DNS check | Port check | Notes |
|---|---|---|---|
| macOS | dig +short host.example | nc -vz host.example 3000 | Also use scutil --dns for resolver details. |
| Linux | getent hosts host.example | nc -vz host.example 3000 | resolvectl status for per-link DNS. |
| Windows | Resolve-DnsName host.example | Test-NetConnection host.example -Port 3000 | Add -InformationLevel Detailed for more output. |
Expected results
- DNS check returns at least one IP.
- Port check reports succeeded; otherwise, investigate firewall, listen address, or routing.
Operations Checklist
Use this repeatable list on every React Native networking issue.
- Inventory
- Record RN, OS, device/emulator, and backend URLs and ports.
- Note VPN/proxy and firewall status.
- Pick a pilot
- Choose one runtime (e.g., Android emulator) and one health endpoint.
- Configure safely
- Use correct host (10.0.2.2, localhost, or LAN IP).
- Optionally use adb reverse for Android development.
- Add dev-only network relaxations if absolutely required; document them.
- Verify stepwise
- Check NetInfo connectivity from the app.
- Make a HEAD request with a 5s timeout; expect 200.
- From your machine, resolve DNS and test the port.
- Confirm server is listening and firewall allows inbound.
- Observe logs
- Android:
adb logcatfor okhttp/ssl/connect errors. - iOS: Xcode or Console for NSURLErrorDomain codes.
- Fix by failure mode
- Wrong host: use runtime-appropriate host.
- Port closed: start server, listen on 0.0.0.0, open firewall.
- DNS breaks: use LAN IP in dev or fix DNS.
- TLS breaks: trust dev CA or use valid certs; remove relaxations later.
- Rollback
- Remove adb reverse mappings.
- Remove debug-only network exceptions.
- Revert firewall changes to team baseline.
Conclusion
React Native networking becomes predictable when you treat it as a sequence of observable steps: inventory, pick a narrow pilot, apply safe and reversible configuration, verify from the actual runtime, and expand only after you get a clean green check. With correct host selection, clear DNS and port tests, and focused logging, you can isolate issues in minutes instead of days. Keep development relaxations strictly debug-only, document every change, and roll them back as soon as your checks pass. This approach scales from individual developers to startup teams and consultant engagements while keeping risk low and outcomes measurable.