Are you debugging a mobile application and don't understand what requests it sends to the server? Do you need to test API behavior under different conditions or intercept a response and modify data on the fly? mitmproxy solves all these tasks — it is a free, open-source tool that allows you to fully control HTTP and HTTPS traffic between the client and the server.
What is mitmproxy and why is it needed by developers
mitmproxy is an interactive MITM (Man-In-The-Middle) proxy with open source code written in Python. It acts as a mediator between your application and the server: intercepting all requests and responses, allowing you to view, modify, replay, and save them.
The main difference between mitmproxy and regular proxy servers is the ability to work with encrypted HTTPS traffic. The tool dynamically generates SSL certificates for each domain, allowing traffic to be decrypted on the fly without disrupting the application's operation.
Here are typical tasks that developers solve using mitmproxy:
- API Debugging — see exact requests and responses, including headers, body, and status codes.
- Reverse Engineering — analyze how third-party applications and services work.
- Testing — substitute server responses to check edge cases.
- Automation — write scripts to modify traffic based on conditions.
- Recording and Replaying — save a session and replay it without a real server.
- Security Analysis — check if the application is sending unnecessary data.
It is important to understand
mitmproxy is a tool for legal testing and development. Use it only to analyze the traffic of applications that you are developing or have permission to test. Intercepting someone else's traffic without permission violates the law.
The tool comes in three variants: the console interactive interface mitmproxy, the web interface mitmweb, and the command-line utility mitmdump. All three use the same core and support Python scripts.
Installing mitmproxy on Windows, macOS, and Linux
mitmproxy can be installed in several ways. We recommend using pip — this ensures you have the latest version and easy updates.
Installation via pip (universal method)
Python 3.9 or newer is required. Check your Python version:
python --version
# or
python3 --version
Install mitmproxy:
pip install mitmproxy
After installation, check:
mitmproxy --version
# Should output: mitmproxy 10.x.x
Installation via package managers
macOS (Homebrew):
brew install mitmproxy
Linux (Ubuntu/Debian):
sudo apt install mitmproxy
# or via snap for the latest version:
sudo snap install mitmproxy
Windows: Download the installer from the official site mitmproxy.org or use pip in PowerShell with administrator rights.
Starting and basic verification
By default, mitmproxy listens on port 8080. Start the web interface to get started:
# Start the web interface on port 8080
mitmweb
# Start on another port
mitmweb --listen-port 9090
# Console interface
mitmproxy
After starting mitmweb, open your browser at http://127.0.0.1:8081 — this is the web interface for viewing traffic. The proxy itself runs on port 8080.
Configuring SSL certificates for intercepting HTTPS
Intercepting HTTPS traffic requires installing the mitmproxy root certificate in the system or browser. Without this step, the browser will show a warning about an insecure connection, and many applications will refuse to work altogether.
How it works technically
When mitmproxy is first launched, it automatically creates a root CA certificate and saves it in the ~/.mitmproxy/ directory. When a client connects to an HTTPS site through the proxy, mitmproxy dynamically generates a certificate for that domain, signing it with its CA. The client trusts this certificate if the CA is added to the trusted list — and decryption occurs transparently.
Installing the certificate in the system
Certificates are located in ~/.mitmproxy/:
mitmproxy-ca-cert.pem— for Linux/macOSmitmproxy-ca-cert.cer— for Windowsmitmproxy-ca-cert.p12— for iOS
macOS:
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain \
~/.mitmproxy/mitmproxy-ca-cert.pem
Linux (Ubuntu/Debian):
sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem \
/usr/local/share/ca-certificates/mitmproxy.crt
sudo update-ca-certificates
Windows: Double-click the file mitmproxy-ca-cert.cer → "Install Certificate" → "Local Computer" → "Trusted Root Certification Authorities".
Installing in Firefox browser
Firefox uses its own certificate store. Go to: Settings → Privacy & Security → Certificates → View Certificates → Authorities → Import. Select the file mitmproxy-ca-cert.pem and check "Trust this CA to identify websites".
Verification
Configure the browser to use the proxy 127.0.0.1:8080 and open any HTTPS site. In the mitmweb interface, you should see decrypted traffic. Alternatively, open http://mitm.it through the configured proxy: mitmproxy will show instructions for installing the certificate for your platform.
Three interfaces: mitmproxy, mitmweb, and mitmdump
The mitmproxy package includes three utilities with different interfaces for different work scenarios. Understanding the differences will help you choose the right tool for each task.
| Utility | Interface | When to use | Features |
|---|---|---|---|
mitmproxy |
Console TUI | Interactive debugging in the terminal | Requires a terminal with color support, powerful filtering |
mitmweb |
Web browser | Visual traffic analysis | User-friendly UI, filter support, export |
mitmdump |
CLI (stdout) | Scripts, CI/CD, automation | No interactivity, output to file or pipe |
Useful launch flags
# Record traffic to a file
mitmdump -w traffic.dump
# Replay recorded traffic
mitmdump -r traffic.dump
# Filtering: only requests to a specific domain
mitmproxy --filter "~d api.example.com"
# Running in transparent proxy mode
mitmproxy --mode transparent
# Running as an upstream proxy (proxy chain)
mitmproxy --mode upstream:http://upstream-proxy:8080
# Specifying a specific port
mitmweb --listen-port 9090 --web-port 9091
# Running with a script
mitmproxy -s my_script.py
mitmproxy filter syntax
mitmproxy supports a powerful filter language for selecting the desired requests:
# ~d — domain filter
~d api.example.com
# ~u — URL filter (regex)
~u /api/v2/users
# ~m — HTTP method filter
~m POST
# ~s — only responses
~s ~c 404
# ~c — status code filter
~c 500
# Combining (AND)
~d api.example.com & ~m POST
# Combining (OR)
~c 404 | ~c 500
# NOT
!~d static.example.com
Writing Python scripts: intercepting and modifying traffic
Scripts are the main superpower of mitmproxy. With them, you can automatically modify requests and responses, log data in the desired format, simulate server errors, and much more. Scripts are written in Python and use an event-driven model.
Main events (hooks)
| Hook | When it is called | Object |
|---|---|---|
request |
A request is received from the client | flow.request |
response |
A response is received from the server | flow.response |
error |
Connection error | flow.error |
tls_start_client |
Start of TLS handshake with the client | tls_start |
Example 1: Logging requests to a file
# logger.py
import mitmproxy.http
import json
from datetime import datetime
def request(flow: mitmproxy.http.HTTPFlow) -> None:
"""Log each request to a JSON file."""
log_entry = {
"timestamp": datetime.now().isoformat(),
"method": flow.request.method,
"url": flow.request.pretty_url,
"headers": dict(flow.request.headers),
"body": flow.request.text if flow.request.text else None
}
with open("requests.log", "a") as f:
f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
def response(flow: mitmproxy.http.HTTPFlow) -> None:
"""Log responses with error codes."""
if flow.response.status_code >= 400:
print(f"[ERROR] {flow.request.method} {flow.request.pretty_url} "
f"-> {flow.response.status_code}")
Run the script:
mitmproxy -s logger.py
Example 2: Modifying requests — changing headers
# modify_headers.py
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
"""Change User-Agent and add a custom header."""
if "api.example.com" in flow.request.pretty_host:
# Change User-Agent
flow.request.headers["User-Agent"] = (
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) "
"AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"
)
# Add authorization header
flow.request.headers["X-Custom-Token"] = "test-token-12345"
# Remove header
if "X-Debug-Info" in flow.request.headers:
del flow.request.headers["X-Debug-Info"]
Example 3: Mocking server response
# mock_response.py
from mitmproxy import http
import json
def request(flow: http.HTTPFlow) -> None:
"""Intercept a request and return a mock response without contacting the server."""
if flow.request.pretty_url.endswith("/api/v1/user/profile"):
# Create mock response
mock_data = {
"id": 42,
"name": "Test User",
"email": "[email protected]",
"premium": True # Testing premium functionality
}
flow.response = http.Response.make(
200, # Status code
json.dumps(mock_data), # Response body
{"Content-Type": "application/json"} # Headers
)
def response(flow: http.HTTPFlow) -> None:
"""Modify the real server response."""
if "/api/v1/products" in flow.request.pretty_url:
try:
data = json.loads(flow.response.text)
# Add a field to each product
for product in data.get("items", []):
product["debug_info"] = "intercepted"
flow.response.text = json.dumps(data)
except (json.JSONDecodeError, KeyError):
pass
Example 4: Simulating slow connection and errors
# chaos_testing.py
from mitmproxy import http
import time
import random
def response(flow: http.HTTPFlow) -> None:
"""Chaos engineering: random delays and errors for testing."""
# Add a random delay from 0 to 2 seconds
if "api.example.com" in flow.request.pretty_host:
delay = random.uniform(0, 2.0)
time.sleep(delay)
# 10% chance of a 503 response
if random.random() < 0.1:
flow.response = http.Response.make(
503,
json.dumps({"error": "Service Unavailable"}),
{"Content-Type": "application/json"}
)
Intercepting mobile application traffic
Analyzing mobile application traffic is one of the most common tasks when using mitmproxy. This is especially useful for reverse engineering mobile application APIs or testing your own application on a real device.
Setup on Android
Step 1. Ensure that the phone and computer are on the same Wi-Fi network.
Step 2. Run mitmproxy on the computer:
mitmweb --listen-host 0.0.0.0 --listen-port 8080
Step 3. On Android: Settings → Wi-Fi → hold the network → Modify network → Advanced → Proxy → Manual. Enter the computer's IP and port 8080.
Step 4. Installing the certificate on Android: open the browser on the device, go to http://mitm.it and download the certificate for Android. Then: Settings → Security → Install certificate → CA certificate.
Android 7+ and Certificate Pinning
Starting from Android 7.0, applications do not trust user CA certificates by default. To intercept traffic from such applications, either root access is required, or modification of network_security_config.xml in the APK. For applications with SSL Pinning, use Frida or Xposed Framework to bypass certificate verification.
Setup on iOS
Step 1. Set up the proxy similarly to Android: Settings → Wi-Fi → tap (i) next to the network → Configure Proxy → Manual.
Step 2. Open Safari and go to http://mitm.it — download the certificate for iOS.
Step 3. Install the profile: Settings → Profile downloaded → Install.
Step 4. Enable trust for the certificate: Settings → General → About → Trust Certificates — enable the switch for mitmproxy.
Intercepting traffic from a specific application via Python
# mobile_app_analyzer.py
from mitmproxy import http
import json
import re
# Domains of the target application
TARGET_DOMAINS = ["api.myapp.com", "cdn.myapp.com"]
def response(flow: http.HTTPFlow) -> None:
"""Analyze the traffic of the mobile application."""
host = flow.request.pretty_host
if not any(domain in host for domain in TARGET_DOMAINS):
return
# Extract JSON responses
content_type = flow.response.headers.get("content-type", "")
if "application/json" in content_type:
try:
data = json.loads(flow.response.text)
print(f"\n{'='*60}")
print(f"URL: {flow.request.pretty_url}")
print(f"Status: {flow.response.status_code}")
print(f"Response: {json.dumps(data, indent=2, ensure_ascii=False)}")
except json.JSONDecodeError:
pass
# Look for tokens in request headers
auth_header = flow.request.headers.get("authorization", "")
if auth_header:
print(f"[AUTH] Token found: {auth_header[:50]}...")
Proxy chain: mitmproxy + upstream proxy
One of the powerful scenarios is using mitmproxy in conjunction with an external proxy server. This allows you to simultaneously intercept and analyze traffic (through mitmproxy) and direct it through an external IP address (through the upstream proxy). This scheme is used when testing geo-dependent APIs or when developing applications that must work through a proxy.
Upstream proxy mode
# Direct all traffic through upstream HTTP proxy
mitmproxy --mode upstream:http://proxy-host:port
# Upstream SOCKS5 proxy
mitmproxy --mode upstream:socks5://proxy-host:port
# With authentication
mitmproxy --mode upstream:http://user:password@proxy-host:port
# Through mitmweb with upstream
mitmweb --mode upstream:http://proxy-host:port
In this mode, mitmproxy accepts requests locally, decrypts HTTPS, allows you to analyze and modify them, and then forwards them through an external proxy. This is especially convenient when testing APIs that are only accessible from certain regions.
For such tasks, residential proxies are well suited — they have real IP addresses of home users from the required countries, allowing you to correctly test geo-dependent API responses.
Dynamic selection of upstream proxy in a script
# dynamic_upstream.py
from mitmproxy import http
from mitmproxy.net.server_spec import ServerSpec
# List of proxies for rotation
PROXY_LIST = [
"http://proxy1.example.com:8080",
"http://proxy2.example.com:8080",
"http://proxy3.example.com:8080",
]
proxy_index = 0
def request(flow: http.HTTPFlow) -> None:
"""Rotate upstream proxies for each request."""
global proxy_index
# Direct API requests through different proxies
if "api.target.com" in flow.request.pretty_host:
proxy_url = PROXY_LIST[proxy_index % len(PROXY_LIST)]
proxy_index += 1
flow.live.change_upstream_proxy_server(
ServerSpec.from_url(proxy_url)
)
print(f"Using proxy: {proxy_url} for {flow.request.pretty_url}")
Transparent proxy (transparent mode)
In transparent mode, the application does not know that its traffic is being intercepted — there is no need to configure the proxy in the settings. This requires setting up iptables/pf at the OS level:
# Running in transparent mode
mitmproxy --mode transparent --listen-port 8080
# Setting up iptables to redirect traffic (Linux)
sudo iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port 8080
sudo iptables -t nat -A OUTPUT -p tcp --dport 443 -j REDIRECT --to-port 8080
Practical use cases of mitmproxy
Let's consider specific tasks that developers solve using mitmproxy in real projects.
Scenario 1: Testing API without changing the server
Imagine: you need to check how the frontend handles a response with an empty data array or a 401 authorization error, but reproducing this on a test server is difficult. mitmproxy allows you to substitute the response on the fly:
# test_edge_cases.py
from mitmproxy import http
import json
def response(flow: http.HTTPFlow) -> None:
url = flow.request.pretty_url
# Test: empty product list
if "/api/products" in url and "test_empty=1" in url:
flow.response.text = json.dumps({"items": [], "total": 0})
# Test: expired token
if "/api/user" in url and "test_auth=1" in url:
flow.response = http.Response.make(
401,
json.dumps({"error": "Token expired", "code": "AUTH_001"}),
{"Content-Type": "application/json"}
)
# Test: exceeding request limit
if "/api/" in url and "test_rate=1" in url:
flow.response = http.Response.make(
429,
json.dumps({"error": "Too Many Requests", "retry_after": 60}),
{"Content-Type": "application/json",
"Retry-After": "60"}
)
Scenario 2: Recording and replaying a session
Useful for creating test fixtures or demonstrating functionality without a real server:
# Recording a session to a file
mitmdump -w session.dump --filter "~d api.example.com"
# Replaying the recorded session (offline)
mitmdump -r session.dump
# Converting to HAR format for analysis
mitmdump -r session.dump --flow-detail 3 > session.txt
Scenario 3: Automatic API documentation
# api_documenter.py
from mitmproxy import http
import json
from collections import defaultdict
# Dictionary for accumulating information about endpoints
endpoints = defaultdict(lambda: {"methods": set(), "status_codes": set(),
"request_fields": set(), "response_fields": set()})
def _extract_fields(data, prefix=""):
"""Recursively extract fields from JSON."""
fields = set()
if isinstance(data, dict):
for key, value in data.items():
full_key = f"{prefix}.{key}" if prefix else key
fields.add(full_key)
fields.update(_extract_fields(value, full_key))
elif isinstance(data, list) and data:
fields.update(_extract_fields(data[0], prefix))
return fields
def response(flow: http.HTTPFlow) -> None:
if "api.example.com" not in flow.request.pretty_host:
return
# Normalize URL (remove ID)
import re
path = re.sub(r'/\d+', '/{id}', flow.request.path)
endpoint = f"{flow.request.method} {path}"
ep = endpoints[endpoint]
ep["methods"].add(flow.request.method)
ep["status_codes"].add(flow.response.status_code)
# Extract request fields
if flow.request.text:
try:
req_data = json.loads(flow.request.text)
ep["request_fields"].update(_extract_fields(req_data))
except json.JSONDecodeError:
pass
# Extract response fields
if flow.response.text:
try:
resp_data = json.loads(flow.response.text)
ep["response_fields"].update(_extract_fields(resp_data))
except json.JSONDecodeError:
pass
def done():
"""Output documentation upon completion."""
print("\n=== API DOCUMENTATION ===\n")
for endpoint, info in sorted(endpoints.items()):
print(f"Endpoint: {endpoint}")
print(f" Status codes: {sorted(info['status_codes'])}")
if info["request_fields"]:
print(f" Request fields: {sorted(info['request_fields'])}")
if info["response_fields"]:
print(f" Response fields: {sorted(info['response_fields'])}")
print()
Scenario 4: Testing geo-dependent responses
When developing applications with regional content, it is important to check how the API responds to requests from different countries. For this, mitmproxy is run in upstream mode with data center proxies from the required regions — this is a fast and reliable way to simulate requests from specific countries.
# geo_test.py
from mitmproxy import http
def response(flow: http.HTTPFlow) -> None:
"""Log geo-dependent headers and data."""
# Check what content the server returns
geo_headers = ["cf-ipcountry", "x-country", "x-geo-country"]
for header in geo_headers:
value = flow.response.headers.get(header)
if value:
print(f"[GEO] {header}: {value} | URL: {flow.request.pretty_url}")
# Look for mentions of currencies and locales in the response
if flow.response.text:
import re
currencies = re.findall(r'"currency":\s*"([A-Z]{3})"', flow.response.text)
locales = re.findall(r'"locale":\s*"([a-z]{2}-[A-Z]{2})"', flow.response.text)
if currencies:
print(f"[CURRENCY] {currencies}")
if locales:
print(f"[LOCALE] {locales}")
Scenario 5: Using mitmproxy in CI/CD
mitmdump is perfect for integration tests in a CI/CD pipeline — it runs as a background process, records traffic, and terminates along with the tests:
#!/bin/bash
# ci_test.sh
# Run mitmdump in the background
mitmdump -w test_traffic.dump -s ci_assertions.py &
MITM_PID=$!
# Give the proxy time to start
sleep 1
# Run tests with the proxy
export HTTP_PROXY=http://127.0.0.1:8080
export HTTPS_PROXY=http://127.0.0.1:8080
pytest tests/integration/ -v
# Stop mitmdump
kill $MITM_PID
# Analyze recorded traffic
mitmdump -r test_traffic.dump -s analyze_traffic.py
The script ci_assertions.py can check that the application does not make unnecessary requests, does not transmit sensitive data in unencrypted form, and complies with API contracts.
Scenario 6: Analyzing traffic of a parser
When developing parsers, mitmproxy helps to understand what requests the browser makes when loading a page — including XHR/fetch requests to APIs that are not visible in the HTML source code. This allows direct access to the site's API instead of parsing HTML. When developing such solutions, residential proxies are often used for IP rotation to avoid blocks when collecting data.
Conclusion
mitmproxy is one of the most powerful tools in a developer's arsenal for working with HTTP/HTTPS traffic. It combines the functions of a debugger, testing environment, API documentation tool, and security analysis tool. The three interfaces — console, web, and CLI — cover all scenarios: from interactive debugging to automation in CI/CD.
```