Two very different things get called blocking cleartext on Android. One is a policy inside an app, telling its own networking stack to refuse plain HTTP. The other is a device-level arrangement that tries to protect everything on the phone at once, usually by routing traffic through a VPN. They solve different problems, and the second one cannot fix the first: a tunnel protects the segment between the phone and the exit, while an app that speaks HTTP still speaks HTTP to whatever sits beyond that exit.

Two problems wearing one name

The first problem is payload: the app writes credentials or tokens into a request that is not encrypted end to end, so every hop that can see it can read it. The second is path: the app may be perfectly well behaved, and you still want the local Wi-Fi operator to learn nothing about where it connects. A VPN addresses the second and only the second.

That distinction decides the owner of the fix. Payload belongs to whoever ships the app, because only that code decides which URL it calls and whether it validates a certificate. Path belongs to the device or the network, because it is about where packets travel rather than what they contain.

Path one: the app you control

If you ship the app, the fix is a transport decision plus a policy that enforces it. Point every endpoint at HTTPS, keep hostname and certificate validation on, and delete the plain-HTTP fallback rather than leaving it one configuration flag away. A redirect does not help here: an HTTP request that receives a 301 has already sent its headers and body in the clear, so credentials must never travel on the assumption that the redirect will protect them.

On Android 7.0 and later, Network Security Configuration expresses the policy. The file below is the smallest useful version: it refuses cleartext everywhere unless a domain-specific exception says otherwise.

<network-security-config>
    <base-config cleartextTrafficPermitted="false" />
</network-security-config>

Reference that resource from the manifest, then read the exceptions that already exist before shipping. An old debug domain or a payment provider SDK entry can quietly re-permit cleartext for the host you were trying to protect.

Two version details are worth knowing. Apps targeting API 28 or later already default to refusing cleartext, so an app that still talks plain HTTP is either targeting an older API level or has an exception somewhere. And when a network security config is present, the older android:usesCleartextTraffic manifest attribute no longer decides the question, which is why changing that flag sometimes appears to do nothing.

Path two: the app you do not control

For an installed third-party app, you cannot rewrite its security configuration through a user setting. The realistic options are the product's own secure-connection controls, a maintained alternative that already uses TLS, or a managed-device policy that constrains what the device may run. Those are different commitments, and only the third one scales to a fleet.

Network rules are weaker substitutes than they look. Allowing only port 443 does not enforce authenticated TLS, since plenty of plaintext protocols can be moved to that port, and blocking port 80 does not cover cleartext on other ports. A browser's HTTPS-only mode is also scoped to that browser, not to a separate mail or chat app on the same phone.

Installing an interception certificate to make traffic look encrypted is a trust change, not a fix. It replaces the app's server authentication with yours, and an app that was not validating certificates in the first place is not repaired by that; you have only moved who can read the stream.

Where a VPN helps and where it does not

A tunnel is the right tool when the requirement is that the local network learns nothing about the connection, or that the app reaches a private service it cannot reach otherwise. It is the wrong tool for making an HTTP client speak HTTPS, because the request leaves the tunnel exactly as it entered it.

Android adds a practical constraint: the platform runs one VPN service at a time. A local filtering app that takes that slot competes with the remote tunnel you actually need, and switching between them is a device-level operation rather than an app-level one. That single slot is a good reason to put payload policy in the app and keep the tunnel for path.

The one case where the two overlap is a private server you also operate. There, a tunnel can make the path private even though the protocol is still cleartext, which reduces exposure but does not change what the payload is. Treat that as risk reduction, not as encryption of the data.

What to test before calling it blocked

Start with the default that surprises people: an app targeting API 28 or later should already refuse cleartext, so check the target level before adding configuration that duplicates a default. Then test the failure case deliberately: a request to a plain HTTP host must fail rather than silently succeed through a fallback.

adb shell dumpsys package com.example.app | grep -E 'targetSdk|versionName'
adb logcat | grep -i cleartext

For the success case, confirm the HTTPS call validates a real certificate chain and that a proxy with an untrusted certificate is rejected rather than tolerated. A handshake that succeeds against any certificate has not proved anything about transport security.

For a third-party app, write down the boundary you can actually enforce, whether that is a managed-device policy, a network rule, or nothing, and say so in the acceptance criteria. The Android security configuration guide lists the platform defaults and which components honour the policy, which is the part that decides whether your configuration does anything at all.

Artihkkalat mat laktásit