June 11, 2026
Intercepting HTTPS on Android 16 with HTTP Toolkit — What Nobody Tells You
Fixing HTTP Toolkit’s ‘Connecting…’ Hang on Android 14+ (VPN + CA Cert)
Vidhan Pancholi
8 min read
If you've tried setting up HTTP Toolkit (or Burp Suite) for HTTPS interception on a modern Android device recently, you've probably stared at the "Connecting…" screen longer than you'd like to admit.
I spent a full session debugging this on a Xiaomi Redmi Note 10 running Android 16 (SDK 36) with SukiSU Ultra (a KernelSU fork). What I thought would be a 5-minute setup turned into a deep dive through ADB logs, MediaStore internals, and Android's APEX architecture.
This post documents every failure I hit, why it happened, and the exact fix — in the order I found them. By the end, you'll have a permanent, reboot-proof HTTPS interception setup.
The Setup
- Device: Xiaomi Redmi Note 10 (M2101K7BG)
- Android: 16 (SDK 36)
- Root: SukiSU Ultra (KernelSU-based)
- HTTP Toolkit: Server v1.26.1 / Android app v1.6.0
- Connection: USB + ADB
How HTTP Toolkit Actually Works (Quick Primer)
Before we debug, it helps to understand the interception chain:
PC: HTTP Toolkit proxy (port 8001)
↑
ADB Reverse Tunnel [adb reverse tcp:8001 tcp:8001]
↑
Device: ProxyVpnService (tun0 virtual interface)
↑
ALL device network traffic → routed through VPN → forwarded to proxy
↑
HTTP Toolkit CA cert trusted as SYSTEM cert → HTTPS decryptedPC: HTTP Toolkit proxy (port 8001)
↑
ADB Reverse Tunnel [adb reverse tcp:8001 tcp:8001]
↑
Device: ProxyVpnService (tun0 virtual interface)
↑
ALL device network traffic → routed through VPN → forwarded to proxy
↑
HTTP Toolkit CA cert trusted as SYSTEM cert → HTTPS decryptedFive things must all work simultaneously. If any one fails, you get "Connecting…" with zero useful feedback on the PC side. This is the core reason the issue is so painful to debug — the failure mode is identical regardless of which step broke.
Failure #1 — The Silent VPN Dialog
What happened
Clicked "Android via ADB" in HTTP Toolkit. The app opened on the phone. PC showed "Connecting…". Nothing else happened.
Digging in
adb logcat -d -b events | grep -iE "vpn|httptoolkit"adb logcat -d -b events | grep -iE "vpn|httptoolkit"Output told the story:
input_focus: Focus entering com.android.vpndialogs/.ConfirmDialog
input_focus: Focus leaving com.android.vpndialogs/.ConfirmDialog — NO_WINDOW
input_focus: Focus entering tech.httptoolkit.android.v1/...RemoteControlMainActivityinput_focus: Focus entering com.android.vpndialogs/.ConfirmDialog
input_focus: Focus leaving com.android.vpndialogs/.ConfirmDialog — NO_WINDOW
input_focus: Focus entering tech.httptoolkit.android.v1/...RemoteControlMainActivityThe VPN permission dialog appeared and disappeared in seconds without being accepted. Android shows this dialog, but on Xiaomi devices with aggressive battery management, it can render behind the app or get dismissed by system gestures.
The appops log confirmed it:
adb shell cmd appops get tech.httptoolkit.android.v1 ACTIVATE_VPN
# rejectTime=+8m28s694ms agoadb shell cmd appops get tech.httptoolkit.android.v1 ACTIVATE_VPN
# rejectTime=+8m28s694ms agoThe fix
adb shell am force-stop tech.httptoolkit.android.v1
adb reverse --remove-all
adb shell settings put global http_proxy :0
adb reverse tcp:8001 tcp:8001
# Re-connect from HTTP Toolkit PC, watch the device screenadb shell am force-stop tech.httptoolkit.android.v1
adb reverse --remove-all
adb shell settings put global http_proxy :0
adb reverse tcp:8001 tcp:8001
# Re-connect from HTTP Toolkit PC, watch the device screenWatch your device screen when you reconnect. The dialog (com.android.vpndialogs/.ConfirmDialog) appears briefly. On Xiaomi: if you don't see it, pull down the notification shade — it might be hiding there. Tap OK.
Failure #2 — VPN Accepted, Service Still Dead
What happened
Accepted the VPN dialog. Still stuck. Checked whether the VPN service was running:
adb shell dumpsys activity services tech.httptoolkit.android.v1
# ACTIVITY MANAGER SERVICES (nothing)adb shell dumpsys activity services tech.httptoolkit.android.v1
# ACTIVITY MANAGER SERVICES (nothing)Nothing. The ProxyVpnService was never instantiated.
Digging deeper
Filtered logcat for HTTP Toolkit's own output:
adb logcat -d | grep -E "tech.httptoolkit|ProxySetup|cert|Installing" \
| grep -v "at com\|at java\|NetworkFactory"adb logcat -d | grep -E "tech.httptoolkit|ProxySetup|cert|Installing" \
| grep -v "at com\|at java\|NetworkFactory"Two smoking guns appeared:
Problem A — Notification permission denied:
adb shell dumpsys package tech.httptoolkit.android.v1 | grep -A1 "POST_NOTIFICATIONS"
# android.permission.POST_NOTIFICATIONS: granted=falseadb shell dumpsys package tech.httptoolkit.android.v1 | grep -A1 "POST_NOTIFICATIONS"
# android.permission.POST_NOTIFICATIONS: granted=falseSince Android 13, foreground services (including VPN) require POST_NOTIFICATIONS to show the persistent notification that keeps them alive. Without it, Android kills the service seconds after it starts.
Problem B — 32 copies of the certificate:
DatabaseUtils: IllegalStateException: Failed to build unique file:
/storage/emulated/0/Download/HTTP Toolkit Certificate.crtDatabaseUtils: IllegalStateException: Failed to build unique file:
/storage/emulated/0/Download/HTTP Toolkit Certificate.crtEvery previous failed connection attempt had written a new copy of the certificate to the Downloads folder. After 31 numbered copies (HTTP Toolkit Certificate (1).crt through (31).crt), MediaStore's unique filename generator started throwing exceptions — meaning HTTP Toolkit couldn't even write the cert file, so it had nothing to pass to the cert installer.
adb shell "find /storage/emulated/0/Download/ -iname '*toolkit*' 2>/dev/null"
# 32 .crt files + 20 .pending-* filesadb shell "find /storage/emulated/0/Download/ -iname '*toolkit*' 2>/dev/null"
# 32 .crt files + 20 .pending-* filesThe fixes
# Fix A: Grant notification permission
adb shell pm grant tech.httptoolkit.android.v1 android.permission.POST_NOTIFICATIONS
# Fix B: Nuke all the accumulated cert files
adb shell "for f in /storage/emulated/0/Download/HTTP\ Toolkit\ Certificate*.crt; do rm -f \"\$f\"; done"
adb shell "for f in /storage/emulated/0/Download/.pending-*HTTP\ Toolkit*; do rm -f \"\$f\"; done"
# Verify clean
adb shell "find /storage/emulated/0/Download/ -iname '*toolkit*' 2>/dev/null"
# Should return nothing# Fix A: Grant notification permission
adb shell pm grant tech.httptoolkit.android.v1 android.permission.POST_NOTIFICATIONS
# Fix B: Nuke all the accumulated cert files
adb shell "for f in /storage/emulated/0/Download/HTTP\ Toolkit\ Certificate*.crt; do rm -f \"\$f\"; done"
adb shell "for f in /storage/emulated/0/Download/.pending-*HTTP\ Toolkit*; do rm -f \"\$f\"; done"
# Verify clean
adb shell "find /storage/emulated/0/Download/ -iname '*toolkit*' 2>/dev/null"
# Should return nothingFailure #3 — "Manual Setup Required" Dialog
What happened
With the cert clutter cleared, the HTTP Toolkit Android app finally showed a dialog I hadn't seen before:
Manual setup required_ Android 16 doesn't allow automatic certificate setup. Open "More security settings" → "Encryption & Credentials" → "Install a certificate" → "CA Certificate" → select the HTTP Toolkit certificate from Downloads._
The logcat confirmed why:
MainActivity: Certificate not trusted, prompting to install
MediaProvider: Moving .pending-1781768600-HTTP Toolkit Certificate.crt → HTTP Toolkit Certificate.crt
# ← complete silence after this. No cert installer launched.MainActivity: Certificate not trusted, prompting to install
MediaProvider: Moving .pending-1781768600-HTTP Toolkit Certificate.crt → HTTP Toolkit Certificate.crt
# ← complete silence after this. No cert installer launched.Why this happens
This is a deliberate Android 14+ security change. Prior to Android 14, apps could call ACTION_INSTALL_CERTIFICATE to programmatically launch the system CA cert installer. Google removed this for third-party apps. HTTP Toolkit writes the cert to Downloads and then... nothing. The install step it relied on no longer works.
HTTP Toolkit's app detects the Android version and shows this manual instruction dialog instead, but without root there is no programmatic path forward.
If you don't have root: Follow the manual steps. They work, but require UI navigation on the device every time.
If you have root: Keep reading — we can do this entirely from ADB.
The Root-Based Fix — APEX Conscrypt Bind Mount
This is where it gets interesting.
Why /system/etc/security/cacerts/ isn't enough anymore
Most guides tell you to push your CA cert to /system/etc/security/cacerts/. That worked on Android ≤ 13. On Android 14+, it does nothing.
The reason: Android 14 moved the CA certificate store into an APEX package (com.android.conscrypt). The Conscrypt TLS library — which every Android app uses for SSL/TLS — reads its trusted CA list exclusively from:
/apex/com.android.conscrypt/cacerts//apex/com.android.conscrypt/cacerts/This path is mounted from an APEX image, making it effectively read-only even with root. Writing to /system/etc/security/cacerts/ still exists but is ignored by the live TLS stack.
The solution is a bind mount: create a staging directory containing all the original system certs plus your new cert, then mount it over the APEX path. The OS sees your directory, not the APEX image.
Step-by-step via ADB
Step 1 — Pull the cert HTTP Toolkit already wrote to Downloads
# Windows PowerShell (not Git Bash — path rewriting breaks adb in Git Bash)
adb pull "/sdcard/Download/HTTP Toolkit Certificate.crt" "C:\Windows\Temp\httptoolkit.crt"# Windows PowerShell (not Git Bash — path rewriting breaks adb in Git Bash)
adb pull "/sdcard/Download/HTTP Toolkit Certificate.crt" "C:\Windows\Temp\httptoolkit.crt"Step 2 — Detect format, convert to PEM, get the hash
HTTP Toolkit writes DER-encoded certs. Android's CA store uses PEM with a specific filename format: <subject_hash_old>.0
# Convert DER → PEM
openssl x509 -inform DER -in "C:\Windows\Temp\httptoolkit.crt" -out "C:\Windows\Temp\httptoolkit.pem"
# Get the subject hash (this becomes the filename)
openssl x509 -inform PEM -in "C:\Windows\Temp\httptoolkit.pem" -subject_hash_old | head -1
# Output example: 0db75fbd# Convert DER → PEM
openssl x509 -inform DER -in "C:\Windows\Temp\httptoolkit.crt" -out "C:\Windows\Temp\httptoolkit.pem"
# Get the subject hash (this becomes the filename)
openssl x509 -inform PEM -in "C:\Windows\Temp\httptoolkit.pem" -subject_hash_old | head -1
# Output example: 0db75fbdStep 3 — Push the PEM cert to the device
adb push "C:\Windows\Temp\httptoolkit.pem" "/data/local/tmp/0db75fbd.0"
# Replace 0db75fbd with your actual hashadb push "C:\Windows\Temp\httptoolkit.pem" "/data/local/tmp/0db75fbd.0"
# Replace 0db75fbd with your actual hashStep 4 — Bind mount into the APEX conscrypt store
adb shell "su -c '
# Create staging dir and populate with all existing system certs
mkdir -p /data/local/tmp/cacerts-new
cp /apex/com.android.conscrypt/cacerts/* /data/local/tmp/cacerts-new/
# Add our cert
cp /data/local/tmp/0db75fbd.0 /data/local/tmp/cacerts-new/0db75fbd.0
chmod 644 /data/local/tmp/cacerts-new/*
chown root:root /data/local/tmp/cacerts-new/*
# Overlay the staging dir over the APEX cacerts path
mount --bind /data/local/tmp/cacerts-new /apex/com.android.conscrypt/cacerts
# Fix SELinux context
chcon -R system_file /apex/com.android.conscrypt/cacerts/
echo done
'"adb shell "su -c '
# Create staging dir and populate with all existing system certs
mkdir -p /data/local/tmp/cacerts-new
cp /apex/com.android.conscrypt/cacerts/* /data/local/tmp/cacerts-new/
# Add our cert
cp /data/local/tmp/0db75fbd.0 /data/local/tmp/cacerts-new/0db75fbd.0
chmod 644 /data/local/tmp/cacerts-new/*
chown root:root /data/local/tmp/cacerts-new/*
# Overlay the staging dir over the APEX cacerts path
mount --bind /data/local/tmp/cacerts-new /apex/com.android.conscrypt/cacerts
# Fix SELinux context
chcon -R system_file /apex/com.android.conscrypt/cacerts/
echo done
'"Step 5 — Restart HTTP Toolkit
adb shell am force-stop tech.httptoolkit.android.v1
adb reverse --remove-all && adb reverse tcp:8001 tcp:8001
adb shell am start -n "tech.httptoolkit.android.v1/tech.httptoolkit.android.RemoteControlMainActivity" \
-a "tech.httptoolkit.android.ACTIVATE" \
-d "https://android.httptoolkit.tech/connect/?data=<YOUR_PAYLOAD>"adb shell am force-stop tech.httptoolkit.android.v1
adb reverse --remove-all && adb reverse tcp:8001 tcp:8001
adb shell am start -n "tech.httptoolkit.android.v1/tech.httptoolkit.android.RemoteControlMainActivity" \
-a "tech.httptoolkit.android.ACTIVATE" \
-d "https://android.httptoolkit.tech/connect/?data=<YOUR_PAYLOAD>"At this point, logcat confirmed everything working:
ProxyVpnService: starting
SessionManager: Initiate connecting to remote tcp server: /192.168.4.204:8001
SessionHandler: Send SYN-ACK to clientProxyVpnService: starting
SessionManager: Initiate connecting to remote tcp server: /192.168.4.204:8001
SessionHandler: Send SYN-ACK to clientAnd from dumpsys connectivity:
VPN CONNECTED — InterfaceName: tun0 — Routes: 0.0.0.0/0 → tun0 (all traffic)
sessionId=HTTP ToolkitVPN CONNECTED — InterfaceName: tun0 — Routes: 0.0.0.0/0 → tun0 (all traffic)
sessionId=HTTP ToolkitHTTPS interception active.
Failure #4 — Rebooted and Lost Everything
What happened
Device was rebooted. HTTP Toolkit no longer intercepted HTTPS. The bind mount was gone.
Why
mount --bind is in-memory. Every reboot the APEX filesystem is freshly mounted from the APEX package. Your overlay disappears.
The fix — Permanent SukiSU/KernelSU/Magisk module
The solution is a root module with a service.sh script that re-applies the bind mount on every boot automatically.
Module structure
/data/adb/modules/httptoolkit-cert/
├── module.prop
├── service.sh ← runs on every boot with root
└── system/
└── etc/
└── security/
└── cacerts/
└── 0db75fbd.0 ← the cert (pre-Android-14 fallback)/data/adb/modules/httptoolkit-cert/
├── module.prop
├── service.sh ← runs on every boot with root
└── system/
└── etc/
└── security/
└── cacerts/
└── 0db75fbd.0 ← the cert (pre-Android-14 fallback)Create it via ADB
# 1. Create directories
adb shell "su -c 'mkdir -p /data/adb/modules/httptoolkit-cert/system/etc/security/cacerts'"
# 2. module.prop
adb shell "su -c \"printf 'id=httptoolkit-cert\nname=HTTP Toolkit System Certificate\nversion=v1\nversionCode=1\nauthor=Security Assessment\ndescription=HTTP Toolkit CA cert for HTTPS interception on Android 14+\n' > /data/adb/modules/httptoolkit-cert/module.prop\""
# 3. Place cert in module system overlay
adb shell "su -c '
cp /data/local/tmp/0db75fbd.0 /data/adb/modules/httptoolkit-cert/system/etc/security/cacerts/0db75fbd.0
chmod 644 /data/adb/modules/httptoolkit-cert/system/etc/security/cacerts/0db75fbd.0
'"# 1. Create directories
adb shell "su -c 'mkdir -p /data/adb/modules/httptoolkit-cert/system/etc/security/cacerts'"
# 2. module.prop
adb shell "su -c \"printf 'id=httptoolkit-cert\nname=HTTP Toolkit System Certificate\nversion=v1\nversionCode=1\nauthor=Security Assessment\ndescription=HTTP Toolkit CA cert for HTTPS interception on Android 14+\n' > /data/adb/modules/httptoolkit-cert/module.prop\""
# 3. Place cert in module system overlay
adb shell "su -c '
cp /data/local/tmp/0db75fbd.0 /data/adb/modules/httptoolkit-cert/system/etc/security/cacerts/0db75fbd.0
chmod 644 /data/adb/modules/httptoolkit-cert/system/etc/security/cacerts/0db75fbd.0
'"service.sh — write locally, push to device
Important: Write this file on your PC and push via
adb push. Do NOT use shell heredocs withsu -c— the shell expands$MODDIR,$STAGING, etc. before they reach root, breaking the script.
#!/system/bin/sh
# HTTP Toolkit CA cert — APEX bind mount for Android 14+
# Re-applied on every boot via KernelSU/SukiSU/Magisk module
MODDIR=${0%/*}
STAGING=/data/local/tmp/httptoolkit-cacerts
# Wait for boot to complete before touching the APEX mount
until [ "$(getprop sys.boot_completed)" = "1" ]; do
sleep 1
done
sleep 3
# Build staging: all original system certs + ours
rm -rf $STAGING
mkdir -p $STAGING
cp /apex/com.android.conscrypt/cacerts/* $STAGING/ 2>/dev/null
cp $MODDIR/system/etc/security/cacerts/0db75fbd.0 $STAGING/0db75fbd.0
chmod 644 $STAGING/*
chown root:root $STAGING/*
# Bind mount over the APEX conscrypt CA store
mount --bind $STAGING /apex/com.android.conscrypt/cacerts
chcon -R system_file /apex/com.android.conscrypt/cacerts/ 2>/dev/null
# Push and install
adb push "C:\Windows\Temp\service.sh" "/data/local/tmp/service.sh"
adb shell "su -c '
cp /data/local/tmp/service.sh /data/adb/modules/httptoolkit-cert/service.sh
chmod 755 /data/adb/modules/httptoolkit-cert/service.sh
'"#!/system/bin/sh
# HTTP Toolkit CA cert — APEX bind mount for Android 14+
# Re-applied on every boot via KernelSU/SukiSU/Magisk module
MODDIR=${0%/*}
STAGING=/data/local/tmp/httptoolkit-cacerts
# Wait for boot to complete before touching the APEX mount
until [ "$(getprop sys.boot_completed)" = "1" ]; do
sleep 1
done
sleep 3
# Build staging: all original system certs + ours
rm -rf $STAGING
mkdir -p $STAGING
cp /apex/com.android.conscrypt/cacerts/* $STAGING/ 2>/dev/null
cp $MODDIR/system/etc/security/cacerts/0db75fbd.0 $STAGING/0db75fbd.0
chmod 644 $STAGING/*
chown root:root $STAGING/*
# Bind mount over the APEX conscrypt CA store
mount --bind $STAGING /apex/com.android.conscrypt/cacerts
chcon -R system_file /apex/com.android.conscrypt/cacerts/ 2>/dev/null
# Push and install
adb push "C:\Windows\Temp\service.sh" "/data/local/tmp/service.sh"
adb shell "su -c '
cp /data/local/tmp/service.sh /data/adb/modules/httptoolkit-cert/service.sh
chmod 755 /data/adb/modules/httptoolkit-cert/service.sh
'"After the next reboot, the cert is live before any app starts up. Fully automatic.
Every-Session Checklist
The module handles the cert permanently. Each testing session just needs:
# 1. Reverse tunnel (required every new ADB session)
adb reverse --remove-all
adb reverse tcp:8001 tcp:8001
# 2. Clear stale proxy (in case Burp was used previously)
adb shell settings put global http_proxy :0
# 3. Connect from HTTP Toolkit PC → accept VPN dialog on device# 1. Reverse tunnel (required every new ADB session)
adb reverse --remove-all
adb reverse tcp:8001 tcp:8001
# 2. Clear stale proxy (in case Burp was used previously)
adb shell settings put global http_proxy :0
# 3. Connect from HTTP Toolkit PC → accept VPN dialog on deviceThat's it. No cert steps, no root commands, no manual navigation.
Verification
# Cert is in APEX store
adb shell "su -c 'ls /apex/com.android.conscrypt/cacerts/0db75fbd.0'"
# VPN is active
adb shell dumpsys connectivity | grep "VPN CONNECTED"
# VPN service is running
adb shell dumpsys activity services tech.httptoolkit.android.v1 | grep ProxyVpnService
# Tunnel is set
adb reverse --list
# UsbFfs tcp:8001 tcp:8001# Cert is in APEX store
adb shell "su -c 'ls /apex/com.android.conscrypt/cacerts/0db75fbd.0'"
# VPN is active
adb shell dumpsys connectivity | grep "VPN CONNECTED"
# VPN service is running
adb shell dumpsys activity services tech.httptoolkit.android.v1 | grep ProxyVpnService
# Tunnel is set
adb reverse --list
# UsbFfs tcp:8001 tcp:8001Failures at a Glance
Key Takeaways
- Android 14+ completely changed the CA cert pipeline. The APEX conscrypt store is the only location that matters for HTTPS interception.
/system/etc/security/cacerts/is a legacy path that most guides still reference incorrectly. mount --bindis the correct technique, notmount -o remount,rw /system. You're not modifying the APEX package — you're overlaying a new directory over its mount point.- Five things must work at once. "Connecting…" is the failure state for all of them. Use
adb logcatfiltered totech.httptoolkitto find the actual broken step rather than guessing. - Write shell scripts to a file and
adb pushthem. Never pipe multiline scripts throughsu -cwith shell variables — the outer shell expands them before root sees them. - Xiaomi devices hide VPN dialogs. Check the notification shade if you don't see the
com.android.vpndialogs/.ConfirmDialogon screen.
One More Thing — Certificate Pinning
This setup intercepts traffic for apps that don't use certificate pinning. For apps with pinning (most banking apps, many production apps), you'll additionally need:
- LSPosed + TrustMeAlready module — disables pinning for selected apps
- Frida with an SSL unpinning script — runtime hook approach
- APK patching with apktool — modify the
network_security_config.xmlto trust user/system CAs
That's a separate post. But the HTTPS interception setup described here is the foundation all those approaches build on.
Setup time after following this guide: ~10 minutes. Setup time before writing this guide: considerably longer.