馃帀 Premium Proxies 路 24-Hour Free TrialClaim Now
Guides

How to Set Proxy on Android With a Username and Password

How to set proxy on Android when Wi-Fi settings have no password field: whitelist your IP, run a local auth forwarder, or use adb's global proxy, then verify.

S SparkProxy 2 22 min read
Share

How to set proxy on Android sounds like a two-minute job until you open the Wi-Fi proxy screen and find a hostname box, a port box, a bypass list, and nowhere to type the username and password your proxy provider gave you. That's not a hidden menu or a vendor skin hiding the field. Android's proxy model has no slot for credentials at all. This guide covers the two ways around it (whitelist the network's public IP, or run a small forwarder that adds the credentials for you), then the adb global proxy that QA testers use, the apps that ignore the setting entirely, and how to prove which IP your traffic actually leaves from.

Why the Wi-Fi Proxy Screen Has No Password Field

On stock Android, the path is Settings > Network & internet > Internet, tap the network, tap the edit (pencil) icon, open Advanced options, then set Proxy to Manual. Zebra's Android 11 and 13 reference guide lists the fields you get: "Proxy hostname", "Proxy port" and "Bypass proxy for". Samsung and other skins move the menus around, but the fields are the same, because they all write the same platform object.

That object is android.net.ProxyInfo. Its public API (reference page last updated 2026-08-03) has buildDirectProxy(host, port, exclList), buildPacProxy(pacUrl), getHost(), getPort(), getExclusionList() and getPacFileUrl(). There is no username, password or credential method anywhere on the class. The PAC option doesn't help either: a PAC script only chooses a proxy, it can't answer a login challenge.

So when you point the Wi-Fi proxy at a gateway that wants user:pass, every request gets 407 Proxy Authentication Required, and most apps just fail. Typing user:pass@gateway.sparkproxy.io into the hostname box doesn't sneak the credentials in. The hostname validator in AOSP's ProxyUtils accepts only letters, digits, dots and hyphens, so the @, : and // are rejected and Settings shows its own error string: "The hostname you typed isn't valid."

Android's own UI also warns you about a second limit, which matters later in this guide. The AOSP Settings string proxy_warning_limited_support reads: "The HTTP proxy is used by the browser but may not be used by the other apps."

Choose a Route Before You Touch Settings

There are three ways to get a phone onto an authenticated proxy. Pick one based on whose network the phone is on and whether you have a USB cable and a laptop.

RouteCredentials liveWorks off your home/office Wi-Fi?Catches apps that ignore the proxy?Best for
IP whitelist, phone points at the gatewayNowhere on the deviceNo, only on networks whose public IP you whitelistedNoA personal phone on a fixed home or office line
Forwarder (on a laptop or in Termux)On the machine running the forwarderYes, if the forwarder runs on the phoneNoCoffee-shop Wi-Fi, dynamic IPs, shared networks
adb global proxy + forwarder on the laptopOn the laptop onlyApplies to every network the phone joinsNoQA benches, test devices, CI rigs
Emulator `-http-proxy` flagIn the emulator launch commandn/aYes, for TCPEmulator-only test runs

The last row is the outlier. Google's emulator proxy documentation (updated 2026-03-12) says the flag accepts http://:@: and "routes all of the emulated device's TCP traffic through the specified proxy." That's why an emulator run can pass while the same app on a physical phone leaks around the proxy. Our post on proxies for mobile app testing teams covers where the emulator fits in a QA setup; this guide is about real devices.

Free trial

Scraping at scale? Skip the blocks.

Fast, unblockable datacentre proxies with unlimited bandwidth.

Route 1: Whitelist the Network's Public IP

If the phone sits on one Wi-Fi network you control, the simplest fix is to make the gateway stop asking for a password. SparkProxy supports both user:pass and IP whitelist authentication. Once your network's public IP is on the whitelist, the gateway accepts connections from it with no credentials, and Android's hostname-and-port screen is all you need.

  1. On the phone, with no proxy set, open https://api.ipify.org in the browser. Write down the address. That's your router's public IP, shared by every device on that Wi-Fi.
  2. Add that IP to the whitelist in your SparkProxy dashboard.
  3. Back in the Wi-Fi proxy screen, set Proxy hostname to gateway.sparkproxy.io and Proxy port to 11000 for Random Rotation, or 11002 for Sticky Sessions, where the exit IP holds for 5 minutes.
  4. Put local addresses in Bypass proxy for, comma-separated with no spaces, for example localhost,192.168.1.1,intranet.sparkproxy.io.
  5. Save, then run the checks in the verification section below.

Three catches come with this route. The Wi-Fi proxy is stored per network, so the moment the phone drops to mobile data or joins another Wi-Fi, it connects directly with no warning. Many home connections get a new public IP after a router restart, which turns into sudden 407s until you update the whitelist. And anyone else on that Wi-Fi can now use your proxy plan, because the gateway can't tell your phone from the smart TV behind the same NAT. Our explainer on IP whitelisting for proxies goes deeper on the NAT and dynamic-IP trade-offs.

For app testing, prefer port 11002. An app opens many parallel connections while it starts up, and on Random Rotation each one can leave from a different IP. A backend that ties a login session to an address may treat that as suspicious and force a logout.

Route 2: Run a Forwarder That Adds Credentials

When whitelisting isn't practical, put a tiny proxy in front of the real one. The phone talks to the forwarder with no authentication. The forwarder adds a Proxy-Authorization header and relays everything to gateway.sparkproxy.io. The credentials never touch Android's settings.

Below is a complete forwarder using only the Python standard library. It handles both HTTPS (CONNECT tunnels, which is nearly all app traffic) and plain HTTP. It doesn't decrypt anything: TLS passes straight through, so you don't need to install a certificate on the phone.

"""authfwd.py: a local HTTP proxy that adds SparkProxy credentials upstream.

Android's Wi-Fi proxy has no username/password field. Point it at this
forwarder instead; the forwarder adds Proxy-Authorization and relays to the
SparkProxy gateway. Standard library only, Python 3.8+.
"""
import asyncio
import base64
import os

LISTEN_HOST = os.environ.get("LISTEN_HOST", "127.0.0.1")
LISTEN_PORT = int(os.environ.get("LISTEN_PORT", "8888"))
UPSTREAM_HOST = os.environ.get("UPSTREAM_HOST", "gateway.sparkproxy.io")
UPSTREAM_PORT = int(os.environ.get("UPSTREAM_PORT", "11000"))
TOKEN = base64.b64encode(
    f"{os.environ['SPARK_USER']}:{os.environ['SPARK_PASS']}".encode()
).decode()

DROP = (b"proxy-authorization:", b"proxy-connection:", b"connection:")


async def pipe(reader, writer):
    try:
        while data := await reader.read(65536):
            writer.write(data)
            await writer.drain()
    except (ConnectionError, asyncio.CancelledError):
        pass
    finally:
        writer.close()


async def handle(client_r, client_w):
    try:
        head = await asyncio.wait_for(client_r.readuntil(b"\r\n\r\n"), 30)
    except (asyncio.IncompleteReadError, asyncio.LimitOverrunError,
            asyncio.TimeoutError, ConnectionError):
        client_w.close()
        return

    lines = head.split(b"\r\n")
    request_line = lines[0]
    is_connect = request_line.upper().startswith(b"CONNECT ")
    kept = [ln for ln in lines[1:] if ln and not ln.lower().startswith(DROP)]
    extra = [b"Proxy-Authorization: Basic " + TOKEN.encode()]
    if not is_connect:
        # One request per upstream connection, so every request carries auth.
        extra.append(b"Connection: close")
    new_head = b"\r\n".join([request_line] + kept + extra) + b"\r\n\r\n"

    try:
        up_r, up_w = await asyncio.open_connection(UPSTREAM_HOST, UPSTREAM_PORT)
    except OSError as exc:
        client_w.write(b"HTTP/1.1 502 Bad Gateway\r\nContent-Length: 0\r\n\r\n")
        await client_w.drain()
        client_w.close()
        print("upstream unreachable:", exc)
        return

    up_w.write(new_head)
    await up_w.drain()
    print(request_line.decode(errors="replace"))
    await asyncio.gather(pipe(client_r, up_w), pipe(up_r, client_w))


async def main():
    server = await asyncio.start_server(handle, LISTEN_HOST, LISTEN_PORT)
    print(f"listening on {LISTEN_HOST}:{LISTEN_PORT} -> "
          f"{UPSTREAM_HOST}:{UPSTREAM_PORT}")
    async with server:
        await server.serve_forever()


if __name__ == "__main__":
    asyncio.run(main())

We tested it on 2026-10-30 with Python 3.14.2 against a stand-in upstream that demands Basic auth. Through the forwarder, curl -x http://127.0.0.1:8888 https://api.ipify.org completed its CONNECT tunnel and a plain-HTTP GET returned 200, with the upstream logging a valid Proxy-Authorization header both times. The same request sent straight to the upstream without the forwarder got 407. We didn't run it against the live SparkProxy gateway with a customer account, so treat the gateway leg as the documented configuration rather than a measurement.

To serve a phone on the same Wi-Fi from a laptop, bind to the LAN interface:

export SPARK_USER='your-username' SPARK_PASS='your-password'
LISTEN_HOST=0.0.0.0 LISTEN_PORT=8888 UPSTREAM_PORT=11002 python3 authfwd.py
# Phone: Proxy hostname = the laptop's LAN IP (e.g. 192.168.1.50), Proxy port = 8888

Binding to 0.0.0.0 makes the forwarder an open, pre-authenticated proxy for everyone on that network. On a shared Wi-Fi, firewall port 8888 to the phone's address only, or use the USB setup in the adb section, which never exposes the port to the LAN.

One more property of this design is handy: the forwarder prints every request line. If you open an app and its hostnames never appear in that log, the app isn't using the proxy. That's the fastest bypass test there is, and we come back to it below.

Running the Forwarder on the Phone With Termux

A laptop-side forwarder only helps while the phone is on the laptop's network. To take the setup anywhere, run the same script on the phone and point the Wi-Fi proxy at 127.0.0.1.

Termux is the usual way to get Python on an unrooted Android device. Its GitHub repository showed the latest stable release as v0.118.3, published 2025-05-22, with the most recent commit on 2026-09-26 when we checked on 2026-10-30. The project README recommends installing from F-Droid or GitHub Releases and describes the Google Play build as an experimental branch with "missing functionality and bugs".

# Inside Termux
pkg update && pkg install python
mkdir -p ~/proxy && cd ~/proxy
# copy authfwd.py here (termux-setup-storage gives access to Downloads)
export SPARK_USER='your-username' SPARK_PASS='your-password'
UPSTREAM_PORT=11002 python authfwd.py
# Wi-Fi proxy on the same phone: hostname 127.0.0.1, port 8888

The default LISTEN_HOST=127.0.0.1 matters here. It keeps the forwarder reachable only from the phone itself, so nobody else on the caf茅 Wi-Fi can ride on your credentials.

You'll still have to set the Wi-Fi proxy to 127.0.0.1:8888 on each network you join, because Android stores it per network. And Android will kill a background process it thinks is idle. The Termux README tells users to make sure battery optimisations are disabled for the app. If pages stop loading after the screen has been off for a while, the forwarder is usually the thing that died.

For Testers: the adb Global Proxy

QA benches don't want per-network taps. Android has a network-independent global proxy, and adb can set it without root:

adb shell settings put global http_proxy 127.0.0.1:8888
adb shell settings get global http_proxy

The value is the Settings.Global.HTTP_PROXY constant, documented as "Host name and port for global http proxy. Uses ':' seperator for between host and port." The DevicePolicyManager.setRecommendedGlobalProxy() reference describes the same global proxy as "network-independent," and adds a line every tester should remember: "This proxy is only a recommendation and it is possible that some apps will ignore it."

In the current AOSP Connectivity source (ConnectivityService.getProxyForNetwork(), read on 2026-10-30), a global proxy is returned before any per-network proxy is consulted. So once it's set, it beats whatever the Wi-Fi screen says and also applies on mobile data.

Pair it with adb reverse and the credentials never leave your laptop. adb reverse tcp:8888 tcp:8888 makes port 8888 on the device connect to port 8888 on the host over USB, so the device-side 127.0.0.1:8888 reaches the forwarder running on the laptop:

# Laptop: forwarder on loopback only
export SPARK_USER='your-username' SPARK_PASS='your-password'
UPSTREAM_PORT=11002 python3 authfwd.py &

# Device: tunnel its 127.0.0.1:8888 to the laptop, then set the global proxy
adb reverse tcp:8888 tcp:8888
adb shell settings put global http_proxy 127.0.0.1:8888

Watch the format. AOSP's ProxyTracker.loadDeprecatedGlobalHttpProxy() splits the string on : and parses the second piece as the port. The behaviour that follows from that code:

Value you putWhat the platform does
`127.0.0.1:8888`Global proxy set to 127.0.0.1, port 8888
`gateway.sparkproxy.io`Port defaults to 8080, which is wrong for SparkProxy
`http://127.0.0.1:8888`Port parse fails, setting silently ignored
`user:pass@gateway.sparkproxy.io:11000`Port parse fails, silently ignored
`:0`Empty host, global proxy cleared

No error is printed in the ignored cases. settings put writes the string happily, and the device carries on without a proxy. The android_proxy.py helper further down refuses those formats before they reach the device.

If you'd rather not run a forwarder on a test bench at all, whitelist the bench's public IP and set http_proxy straight to gateway.sparkproxy.io:11002.

Clearing the adb Proxy Without Leaving It Stuck

This is the step that catches people. The obvious undo, adb shell settings delete global http_proxy, removes the key you set, but in the AOSP code a missing http_proxy makes loadDeprecatedGlobalHttpProxy() return early without touching the active proxy. The values it copied into the hidden global_http_proxy_host and global_http_proxy_port keys stay put, and loadGlobalProxy() reads those keys back at boot. A device can stay proxied after you "deleted" the setting and rebooted, which shows up as a test phone that has no internet the day after the bench laptop goes home.

Putting :0 works because it parses to an empty host, and ProxyTracker.setGlobalProxy() treats an empty host as "clear": it writes an empty host and port 0 into those keys. Do that first, then delete:

adb shell settings put global http_proxy :0
adb shell settings delete global http_proxy
adb shell settings get global global_http_proxy_host   # expect an empty line
adb reverse --remove tcp:8888

For a bench with several devices, wrap the set, show and clear steps so nobody hand-types them:

"""android_proxy.py: set, inspect and clear the Android global proxy over adb.

Usage:
  python android_proxy.py set 127.0.0.1:8888   # host:port only, no scheme
  python android_proxy.py show
  python android_proxy.py clear
  python android_proxy.py check                # opens an IP echo page on the device
"""
import re
import subprocess
import sys

KEYS = ("http_proxy", "global_http_proxy_host", "global_http_proxy_port",
        "global_http_proxy_exclusion_list")


def adb(*args):
    out = subprocess.run(["adb", *args], capture_output=True, text=True, check=True)
    return out.stdout.strip()


def show():
    for key in KEYS:
        print(f"{key:34} {adb('shell', 'settings', 'get', 'global', key)}")


def set_proxy(value):
    # The platform splits on ':' and silently ignores anything it can't parse,
    # so reject schemes and credentials here instead of failing quietly.
    if not re.fullmatch(r"[A-Za-z0-9.\-]+:\d{1,5}", value):
        sys.exit("expected host:port, e.g. 127.0.0.1:8888 (no http://, no user:pass)")
    adb("shell", "settings", "put", "global", "http_proxy", value)
    show()


def clear():
    # ':0' parses to an empty host, which makes the platform wipe the
    # global_http_proxy_* keys. Deleting http_proxy alone leaves them set.
    adb("shell", "settings", "put", "global", "http_proxy", ":0")
    adb("shell", "settings", "delete", "global", "http_proxy")
    show()


def check():
    adb("shell", "am", "start", "-a", "android.intent.action.VIEW",
        "-d", "https://api.ipify.org")
    print("Read the IP on the device screen and compare it with your direct IP.")


if __name__ == "__main__":
    cmd = sys.argv[1] if len(sys.argv) > 1 else "show"
    if cmd == "set" and len(sys.argv) == 3:
        set_proxy(sys.argv[2])
    elif cmd in ("show", "clear", "check"):
        {"show": show, "clear": clear, "check": check}[cmd]()
    else:
        sys.exit(__doc__)

Set ANDROID_SERIAL to target one device when several are attached. We compiled this helper but had no device on our build machine, so the clearing behaviour above comes from reading AOSP source on its main branch, not from a device run. Behaviour on older Android releases or heavily modified vendor builds may differ, which is why the script prints all four keys after every change.

Apps That Ignore the Android Proxy

Setting the proxy correctly is half the job. Android's proxy is advice that each app's network stack chooses to follow or not. The ProxyInfo reference spells out the split: proxy settings "are already integrated within the java.net and Apache HTTP stack," and "Other HTTP stacks will need to obtain the proxy info by watching for the Proxy.PROXY_CHANGE_ACTION broadcast." A stack that never does that connects directly.

The categories that usually bypass it:

  • Flutter apps on dart:io. Dart's HttpClient.findProxy documentation says: "If this function is not set, direct connections will always be used." The default client in package:http (current version 1.6.0) is IOClient on Android, so unless the developer wired up a proxy lookup or switched to cronet_http, a Flutter app ignores the Wi-Fi and global proxy alike.
  • Apps with their own native networking, such as games and video apps that open raw sockets from C++ code.
  • UDP traffic of any kind. An HTTP proxy carries TCP connections only. Voice and video calls, and anything else on UDP, go direct.
  • Background work after a network switch, when the Wi-Fi-only proxy no longer applies because the phone moved to mobile data.

The browser is the case Android itself vouches for, per that Settings warning. For WebView, Chromium's proxy documentation says it "uses the Android system's PAC resolver," so it reads the system configuration too. The same document adds a detail relevant here: Chrome "will not use any credentials embedded in the proxy settings," so even on platforms that store them, authentication goes through the normal challenge flow.

A proxy cannot fix a bypassing app. Whitelisting, forwarders and the global proxy all depend on the app asking the system which proxy to use. To force an app that doesn't ask, you need a VPN-style tunnel that captures all traffic on the device, or you test it on the emulator, whose proxy works below the app at the TCP layer. If your test plan includes Flutter builds or games, budget for that from the start. Our mobile proxies guide for social media automation and app testing covers when a mobile carrier IP matters for those apps at all.

Confirm the Exit IP, Not Just the Setting

A saved setting only proves the setting was saved. Prove the path in three layers.

1. Check the gateway from a laptop first. This separates "my credentials or whitelist are wrong" from "the phone isn't using the proxy":

"""exit_ip.py: confirm credentials and rotation before touching the phone."""
import os
import urllib.request

ECHO = "https://api.ipify.org"
USER, PASS = os.environ["SPARK_USER"], os.environ["SPARK_PASS"]


def fetch(proxy=None):
    handlers = []
    if proxy:
        handlers.append(urllib.request.ProxyHandler({"http": proxy, "https": proxy}))
    opener = urllib.request.build_opener(*handlers)
    with opener.open(ECHO, timeout=20) as resp:
        return resp.read().decode().strip()


print("direct      ", fetch())
for port, label in ((11000, "random"), (11002, "sticky")):
    url = f"http://{USER}:{PASS}@gateway.sparkproxy.io:{port}"
    print(f"{label:6} {port}", [fetch(url) for _ in range(3)])

On port 11000 the three addresses should normally differ. On 11002 they should match for up to 5 minutes. If both lines raise a 407 error, fix the credentials before going near the phone.

2. Check from the device's browser. Open https://api.ipify.org on the phone (or run python android_proxy.py check). It should show a proxy IP, not the one you wrote down in Route 1. With the forwarder running, a CONNECT api.ipify.org:443 line appears in its log at the same moment.

3. Check the app you actually care about. Open it, use the feature you're testing, and watch the forwarder log. Its API hostnames should scroll past. If the log stays quiet while the app clearly loads data, that app is in the bypass list from the previous section, and a browser check passing tells you nothing about it.

For a quick on-device read of the active value, adb shell settings get global http_proxy shows the global proxy. The per-network Wi-Fi proxy lives in that network's saved configuration rather than in Settings.Global, so check it in the Wi-Fi screen.

Symptoms and Fixes

Work down this list from the top. The cheapest checks come first.

What you seeMost likely causeFixCan the proxy fix it?
"The hostname you typed isn't valid." in the Wi-Fi dialogScheme or `user:pass@` typed into the hostnameHostname only: `gateway.sparkproxy.io` or `127.0.0.1`Yes, config
Chrome shows `ERR_PROXY_CONNECTION_FAILED`Nothing listening at the host and port: forwarder stopped, wrong LAN IP, or `adb reverse` droppedRestart the forwarder, re-run `adb reverse`, check the portYes, config
Every page fails with a 407Public IP changed or was never whitelisted, or wrong credentials in the forwarderRe-check with `exit_ip.py`, update the whitelistYes, account
Browser shows a proxy IP, app still shows home regionApp bypasses the system proxyEmulator, VPN-style capture, or test on the backendNo
Works on Wi-Fi, leaks on mobile dataWi-Fi proxy is per networkUse the adb global proxy, or switch mobile data offPartly
Device has no internet after testingGlobal proxy still set after `settings delete``settings put global http_proxy :0`Yes, config
App logs out repeatedly behind the proxyExit IP rotating between parallel connectionsUse port 11002 (Sticky Sessions)Yes, config
TLS errors in one app onlyCertificate pinning, or a decrypting proxy upstreamThe forwarder here doesn't decrypt; look for something else intercepting TLSNo

That last row comes up when people swap in a debugging proxy. Android's network security configuration documentation says apps trust the pre-installed system CAs by default, and only "apps targeting Android 6.0 (API level 23) and lower" also trust user-added certificates. A pass-through forwarder like authfwd.py never presents its own certificate, so it can't cause this error. An intercepting tool can.

On cost: SparkProxy plans are packs starting from $75/month (USD, as listed on sparkproxy.io on 2026-10-09) with unlimited bandwidth, so the billing unit is the plan and its concurrency, not gigabytes. A phone left on the proxy overnight pulling app updates doesn't run up a traffic bill. The 24-hour free trial needs no credit card, which is enough to run all three verification layers above on a real device before you commit.

Frequently asked questions

FAQ

Android stores the proxy as a ProxyInfo object, which has a host, port, exclusion list and optional PAC URL, and nothing for credentials. Every vendor skin writes that same object, so no menu will reveal a hidden password field. Use IP whitelisting or a local forwarder instead.

Run a forwarder that adds the Proxy-Authorization header, either on a laptop on the same Wi-Fi or on the phone in Termux, and point the Wi-Fi proxy at it. Neither needs root, and the credentials stay out of Android's settings.

No. Android's own Settings warns that the HTTP proxy "may not be used by the other apps." Apps on the java.net stack, Chrome and WebView follow it, while Flutter apps on dart:io, native socket code and all UDP traffic usually go direct.

Run adb shell settings put global http_proxy host:port with no scheme and no credentials. Pair it with adb reverse tcp:8888 tcp:8888 and a laptop-side forwarder if the proxy needs a password.

Run adb shell settings put global http_proxy :0 first, then delete the key. In AOSP's code, deleting http_proxy alone leaves the hidden global_http_proxy_host and global_http_proxy_port values in place, so the device can stay proxied after a reboot.

Open https://api.ipify.org in the phone's browser and compare it with the address you saw before enabling the proxy. For a specific app, watch the forwarder log while you use it, because a browser check can't tell you whether that app follows the proxy.

Special Discount 路 20% off

Get 20% off your first month

Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.

Save up to 15% more on quarterly, half-yearly and yearly plans

Claim Discount

About the Author

SparkProxy Technical Team builds and operates SparkProxy's proxy network (1M+ premium IPs across 80+ countries, HTTP/HTTPS/SOCKS5, IP whitelist and user:pass auth, 1 Gbps ports) and the SparkProxy Scraping API. The forwarder in this guide was tested on 2026-10-30 with Python 3.14.2 against a local Basic-auth upstream. The adb behaviour is taken from AOSP source read the same day, as noted in each section.

Sources, read 2026-10-30: ProxyInfo API reference 路 Settings.Global 路 DevicePolicyManager 路 AOSP ProxyTracker.java 路 AOSP ProxyUtils.java 路 AOSP Settings strings.xml 路 adb commandline.cpp 路 Emulator proxy setup 路 Network security configuration 路 Zebra: configuring for a proxy server 路 Dart HttpClient.findProxy 路 package:http 路 Chromium proxy docs 路 Chromium net_error_list.h 路 Termux app repository

Keep reading

Related articles

SSL WRONG_VERSION_NUMBER With a Proxy: The Fix

Getting SSL WRONG_VERSION_NUMBER through a proxy in Python? It's almost always https:// in the proxy URL. The one-character fix, plus every SSLEOFError cause.

SparkProxy路Guides