Expo App HTTP Fails on Android 9: Cleartext, Not the VPN
An Expo build calling a plain HTTP API fails on Android 9 and later. The tunnel is innocent: the cleartext flag belongs in the build config.
You shipped a new build, moved to the VPN, and the same screen stopped loading. On Android 9 and later the more common explanation is duller: the platform refuses plain HTTP for apps targeting API 28 or higher, and the VPN only changed enough context to expose it. The error you get depends on the HTTP client, which is why it so rarely says cleartext.
What the failure actually is
From Android 9 onward the platform blocks cleartext by default once an app targets API level 28 or above. The request to a plain HTTP address is rejected by the platform before a single packet leaves the device. Which error you see depends on the HTTP library: sometimes a generic network failure, sometimes a timeout, sometimes a message that names cleartext outright. What it never does is reach your server. Android documents the default and the domain-scoped alternative in its security configuration guide.
A browser on the same device can still load the page, which is the clue most people miss. Browsers are not subject to your app's network security policy.
Why the VPN gets blamed
Three things usually change at once. The app may resolve a different hostname after the VPN connects, the device may be a new one with a newer build, or the build itself may have moved to a target API level that triggers the default. The VPN is visible and the target SDK is not, so the VPN gets the ticket.
Compare the old and new builds' target levels before touching network configuration. An app that targets 27 or lower is exempt, which explains every it-works-on-my-older-phone report.
Where the Expo flag actually lands
In a managed Expo project, adding a setting to app.json does not edit an Android manifest, because prebuild and development builds are what produce the manifest. A config plugin is the thing that writes the setting at that stage. The supported route is expo-build-properties, configured in the app config with the cleartext option under android.
{
"plugins": [
["expo-build-properties", {
"android": { "usesCleartextTraffic": true }
}]
]
}The snippet only sets the plugin option; the manifest is produced later. Run a prebuild or a fresh development-client build, then inspect the generated AndroidManifest.xml for android:usesCleartextTraffic before you retest, because a JS-only reload keeps the old manifest. Match the plugin version to the Expo SDK the project runs on. Plugin options move between releases, and a snippet copied from a different SDK is a reliable source of silent no-ops.
The bare workflow and the narrower option
For bare React Native the equivalent is android:usesCleartextTraffic on the application element, or a network security configuration that allows cleartext for named domains only. The domain-scoped form is the one to prefer, because it allows the internal API and leaves everything else on HTTPS. It also documents its own exception list, which is the part a reviewer will ask about.
Neither approach changes the platform default for anything you did not name.
Prove it from the device log first
Before editing configuration, get the exact failure. adb logcat on an attached device shows the platform's cleartext rejection and names the component that raised it. A refused socket, a name that will not resolve or a failed TLS handshake points somewhere else entirely, and enabling cleartext would only weaken the app for nothing.
Then keep production honest. Cleartext is acceptable for a lab endpoint that cannot offer TLS, and only for the shortest time you can schedule. If the API can terminate TLS inside the private network, put the certificate in the device's trust store and leave cleartext disabled, because the tunnel encrypts the path to its exit and nothing past it.
Once the endpoint answers on HTTPS, drop the permission, rebuild, and test on the VPN and on ordinary Wi-Fi in the same session. Name resolution can differ between those two paths, and only the pair tells you which one is telling the truth.