You're developing a React Native app using React Native CLI, testing on a physical device, and trying to connect to your QA server (like https://qa.example.com/api/). You keep getting this frustrating error:
Network Error
message: "Network Error"
This happens because your QA server likely uses a self-signed certificate or an untrusted/invalid SSL certificate, and both iOS and Android block these connections by default for security reasons.
Understanding Why This Happens
Modern mobile operating systems (Android 7+ and iOS 9+) enforce strict transport security:
- Android: Starting from Android 7 (Nougat, API 24), apps no longer trust user-installed certificates by default
- iOS: App Transport Security (ATS) requires all connections to use HTTPS with valid certificates
When your QA environment uses a self-signed certificate or a certificate from a non-public Certificate Authority (CA), your app refuses the connection - hence the "Network Error".
β Verified Solutions
Based on official Android and iOS documentation, here are the proven solutions that actually work:
Solution 1: Android - Network Security Configuration
This is the recommended approach for Android. It allows you to specify which certificates your app should trust without compromising security.
Step 1: Create Network Security Config
Create this file: android/app/src/main/res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- Configuration for your QA server -->
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">qa.example.com</domain>
<trust-anchors>
<!-- Trust system certificates -->
<certificates src="system" />
<!-- Trust user-installed certificates -->
<certificates src="user" />
</trust-anchors>
</domain-config>
</network-security-config>
What this does:
- Allows HTTPS connections to
qa.example.comand all its subdomains - Trusts both system certificates AND user-installed certificates
- Keeps security enabled (
cleartextTrafficPermitted="false")
Step 2: Reference it in AndroidManifest.xml
Update android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yourapp">
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
...>
<!-- Your other application settings -->
</application>
</manifest>
Alternative: Trust All User Certificates (Easier but Less Secure)
If you want to trust user certificates globally (not recommended for production):
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
Solution 2: iOS - App Transport Security Exceptions
For iOS, you need to add exceptions to your Info.plist file.
Step 1: Locate Your Info.plist
Find the file: ios/YourAppName/Info.plist
Step 2: Add ATS Exception
Add this configuration to allow insecure connections to your QA server:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>qa.example.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
What this does:
- Allows insecure connections specifically to
qa.example.com - Includes all subdomains
- Keeps ATS enabled for all other domains (secure by default)
Alternative: Disable ATS Globally (Not Recommended)
If you need to disable ATS entirely (only for development):
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
β οΈ Warning: Apple may reject apps that use NSAllowsArbitraryLoads without proper justification.
π§ Complete Implementation Example
Here's your complete setup for connecting to https://qa.example.com/api/:
1. API Service Configuration
// src/services/apiService.js
import axios from 'axios';
const API_BASE_URL = 'https://qa.example.com/api';
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor
api.interceptors.request.use(
(config) => {
console.log('Making request to:', config.url);
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Response interceptor
api.interceptors.response.use(
(response) => {
return response;
},
(error) => {
console.error('API Error:', {
message: error.message,
code: error.code,
response: error.response?.data,
status: error.response?.status
});
return Promise.reject(error);
}
);
export default api;
2. Login Screen Example
// src/screens/LoginScreen.js
import React, { useState } from 'react';
import { View, TextInput, Button, Alert } from 'react-native';
import api from '../services/apiService';
const LoginScreen = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const handleLogin = async () => {
try {
setLoading(true);
const response = await api.post('/login', {
email,
password
});
console.log('Login successful:', response.data);
Alert.alert('Success', 'Login successful!');
// Handle successful login (save token, navigate, etc.)
} catch (error) {
console.error('Login error:', error);
Alert.alert(
'Login Failed',
error.response?.data?.message || 'Network error occurred'
);
} finally {
setLoading(false);
}
};
return (
<View style={{ padding: 20 }}>
<TextInput
placeholder="Email"
value={email}
onChangeText={setEmail}
style={{ borderWidth: 1, marginBottom: 10, padding: 10 }}
/>
<TextInput
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
style={{ borderWidth: 1, marginBottom: 10, padding: 10 }}
/>
<Button
title={loading ? "Logging in..." : "Login"}
onPress={handleLogin}
disabled={loading}
/>
</View>
);
};
export default LoginScreen;
π± Rebuild and Test
After making these changes, you must rebuild your app:
# For Android
npx react-native run-android
# For iOS
cd ios && pod install && cd ..
npx react-native run-ios
π Debugging Tips
If you're still experiencing issues, try these debugging steps:
1. Check the Certificate
Verify your QA server's certificate:
# Check certificate details
openssl s_client -connect qa.example.com:443 -showcerts
# Get certificate
echo | openssl s_client -servername qa.example.com -connect qa.example.com:443 2>/dev/null | openssl x509 -text
2. Test the Endpoint
Use curl to test if the endpoint is accessible:
curl -v https://qa.example.com/api/health
3. Enable Detailed Logging
Add detailed error logging to your API calls:
catch (error) {
console.log('=== Full Error Details ===');
console.log('Message:', error.message);
console.log('Code:', error.code);
console.log('Config:', error.config);
console.log('Response Data:', error.response?.data);
console.log('Response Status:', error.response?.status);
console.log('Response Headers:', error.response?.headers);
console.log('========================');
}
4. Check Network Connectivity
Ensure your device can reach the QA server:
# On your computer, check if QA server is reachable
ping qa.example.com
# Check if HTTPS port is open
telnet qa.example.com 443
π Security Best Practices
For Development/QA:
β DO:
- Use domain-specific exceptions (not global)
- Document why each exception is necessary
- Keep exceptions to minimum required domains
- Use
includeSubdomainscarefully
β DON'T:
- Disable ATS/Network Security completely
- Use these configurations in production builds
- Trust all certificates globally
- Commit sensitive certificates to version control
Before Production:
- Remove all development exceptions
- Get a proper SSL certificate for production (Let's Encrypt, DigiCert, etc.)
- Test on real HTTPS endpoints
- Use SSL pinning for sensitive apps
// For production, consider using react-native-ssl-pinning
import { fetch } from 'react-native-ssl-pinning';
fetch('https://your-production-api.com/endpoint', {
method: 'POST',
sslPinning: {
certs: ['production-cert'] // Certificate in android/app/src/main/assets/
},
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ data: 'value' })
});
π¦ Alternative: Use SSL Pinning Library
For more robust SSL handling, especially in production, consider using a dedicated library:
npm install react-native-ssl-pinning
Then implement certificate pinning:
import { fetch } from 'react-native-ssl-pinning';
const response = await fetch('https://qa.example.com/api/login', {
method: 'POST',
timeoutInterval: 10000,
sslPinning: {
certs: ['qa-cert'] // Place qa-cert.cer in android/app/src/main/assets/
},
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password })
});
π― Summary Checklist
For Android:
- Create
network_security_config.xmlinres/xml/ - Add domain configuration with trust anchors
- Reference config in
AndroidManifest.xml - Rebuild app with
npx react-native run-android
For iOS:
- Open
Info.plistin Xcode or text editor - Add
NSAppTransportSecurityexceptions - Specify your QA domain in
NSExceptionDomains - Run
pod installin ios folder - Rebuild app with
npx react-native run-ios
For Both:
- Verify API base URL is correct
- Test network connectivity to QA server
- Check device logs for detailed errors
- Document why exceptions are needed
- Plan to remove exceptions before production
π Official Documentation References
- Android Network Security Configuration
- iOS App Transport Security
- React Native SSL Pinning
- Android Developer Blog - Certificate Changes
π‘ Common Issues & Solutions
Issue: Still getting "Network Error" after configuration
Solution: Make sure you've rebuilt the app completely. Sometimes a clean build is needed:
# Android
cd android && ./gradlew clean && cd ..
npx react-native run-android
# iOS
cd ios && pod deintegrate && pod install && cd ..
npx react-native run-ios
Issue: Works on Android but not iOS
Solution: iOS requires more specific ATS exceptions. Make sure you've added the domain to NSExceptionDomains and set NSExceptionAllowsInsecureHTTPLoads to true.
Issue: Certificate verification fails
Solution: Install the QA certificate on your device manually:
- Android: Settings β Security β Install from storage
- iOS: Email certificate β Install β Trust in Settings
Issue: Works in development but fails in release build
Solution: Release builds may strip debugging configurations. Ensure your Network Security Config and Info.plist exceptions are properly included in release builds.
π Conclusion
The "Network Error" when connecting to HTTPS QA servers in React Native is a security feature, not a bug. By properly configuring Network Security Config for Android and App Transport Security for iOS, you can safely connect to your QA environment during development while maintaining security for other connections.
Remember: These configurations are for development and testing only. Always use proper SSL certificates in production and remove these exceptions before shipping your app to users.
Questions or issues? Drop a comment below or check the official documentation links above.
Found this helpful? Share it with your fellow React Native developers! π
Top comments (0)