Proxy Modes
RelayCore supports three proxy operating modes, covering deployments from a developer laptop to a corporate gateway.
Explicit proxy (default)
The client is explicitly configured to use RelayCore as its HTTP/HTTPS proxy. This is the most common mode for local development and debugging — no OS or client changes beyond setting the proxy URL.
# Start
relay-core-cli run
# Point your browser or system proxy at
# 127.0.0.1:8080 (the default listen address) Once configured, all traffic flows through RelayCore; HTTPS is decrypted using dynamically-issued certificates (see Certificates & HTTPS interception).
Transparent proxy
The client is unaware of the proxy; the OS or network device redirects traffic to RelayCore. Useful for device debugging, mobile devices, embedded devices, and scenarios where you cannot change client config.
relay-core-cli run --transparent macOS (PF)
Use the relay-core-cli proxy subcommand to manage PF rules:
# Generate PF config (defaults to en0, override with --interface)
relay-core-cli proxy generate --port 8080
# Load and enable the rules (requires sudo)
sudo relay-core-cli proxy load --port 8080 --interface en0
# Show current state
relay-core-cli proxy status
# Disable
sudo relay-core-cli proxy unload The generated rules redirect TCP traffic on ports 80/443 to the local proxy port. Supported on macOS only.
Linux (TPROXY)
Enabled via --transparent. Linux transparent proxy requires iptables/nftables and routing-table setup that RelayCore does not manage directly; see the Linux section in Troubleshooting.
UDP TPROXY
relay-core-cli run --transparent --udp-tproxy-port 8080 Enables UDP transparent proxy. Linux only; requires IP_TRANSPARENT kernel support.
Upstream proxy (chained proxying)
Place RelayCore behind another proxy. All outbound traffic goes to RelayCore first, which then forwards through a parent proxy. Typical for corporate proxies, VPN concentrators, and TLS-inspecting middleboxes.
# Plain HTTP upstream
relay-core-cli run --upstream http://corp-proxy.internal:8080
# HTTPS upstream (TLS to the proxy itself)
relay-core-cli run --upstream https://secure-proxy.internal:8443
# With Basic auth
export RELAYCORE_UPSTREAM_PASSWORD='s3cret'
relay-core-cli run --upstream http://corp-proxy.internal:8080 --upstream-auth-user alice
# Fall back to direct if upstream is unreachable (default fail-closed)
relay-core-cli run --upstream http://corp-proxy.internal:8080 --upstream-fail-open Bypass list
Specify targets that should not go through the upstream with --upstream-bypass. Three pattern kinds, freely mixed:
--upstream-bypass "127.0.0.1,::1,localhost,*.internal,cidr:10.0.0.0/8,cidr:192.168.0.0/16" | Pattern | Matches |
|---|---|
127.0.0.1 | Literal IPv4 / IPv6 address |
localhost | Literal hostname (case-insensitive) |
*.internal | Glob — matches subdomains |
cidr:10.0.0.0/8 | CIDR block (IPv4 / IPv6) |
In transparent mode the original destination IP is also checked against the bypass list, so loopback and RFC1918 traffic to local services stays local even when the upstream is up.
HTTP vs HTTPS upstream
When the URL starts with https://, RelayCore performs a full TLS handshake to the proxy before sending CONNECT. The upstream certificate is validated against the system trust store. In private-CA environments, import the CA into the OS trust store.
Mutating at runtime
Upstream is part of the ProxyPolicy and can be patched through the REST API:
# Read the current policy
curl -s http://127.0.0.1:8082/api/v1/policy
# Update only the upstream section
curl -s -X PATCH http://127.0.0.1:8082/api/v1/policy \
-H 'Content-Type: application/json' \
-d '{
"upstream": {
"proxy_url": "http://corp-proxy.internal:8080",
"bypass_hosts": ["*.internal", "127.0.0.1"],
"fail_open": false
}
}' Changing proxy_url at runtime returns 409 Conflict because existing connections are already pinned to the old path. Restart the proxy to apply.
Security notes
- The
passwordfield usessecrecy::SecretString. TheDebugimpl, theGET /api/v1/policyresponse, and the JSON serializer all mask it as***. - When the upstream is enforcing policy, keep
--upstream-fail-open=false(the default) — silently going direct would defeat the point. - Policy changes are written to
/api/v1/auditwithkind=policy_updatedand the actor that triggered the change.