MEHS / Knowledge base

MEHS configuration reference

This is the authoritative schema 1 reference for the client session file and server deployment file shipped in 0.9.x. It lists accepted field names, JSON types, defaults, constraints, and validation rules. Examples are copy-ready except for deployment-specific paths and credentials. Unknown fields are rejected, including inside nested objects; a file must match the schema implemented by the binary that reads it.

Server policy wins. The client states what it wants; the server returns the effective values and any corrections (printed as server policy adjusted ...). For example a client asking for 1 UDP connection against a server limited to 0 gets 0.

Schema 1 contract and evolution

Client session configuration and server deployment configuration are separate files, but both use schema_version: 1. The client currently defaults an omitted schema_version to 1; the server requires it explicitly. Include the field in all new files. Both parsers reject unknown fields and unknown enum values, including keys nested under tun, apps, policy, and each clients item. Missing optional fields use the defaults in the tables below. Invalid types, out-of-range values, incompatible transport-pool combinations, malformed CIDRs, and unsupported profile names fail validation or server startup; they are not silently ignored.

Schema 1 core field names are soft-frozen: do not silently rename, remove, or change their interpretation. A future field may be added to schema 1 only when it is optional, has a default that preserves existing behavior, and does not invalidate configurations that omit it. Because unknown fields are rejected, a config that uses a newly added field requires a binary that implements that field; keep config and binary versions aligned and retain old config examples for older releases. Renaming/removing a core field, changing its type or meaning, or changing its omission default in a behavior-changing way requires a deliberate schema_version: 2 for the affected config, changelog migration notes, updated examples, and paired client/server upgrade guidance. A new profile identifier may be needed for a changed transport profile, but it does not replace a schema version change. Do not add speculative keys for future features to schema 1.

The downloadable JSON examples are maintained schema 1 examples. Keep configuration files aligned with the release that consumes them.

Future extension areas, not schema 1 fields: multi-exit/peer relay/LAN discovery/BLE and distributed store backend URIs.

1. Client session config (mehs-client.json)

All fields are optional; omitted fields use the default.

FieldType / rangeDefaultWhat it does and its effectRecommended
schema_versioninteger, exactly 1 (u16)1 if omittedVersion of this file format. Any other value is rejected. Include it explicitly in new files.1
modestring: auto, tcp_tls, quicautoCarrier selection policy. tcp_tls uses TLS 1.3/TCP; quic uses QUIC/UDP and requires the server's QUIC listener. auto measures available carriers and may migrate between them. The CLI, when no session file or mode override is supplied, uses tcp_tls.tcp_tls; quic if UDP is open
e2e_cryptooff | mlkem768-aes256offOptional application-layer protection of every MEHS frame using ML-KEM-768 and AES-256-GCM. The server policy must permit the same suite; mismatches fail with a dedicated crypto negotiation error before flow traffic.off; enable with a coordinated server
tls_fingerprintstring or nullnullOptional TLS ClientHello preset (chrome-stable, mobile-yandex-stable, firefox-stable, mobile-firefox-stable) or custom JSON profile path. Omitted preserves rustls defaults. The MEHS data ALPN and mehs-data/1 framing remain unchanged.omit
client_modeproxy | tunproxyproxy: local SOCKS5 (+ optional HTTP CONNECT) listeners; apps must be pointed at them. tun: system-wide virtual interface; every app is tunnelled with no per-app setup. TUN needs root/administrator.proxy for apps/dev, tun for whole-device VPN
tunobjectsee section 2Settings used only when client_mode is tun.—
tcp_connection_countinteger (u8), 0–321Size of the TCP carrier pool. 0 disables TCP. At least one carrier count must be non-zero; tcp_tls requires a nonzero TCP count. Server policy may lower it.1
udp_connection_countinteger (u8), 0–321Size of the QUIC/UDP carrier pool. 0 disables QUIC; quic requires a nonzero UDP count. Server policy may lower it.1 (0 for TCP-only)
connect_timeout_secondsinteger (u64), 1–30015Maximum time to establish control + data connection. Lower fails fast on dead networks; higher helps on very slow links.15 (mobile: 20)
inf_reconnectbooleanfalseDisable the supervisor retry limit after an established session. Backoff still caps at 30 seconds; initial connection and non-retryable setup failures remain fatal. Client-local; not negotiated with the server.false
server_unreachable_secondsinteger (u64), 1–30015How long a link may be silent and its keepalive probe unanswered before the link is declared dead (then replaced/failed over).15
health_check_secondsinteger (u64), 1–30030Interval for path-health evaluation (RTT/loss/throughput probes of the active and standby carrier; auto + hot change only).30 (gaming: 15)
keepalive_secondsinteger (u64), 1–30015Idle time after which a link sends a keepalive Probe frame (answered by the server). Transport-level keepalives (TCP keepalive, QUIC 10 s ping, 90 s idle timeout) are built in too and keep NAT mappings alive for 24 h+ sessions.15
migration_drain_timeout_secondsinteger (u64), 1–30060Maximum time the old carrier keeps draining open flows after a migration, before it is closed.60
migration_cooldown_secondsinteger (u64), 1–30010Minimum gap between path switches (prevents flapping).10
selection_improvement_percentinteger (u8), 1–10015How much better (lower score) the standby carrier must be before switching.15
selection_windows_requiredinteger (u8), 1–322Consecutive health checks the standby must win.2
session_negotiation_ttl_secondsinteger (u64), 1–300120Time limit for authorize + finalize of each link (a link that cannot negotiate in time is abandoned and retried).120
transport_cache_ttl_secondsinteger (u64), 1–2 678 40086400How long a measured transport choice is reused before auto measures again.86400
max_frame_payload_bytesinteger (u32), 256–65 53665536Largest frame payload.65536
max_reassembly_bytes_per_flowinteger (u32), max_frame_payload_bytes–1 048 5761048576Per-flow receive buffering bound.1048576
max_unacked_bytes_per_directioninteger (u32), max_frame_payload_bytes–8 388 6088388608Send window of unacknowledged data.8388608
is_hot_change_availablebooleantrueLive TCP↔QUIC migration in auto mode (standby link + drain). false picks a carrier once and never switches.true
appsobject{ "bypass": [], "only": [] }Per-application routing on supported Linux/Windows paths; see below.omit

All duration values except transport_cache_ttl_seconds are integers from 1 through 300. selection_improvement_percent is an integer from 1 through 100; selection_windows_required is an integer from 1 through 32. Reassembly and unacknowledged-data limits cannot be smaller than max_frame_payload_bytes. Unsupported enum values, out-of-range values, both connection pools being zero, or a disabled pool required by mode reject the configuration.

apps object

FieldType / rangeDefaultMeaning and validation
bypassarray of strings, at most 64 entries[]These app executable names or paths connect directly. Each entry must be 1–260 characters and non-whitespace.
onlyarray of strings, at most 64 entries[]Only these app executable names or paths are tunnelled. Each entry must be 1–260 characters and non-whitespace.

The two lists cannot both be nonempty. Per-app routing currently supports Linux and Windows; other platforms accept the fields but do not provide the same routing behavior.

Example (client JSON example):

{
  "schema_version": 1,
  "mode": "tcp_tls",
  "e2e_crypto": "off",
  "client_mode": "proxy",
  "tcp_connection_count": 1,
  "udp_connection_count": 1,
  "connect_timeout_seconds": 15,
  "inf_reconnect": false,
  "keepalive_seconds": 15
}

TLS fingerprint presets apply to HTTPS control connections and TCP/TLS data connections. QUIC data connections continue to use rustls because their TLS implementation is provided by Quinn. Without a fingerprint, rustls continues to use its embedded Mozilla root set. With a fingerprint, the browser-capable backend loads native platform trust roots; specifying --ca-file replaces those defaults with the supplied roots. For HTTP requests, the User-Agent follows the selected preset; custom profiles may supply their own, otherwise the existing default is used. Preset templates and user-agent versions can age out; update or replace the profile without changing the MEHS wire protocol.

Custom profiles are strict JSON files (unknown keys are rejected). alpn is a preference list; the client adds the ALPN required by the selected MEHS transport profile so the server can keep selecting mehs-data/1 without a wire-protocol change. See TLS fingerprint JSON example for all fields.

2. TUN settings (the tun object)

Used when client_mode is tun (or --mode tun).

FieldType / rangeDefaultWhat it does and its effectRecommended
namestring, 1–15 bytes, no NULmehs0Interface name on Linux/Windows. macOS needs utunN or lets the OS choose. Ignored with fd.mehs0
mtu576–90001400Interface MTU. Lower it (e.g. 1280) if large downloads stall on a tunnelled PPPoE/mobile network; higher raises throughput only if the whole path supports it.1400
routesarray of IPv4/IPv6 CIDR strings[]Prefixes sent into the tunnel. Empty = everything (0.0.0.0/1, 128.0.0.0/1, ::/1, 8000::/1, which override the default route without deleting it). A list = split tunnel, e.g. ["91.108.4.0/22"].[] (full)
exclude_routesarray of IPv4/IPv6 CIDR strings[]Prefixes kept on the normal network path (split routing), e.g. ["192.168.0.0/16","10.0.0.0/8"] for a LAN or corporate network.[]
manage_routesbooleantruetrue: MEHS installs and removes routes (and carrier bypass routes). false: the host configures routing (required when the OS or app owns it, e.g. Android VpnService or iOS NEPacketTunnelProvider).true desktop, false Android/iOS
block_quicbooleantrueDrops application UDP/443 (QUIC/HTTP3) so browsers fall back to TCP, which is much faster inside a reliable carrier. Set false to forward it (works, but head-of-line blocking hurts).true
fdsigned integer (i32) or nullnullAn already-open TUN file descriptor from the host (Android VpnService). Implies the host did address/route setup and disables route management. Unix only; iOS uses the packet-flow FFI instead.null desktop

name must contain 1–15 bytes and no NUL. mtu must be 576–9000. Every route must be a parseable IPv4 CIDR with prefix 0–32 or IPv6 CIDR with prefix 0–128. The client validates these fields even when TUN mode is not selected.

{
  "client_mode": "tun",
  "tun": { "name": "mehs0", "mtu": 1400, "routes": [], "exclude_routes": [], "manage_routes": true, "block_quic": true }
}

Limits of TUN mode: connections to private/LAN addresses are refused by the server (its outbound_block_private); IPv6 destinations work only if the server has IPv6 egress; half-closed TCP connections close both directions; in-flight connections are reset on reconnect (the interface and routes stay up).

3. Client arguments (CLI and FFI)

The same arguments drive the mehs binary and mehs_runtime_start (as a JSON array of strings). Flags override the session-config file.

ArgumentRequiredDefaultMeaningRecommended
--control HOST:PORTyes—HTTPS control endpoint. HOST is also the TLS server name and must match the certificate.your server
--client-id IDyes—Client ID registered in the server's clients.—
--secret SECRET / env MEHS_CLIENT_SECRETyes—Client secret. Prefer the env var (arguments are visible to other users). FFI: pass as the secret parameter.env var
--session-config PATHnobuilt-in defaultsSession JSON from section 1.—
--transport tcp|quic|autonotcpData carrier; overrides the config mode (auto measures both).tcp
--e2e-crypto SUITEnooffoff or mlkem768-aes256; overrides e2e_crypto in session JSON. The server must permit the suite.omit
--tls-fingerprint NAME|PATHnooffSelect one of the four built-in ClientHello presets or a custom JSON profile file; invalid names/files fail at startup. Same as tls_fingerprint and overrides it.omit
--transport-profile NAMEnomehs-data/1Supported values: mehs-data/1, xhttp, trusttunnel, trusttunnel-http, notmehs; unknown values fail closed.omit
--http-path PATHno/mehs/streamRequest path for HTTP-shaped profiles; must match server http_path.omit
--data HOST:PORTnocontrol host, port + 1Data endpoint. For quic this is the UDP quic_data_listen.set for QUIC
--ca-file PATHnobuilt-in WebPKI rootsPEM CA/server certificate(s) to trust. Required for self-signed servers.pin your CA
--socks-bind ADDRno127.0.0.1:1080SOCKS5 listener (proxy mode). Keep it on loopback.127.0.0.1:1080
--http-bind ADDRnooffHTTP CONNECT listener (proxy mode).127.0.0.1:8080
--udp-associatenooffEnables SOCKS5 UDP ASSOCIATE for UDP apps through the proxy (for example voice calls or games); HTTP CONNECT is TCP-only.Enable when a SOCKS5 app needs UDP
--remote-dnsnooffResolves proxied destination names on the server instead of locally. Requires server support for domain destinations; the client still resolves its control/data endpoints locally.Enable with a compatible server
--mode proxy|tunnoproxySame as client_mode.—
--tun-name NAME, --tun-mtu Nnomehs0, 1400Set TUN interface name and MTU; same validation as the tun object.see section 2
--tun-route CIDR, --tun-exclude CIDRnononeRepeatable route and exclude-route values; a supplied route list selects split-tunnel routing.see section 2
--tun-no-routesnooffDo not change host routes (manage_routes:false).host-managed routes
--tun-allow-quicnooffForward application UDP/443 rather than dropping it (block_quic:false).omit
--tun-fd NnononeUse an already-open host TUN descriptor (Android); disables route management. iOS uses the packet-flow FFI, not this option.host-managed TUN
--app-bypass NAME, --app-only NAMEnononeRepeatable app routing lists corresponding to apps.bypass and apps.only; mutually exclusive. Linux/Windows only.omit

Reconnect behaviour: after a session has been up, network failures reconnect with delays of 1, 2, 4, 8, 16, then 30 seconds. By default, only retries at 30 seconds count toward the five-attempt limit: ten retries and 181 seconds of scheduled waiting total, excluding connection-attempt duration. After the final unsuccessful retry, the CLI exits 1 and the FFI reports failed. Set "inf_reconnect": true in the client session file to disable this attempt limit and continue retrying at 30-second intervals. A session lasting at least 60 seconds resets the backoff and retry budget; shorter sessions do not. Any failure of the first connection is fatal, including connection refusal or timeout. Authorization, certificate, configuration and policy failures during session setup are fatal even when reconnecting; inf_reconnect does not change error classification.

4. Server config (mehs-server.json)

Pointed to by the MEHS_SERVER_CONFIG environment variable. The file must not be group/world-readable on Unix.

FieldType / rangeDefaultWhat it does and its effectRecommended
schema_versioninteger (u16), exactly 1requiredFormat version; unlike the client file, the server does not default it.1
listenip:portrequiredHTTPS control API (/health, /metrics, /v1/authorize, /v1/finalize), TLS 1.3. /metrics is always enabled and requires no config field.0.0.0.0:8443
data_listensocket address or nulllisten IP and port + 1TLS 1.3 TCP data listener. The derived port must fit in 16 bits.0.0.0.0:8444
quic_data_listensocket address or nullnull (off)Optional QUIC data listener (UDP). Open the UDP port in the firewall. Same port number as data_listen is fine (different protocol).0.0.0.0:8444
certificate_chainpathrequiredPEM chain valid for the DNS name/IP clients connect to.—
private_keypathrequiredPEM private key; root-only.—
clientsarray of { "id": string, "secret": string }[]Local allowlist (maximum 4096); IDs must be unique, 1–256 bytes, and secrets 1–4096 bytes. Each object rejects unknown fields. Without external_auth_url, configured secrets must also be at least 16 bytes and must not start (case-insensitively) with replace-with or generate-with.one per device
credential_store_file, session_store_filepath string or nullnoneOptional file-backed stores (requires file-store support); credential secrets are stored in plaintext in files with mode 0600.omit
external_auth_urlHTTP(S) URL string or nullnoneOptional external authorization endpoint. Must have no credentials or fragment; defaults to HTTPS port 443 or HTTP port 80. When configured, valid local client secrets are not required; configure at least this endpoint or a credential source.omit
max_pending_negotiationsinteger (usize), 1–65 5361024Concurrent authorize→finalize handshakes.1024
policyobjectsee section 5Allowed modes and carrier limits.—
max_data_connectionsinteger, 1–409632Concurrent data connections across all clients.256 for a shared server
max_flows_per_sessioninteger, 1–40961024Concurrent proxied connections per client session. Closed flows free their slot.1024–4096
handshake_timeout_secsinteger, 1–30010Time a data connection has to authenticate.10
connect_timeout_secsinteger, 1–30010Time to connect to a requested destination.10
shutdown_grace_secsinteger, 0–3005On SIGTERM/Ctrl-C, how long active sessions get to close cleanly. 0 disables the grace wait.5
outbound_block_privatebooleantrueRefuse destinations that are loopback/private/link-local/etc. Keep true on any public server: otherwise clients can reach the server's own network.true
outbound_allow_cidrsarray of CIDR strings, at most 1024[]If nonempty, only destinations in these prefixes are allowed; private blocking is applied first. An empty list imposes no allowlist restriction.[]
transport_profileprofile string or nullmehs-data/1Single enabled profile: mehs-data/1, xhttp, trusttunnel (also accepted as trusttunnel-http), or notmehs. Unknown names fail startup.omit
transport_profilesarray of profile strings or nullfrom transport_profileA nonempty list enables several profiles at once and takes precedence over transport_profile; duplicates are removed. An empty list falls back to the single profile.["mehs-data/1","xhttp","trusttunnel","notmehs"]
http_pathstring or null, 1–256 bytes beginning //mehs/streamURL path for HTTP-shaped data profiles; must match the client when it connects with an HTTP profile.a custom path

listen, data_listen, and quic_data_listen must parse as socket addresses (IP literal and port); data_listen is derived from listen when omitted or null. The config file is limited to 1 MiB and must not be group/world-readable on Unix. credential_store_file and session_store_file require file-store support; the clients array seeds an empty credential store, and an already-populated store takes precedence. After loading stores, the server requires at least one local credential or external_auth_url.

Operational metrics are available at https://<listen-address>/metrics in Prometheus text format in release packages, with no configuration required. The endpoint shares the control listener's TLS certificate and access policy; see server operations and metric definitions.

External authorization contract (external_auth_url)

The following request and response fields are the frozen external authorization contract. Integrations must use these exact, case-sensitive names and JSON types. Changing their names, types, requiredness, or meaning requires an explicitly versioned contract; the version request field is not contract-version negotiation.

When configured, this endpoint replaces local credential checks for POST /v1/authorize; there is no fallback to local credentials on denial or failure. The server sends an HTTP/1.1 POST to the configured URL's path and query (/ if no path is supplied), with Content-Type: application/json, Accept: application/json, Content-Length, and Connection: close. Credentials are sent in the JSON body, not as HTTP authorization headers or added query parameters.

Sent JSON body

FieldJSON typeMeaning
usernamestringThe client's submitted /v1/authorize client_id, unchanged.
passwordstringThe client's submitted /v1/authorize secret, unchanged.
versionstringThe MEHS server package version , not the client version or MEHS protocol version.
{
  "username": "client-893651",
  "password": "example-password",
  "version": "0.1.0"
}

The version value above is illustrative; integrations must not assume that exact value. No transport policy, session ID, or other JSON fields are sent.

Expected JSON response

The endpoint must return HTTP 200 with a JSON object containing exactly these two fields. Both fields are required even when authorization is denied; unknown fields, missing fields, nulls, and incorrect types are rejected.

FieldJSON typeRequiredMeaning
authorizedbooleanyestrue accepts the credentials; false denies them.
usernamestringyesCanonical account identity on acceptance. Must be nonblank after trimming, at most 256 UTF-8 bytes, and contain no control characters. It is used unchanged, not trimmed. On denial it is ignored and may be empty.

Acceptance:

{
  "authorized": true,
  "username": "canonical-alice"
}

Denial:

{
  "authorized": false,
  "username": ""
}

The canonical username becomes the account identity; the originally submitted username remains bound to the client data-channel handshake. External acceptance does not bypass the MEHS server's policy or capacity checks.

A valid denial returns HTTP 401 with {"code":"unauthorized"} to the MEHS client. A non-200 response (including 401/403 or a redirect), invalid JSON/schema, an invalid canonical username on acceptance, connection/TLS failure, or timeout returns HTTP 502 with {"code":"external_auth_unavailable"}. Redirects are not followed and requests are not retried. The TCP connect timeout is 3 seconds and the entire external authorization operation has a 5-second timeout.

Responses are limited to 16 KiB in total, including HTTP headers and any chunked encoding overhead. Content-Length, chunked transfer encoding, and connection-close-delimited bodies are supported. The endpoint must close the connection after its response: the server reads until EOF even when a Content-Length is present.

Use HTTPS in production; HTTP exposes the password in transit. HTTPS uses public WebPKI trust roots and server-name verification, without a client certificate. The endpoint is trusted to assert account identities and must protect credentials.

Client-side HTTP profile / per-app options. transport_profile (client, --transport-profile) selects the carrier; --http-path sets the path. apps.bypass (--app-bypass NAME, repeatable): listed apps connect directly outside the tunnel. apps.only (--app-only NAME): only listed apps are tunnelled, all others go direct. The two cannot be combined; names are executable names or full paths (max 64 entries, 1–260 chars). Supported on Linux and Windows in proxy mode (TCP via SOCKS5 CONNECT / HTTP CONNECT; UDP ASSOCIATE stays tunnelled) and TUN mode (TCP and UDP). The owning process is found from the connection's local port; unknown owners stay tunnelled. Linux needs root to see other users' processes. block_quic still applies to direct apps in TUN mode.

{
  "schema_version": 1,
  "listen": "0.0.0.0:8443",
  "data_listen": "0.0.0.0:8444",
  "quic_data_listen": "0.0.0.0:8444",
  "certificate_chain": "/etc/mehs/server-chain.pem",
  "private_key": "/etc/mehs/server-key.pem",
  "clients": [{ "id": "client-1", "secret": "<openssl rand -base64 32>" }],
  "max_flows_per_session": 1024,
  "policy": { "allowed_modes": ["tcp_tls", "quic"], "max_tcp_connections": 2, "max_udp_connections": 2, "e2e_crypto": "off" }
}

5. Server policy (the policy object)

FieldType / rangeDefaultEffectRecommended
allowed_modesarray of tcp_tls, quic strings["tcp_tls","quic"]Modes clients may use; duplicates are removed. Must include at least one implemented mode with a nonzero corresponding limit; auto is not a server-allowed mode.both
pinned_modetcp_tls, quic string or nullnullForce every client onto one allowed mode that has a nonzero connection limit.null
max_tcp_connectionsinteger (u8), 0–3232 (2 in the example)Upper bound for a client's TCP carrier count. 0 forbids TCP.2
max_udp_connectionsinteger (u8), 0–3232Upper bound for QUIC carrier count. 0 forbids QUIC.2
e2e_cryptooff | optional | requiredoffoff forbids E2E; optional accepts either plaintext MEHS framing or ML-KEM-768 + AES-256-GCM when requested; required rejects clients that do not request the suite. Mismatches return error code 9, crypto_negotiation.off

The policy object rejects unknown fields. It also rejects an empty or unsupported allowed_modes list, a policy that leaves no usable carrier, and a pinned_mode that is not allowed or has a zero connection limit. Unsupported E2E suite identifiers or protocol versions are rejected rather than downgraded.

E2E is negotiated on each data connection before any flow is opened and works over TCP/TLS, QUIC, and HTTP-shaped carriers. With both sides off, the existing authentication and frame path is unchanged. When enabled, complete MEHS frames (including their headers) are encrypted; the outer frame exposes only the session epoch and per-direction record counter. The ML-KEM exchange is authenticated by the existing TLS/QUIC channel. Continue validating or pinning the server certificate: this is defense in depth, not protection from an active TLS-terminating intermediary.

6. Ready-made profiles

Defaults and recommendation

The CLI defaults remain unchanged for schema 1: proxy mode, SOCKS5 UDP ASSOCIATE off, and proxy destination-name resolution local (--remote-dns off). In particular, --remote-dns is opt-in because the server must support domain destinations and older servers reject them. This avoids changing existing CLI or config behavior; no wire-protocol change is made by these recommendations. Enable remote DNS only when the server supports it. It avoids local resolution of proxied destination names, but the client still resolves its own control/data endpoints locally.

For everyday whole-device VPN use, choose TUN mode and preserve access to the actual LAN prefixes in use on that network. For app-based proxy use, keep the listeners on loopback, enable remote DNS to avoid local lookup of proxied names, and add --udp-associate only when SOCKS5 UDP traffic (such as calls or games) is needed. HTTP CONNECT does not use SOCKS5 UDP ASSOCIATE. TUN forwards DNS and UDP transparently; these proxy flags do not configure TUN DNS behavior.

Copy-ready profiles

Daily system-wide VPN (Linux/Windows/macOS; adjust LAN exclusions to match your network):

mehs --control vpn.example.com:8443 --client-id laptop --ca-file ca.pem \
  --transport tcp --mode tun \
  --tun-exclude 192.168.0.0/16 --tun-exclude 10.0.0.0/8 \
  --tun-exclude 172.16.0.0/12

The server addresses are automatically excluded from TUN routes. Add or remove --tun-exclude entries for the local networks you want to reach outside the VPN; do not use the example CIDRs blindly if they overlap networks that should go through your VPN.

Local proxy (works without admin rights; SOCKS5 UDP is optional):

mehs --control vpn.example.com:8443 --client-id laptop --ca-file ca.pem \
  --socks-bind 127.0.0.1:1080 --http-bind 127.0.0.1:8080 --remote-dns

Add --udp-associate to this command if SOCKS5 UDP is needed for voice calls or games. It is off by default; HTTP CONNECT remains TCP-only.

Lossy mobile network: --transport quic --data vpn.example.com:8444 with "connect_timeout_seconds": 20.

Split tunnel (only selected ranges): --mode tun --tun-route 91.108.4.0/22 --tun-route 149.154.160.0/20.

Android app (host owns the VPN): see flutter-integration.md: --mode tun --tun-fd <fd> (implies no route management).