Skip to content

Proxy Control

The proxy control API manages Ghost’s core traffic interception engine. These endpoints let you start and stop the MITM proxy, configure the operating system to route traffic through Ghost, simulate slow network conditions, bypass SSL interception for specific hosts, manage the CA certificate, and query network information.

Think of these endpoints as the control panel for Ghost’s traffic capture — they’re the switches and dials that determine whether traffic is being intercepted, how it’s being modified (throttled, bypassed), and whether the operating system knows to send traffic through Ghost in the first place.

GET /api/v1/proxy/status

Returns the current state of the proxy server — whether it’s running, whether it can actually capture traffic, which port it’s listening on, and the health of the network connection.

Response:

{
"running": true,
"capturing": true,
"port": 4545,
"addr": ":4545",
"session_id": "01HWXYZ...",
"network_available": true
}
FieldTypeDescription
runningbooleanWhether the proxy listener is active (TCP socket is open and accepting connections)
capturingbooleanWhether traffic can actually flow through Ghost — a computed field that goes beyond just “running” (see below)
portintegerThe TCP port the proxy is listening on. If the proxy was configured with port 0 (ephemeral), this shows the actual port assigned by the OS
addrstringThe full listener address string (e.g., ":4545" meaning all interfaces on port 4545)
session_idstringThe ID of the currently active session — all captured flows are saved to this session
network_availablebooleanWhether the host machine has a working network connection (tracked by the watchdog, see below)

The capturing field is smarter than just checking if the proxy is running. It answers the question: “If a request happened right now, would Ghost actually see it?” The logic depends on how Ghost is configured to intercept traffic:

  • Network down: If the host machine has no network connectivity, capturing is always false regardless of other settings — there’s nothing to capture
  • Manual proxy or device mode: If the user configured their browser or device to point at Ghost manually (not using the system proxy), capturing is true whenever running is true — Ghost is ready to receive traffic, the routing is the user’s responsibility
  • System proxy mode: If Ghost is configured to use the OS system proxy, capturing checks that: (1) the proxy is running, (2) the OS system proxy is enabled, AND (3) the system proxy is actually pointing at Ghost’s port (not at some other proxy). This catches the edge case where a VPN or security agent has overridden the system proxy settings
POST /api/v1/proxy/start

Starts the MITM proxy listener. No request body needed.

What happens internally:

  1. Checks if already running — returns 409 Conflict if the proxy is already active
  2. Starts the listener — opens the TCP socket on the configured port. Port binding is synchronous, so “port already in use” errors fail immediately with 500
  3. Flushes stale connections — calls ResetConnections() to clear any idle HTTP/1.1 and HTTP/2 connections left over from a previous run (these would try to reuse connections to servers that may have timed them out)
  4. Enables system proxy (if configured) — when the proxy method is set to "system_proxy", Ghost tells the OS to route all traffic through itself using a PAC (Proxy Auto-Config) file
  5. Detects VPN interference — checks if a VPN or security agent (like GlobalProtect) is overriding the proxy settings at the macOS SCDynamicStore level. If detected, requests admin privileges to override the VPN’s settings so Ghost’s proxy takes priority
  6. Starts the watchdog — launches a background goroutine that monitors network health and proxy drift every 5 seconds (see Watchdog section below)
  7. Broadcasts a proxy.started WebSocket event with the full proxy status

Response: {"ok": true}

Errors: 409 (already running), 500 (failed to bind port)

POST /api/v1/proxy/stop

Gracefully stops the proxy. No request body needed.

What happens internally (order matters):

  1. Checks if running — returns 409 Conflict if the proxy isn’t active
  2. Stops the watchdog — halts the background monitoring goroutine
  3. Restores VPN state — if Ghost had overridden a VPN’s proxy settings, restores them
  4. Disables system proxy FIRST — this is critical. The system proxy must be disabled before the listener shuts down. If the listener closed first, all system traffic would be routed to a dead port, breaking the user’s internet connection until the system proxy is cleaned up
  5. Retry logic for disable — disabling the system proxy can fail transiently (especially on macOS during network interface transitions), so Ghost retries up to 3 times with a 500ms delay between attempts
  6. Stops the listener — closes the TCP socket and drains active connections
  7. Broadcasts a proxy.stopped WebSocket event

Response: {"ok": true}

Errors: 409 (not running), 500 (failed to stop)

Not an API endpoint, but critical background behavior that starts when the proxy starts. The watchdog runs every 5 seconds and handles three scenarios:

Network goes down (was up, now down):

  • Immediately disables the system proxy — if the upstream network is dead, routing traffic through Ghost just adds a hop to a dead end
  • Broadcasts a proxy.status event so the frontend shows the network-down state

Network comes back (was down, now up):

  • Flushes all stale connections (old HTTP keep-alive connections won’t work after a network drop)
  • Waits 2 seconds for the network stack to stabilize (DHCP lease renewal, DNS resolution, route table updates)
  • Re-enables the system proxy
  • Broadcasts a proxy.status event

Proxy drift (network is stable):

  • Checks if all network services still have Ghost’s proxy settings — on macOS, VPN clients, security agents, and network interface changes can silently reset the proxy configuration
  • If drift is detected, re-applies Ghost’s proxy settings
  • Also checks the effective proxy via scutil --proxy — if a VPN has overridden Ghost at the SCDynamicStore level mid-session, applies the VPN state override
POST /api/v1/proxy/system/enable

Configures the operating system to route HTTP/HTTPS traffic through Ghost’s proxy.

Request body (1 MB limit):

{
"host": "127.0.0.1",
"port": 4545
}
FieldRequiredDefaultDescription
hostNo"127.0.0.1"The host address to set in the system proxy configuration
portYesThe proxy port. Must be greater than 0

Response: {"ok": true}

Errors: 400 (invalid body or port ≤ 0), 501 (platform doesn’t support system proxy — e.g., some Linux configurations), 500 (enable failed)

POST /api/v1/proxy/system/disable

Removes the system proxy configuration, returning the OS to its default “direct connection” state. No request body needed.

Response: {"ok": true}

Errors: 501 (unsupported platform), 500 (disable failed)

GET /api/v1/proxy/system/status

Checks whether the OS system proxy is currently enabled and where it’s pointing.

Response:

{
"enabled": true,
"host": "127.0.0.1",
"port": 4545
}
FieldTypeDescription
enabledbooleanWhether the OS is currently routing traffic through a proxy
hoststringThe proxy host address configured in the OS
portintegerThe proxy port configured in the OS

Errors: 501 (unsupported platform), 500 (status check failed)

GET /proxy.pac

No authentication required — this endpoint is accessed by the OS proxy auto-configuration system, which can’t include auth headers.

Serves a PAC (Proxy Auto-Configuration) file — a small JavaScript function that the OS evaluates for every HTTP request to decide whether to use a proxy.

Ghost’s PAC file routes:

  • Direct (no proxy): localhost, 127.0.0.1, ::1, and plain hostnames (no dots — these are typically local network names)
  • Through Ghost: Everything else → PROXY 127.0.0.1:{proxyPort}; DIRECT (try Ghost first, fall back to direct if Ghost is unreachable)

Content-Type: application/x-ns-proxy-autoconfig Cache-Control: no-cache (prevents the OS from caching stale PAC files after Ghost restarts on a different port)

Ghost can simulate slow network conditions by limiting bandwidth and adding latency. This is useful for testing how your application behaves on slow connections (3G, EDGE) or high-latency networks.

GET /api/v1/proxy/throttle

Returns the current throttling configuration.

When throttling is disabled:

{
"enabled": false
}

When throttling is enabled:

{
"enabled": true,
"profile": {
"name": "3G",
"download_kbps": 750,
"upload_kbps": 250,
"latency_ms": 100
}
}
POST /api/v1/proxy/throttle

Activates network throttling. You can either use a built-in preset name or specify custom values.

Using a preset:

{
"preset": "slow-3g"
}

Using custom values:

{
"download_kbps": 500,
"upload_kbps": 200,
"latency_ms": 150
}

Validation:

  • If preset is provided, it must match one of the preset keys (see table below) — otherwise returns 400
  • If no preset, at least one of download_kbps, upload_kbps, or latency_ms must be non-zero — setting all three to zero is meaningless (that’s just “no throttle”)
  • A value of 0 for any field means “unlimited” (no restriction on that dimension)

Response: The throttle state after applying (same format as GET)

Broadcasts a proxy.throttle WebSocket event with the new state.

Preset KeyDisplay NameDownload (kbps)Upload (kbps)Latency (ms)
"slow-3g"Slow 3G400400200
"3g"3G750250100
"4g"4G4,0003,00020
"wifi"WiFi10,0005,0002
"edge"EDGE240200300

These simulate real-world network conditions. The implementation uses golang.org/x/time/rate token buckets with a 32 KB burst size — data is released in chunks rather than byte-by-byte, which models real network behavior (data arrives in packets, not continuous streams). Latency is injected once per connection on the first read, simulating the initial connection delay.

DELETE /api/v1/proxy/throttle

Removes all throttling — traffic flows at full speed. No request body needed.

Response: {"ok": true}

Broadcasts a proxy.throttle WebSocket event with {"enabled": false}.

GET /api/v1/proxy/throttle/presets

Returns all built-in throttle profiles as a JSON array.

Response:

[
{"name": "3G", "download_kbps": 750, "upload_kbps": 250, "latency_ms": 100},
{"name": "Slow 3G", "download_kbps": 400, "upload_kbps": 400, "latency_ms": 200},
{"name": "4G", "download_kbps": 4000, "upload_kbps": 3000, "latency_ms": 20},
{"name": "WiFi", "download_kbps": 10000, "upload_kbps": 5000, "latency_ms": 2},
{"name": "EDGE", "download_kbps": 240, "upload_kbps": 200, "latency_ms": 300}
]

Some hosts should not be intercepted — either because they use certificate pinning (your app checks that the certificate is exactly the one it expects, not Ghost’s generated certificate) or because you simply don’t need to see that traffic. SSL bypass tells Ghost to pass these connections through as raw TCP tunnels without any interception.

GET /api/v1/proxy/ssl-bypass

Returns the current list of SSL bypass host patterns.

Response:

{
"hosts": ["*.apple.com", "accounts.google.com"]
}

The hosts field may be null if no bypass hosts are configured (empty list).

PUT /api/v1/proxy/ssl-bypass

Replaces the entire bypass list (not append — send the complete list).

Request body (1 MB limit):

{
"hosts": ["*.apple.com", "accounts.google.com", "*.googleapis.com"]
}

Patterns support:

  • Exact match: accounts.google.com — only that exact hostname
  • Wildcard suffix: *.apple.com — any subdomain of apple.com (e.g., push.apple.com, api.apple.com)

The bypass list is both applied to the running proxy immediately (in-memory update) and persisted to the config file (so it survives restarts). The config is copied before mutation to avoid race conditions with concurrent readers.

Response: {"ok": true}

Ghost generates its own Certificate Authority (CA) to create per-host certificates on the fly. These endpoints manage the CA certificate — downloading it, checking its status, installing it into the OS trust store, and verifying the installation.

GET /api/v1/cert/ca.crt

Downloads Ghost’s CA certificate in PEM format. This is the certificate you need to install on devices or in browsers to trust Ghost’s generated certificates.

Content-Type: application/x-pem-file Content-Disposition: attachment; filename="ghost-ca.crt"

Returns 404 if no CA certificate is loaded (shouldn’t happen in normal operation — the CA is created on first run).

GET /api/v1/cert/status

Returns detailed information about the loaded CA certificate.

Response:

{
"loaded": true,
"subject": "Ghost Root CA",
"issuer": "Ghost Root CA",
"not_before": "2024-01-01T00:00:00Z",
"not_after": "2034-01-01T00:00:00Z",
"serial": "123456789...",
"is_ca": true
}
FieldTypeDescription
loadedbooleanWhether a CA certificate is loaded in memory. If false, all other fields are zero-valued
subjectstringThe certificate’s Common Name (CN) — always “Ghost Root CA”
issuerstringWho signed this certificate — same as subject since it’s self-signed
not_beforeISO 8601When the certificate becomes valid (1 hour backdated from creation to handle clock skew)
not_afterISO 8601When the certificate expires (10 years from creation)
serialstringThe certificate’s serial number (128-bit random value)
is_cabooleanWhether this is a CA certificate (always true — this is the root CA, not a leaf cert)
POST /api/v1/cert/install

Installs Ghost’s CA certificate into the operating system’s trust store so the OS (and browsers that use the OS store) will trust Ghost’s generated certificates. No request body needed.

Platform behavior:

  • macOS: Adds to the Login Keychain using the security command-line tool (60-second timeout, 10-second verification)
  • Windows: Adds to the Current User Root store using certutil -user (10-second timeout, no UAC prompt needed)
  • Linux: Installs to the system CA bundle — auto-detects the distro family from /etc/os-release (Debian, Red Hat, Arch, SUSE) and uses the appropriate command (30-second timeout, requires sudo)

Response: {"ok": true}

Errors: 501 (unsupported platform), 500 (installation failed)

GET /api/v1/cert/installed

Checks whether Ghost’s CA certificate is currently installed and trusted by the OS.

Response:

{
"installed": true,
"store": "Login Keychain",
"method": "security"
}
FieldTypeDescription
installedbooleanWhether Ghost’s CA cert is in the OS trust store
storestringWhich trust store it was found in (platform-specific name)
methodstringThe tool/method used to check (platform-specific)

Errors: 501 (unsupported platform), 500 (check failed)

GET /api/v1/network/info

Returns the machine’s local IP addresses and the proxy port. Used by the frontend to generate QR codes and display setup instructions for mobile devices.

Response:

{
"local_ips": ["192.168.1.100", "10.0.0.5"],
"proxy_port": 4545
}

The local_ips array contains all non-loopback IPv4 addresses found on the machine’s network interfaces. This typically includes your Wi-Fi address and any VPN tunnel addresses.

EventTriggerPayload
proxy.startedProxy started via POST /startFull ProxyStatusResponse (running, capturing, port, addr, session_id, network_available)
proxy.stoppedProxy stopped via POST /stopFull ProxyStatusResponse
proxy.statusNetwork state change or proxy drift detected by watchdogFull ProxyStatusResponse
proxy.throttleThrottle set or cleared{"enabled": true, "profile": {...}} or {"enabled": false}
MethodPathPurpose
GET/proxy.pacPAC file for auto-configuration (no auth)
GET/api/v1/proxy/statusCurrent proxy state
POST/api/v1/proxy/startStart the proxy
POST/api/v1/proxy/stopStop the proxy
POST/api/v1/proxy/system/enableEnable OS system proxy
POST/api/v1/proxy/system/disableDisable OS system proxy
GET/api/v1/proxy/system/statusCheck OS system proxy state
GET/api/v1/proxy/throttleGet current throttle
POST/api/v1/proxy/throttleSet throttle (preset or custom)
DELETE/api/v1/proxy/throttleClear throttle
GET/api/v1/proxy/throttle/presetsList built-in throttle profiles
GET/api/v1/proxy/ssl-bypassGet SSL bypass host list
PUT/api/v1/proxy/ssl-bypassSet SSL bypass host list
GET/api/v1/cert/ca.crtDownload CA certificate
GET/api/v1/cert/statusCA certificate details
POST/api/v1/cert/installInstall CA to OS trust store
GET/api/v1/cert/installedCheck CA installation
GET/api/v1/network/infoLocal IPs and proxy port