MEHS HTTPS control server
The server implements the TLS 1.3 control API and a separate TLS 1.3 TCP data listener. It authenticates configured clients, applies transport policy, issues one-use UUIDv4 negotiation IDs that expire after 120 seconds, and finalizes a selected mode. The data listener binds the finalized epoch to the configured client credentials and forwards TCP flow-open requests to target sockets. Credential and short-lived session stores default to in-memory; restart invalidates sessions when using the default stores.
Configuration
Start from mehs-server.example.json, replace every placeholder secret, and point the certificate/key fields at a certificate valid for the server DNS name. The server configuration uses schema_version: 1; its accepted fields, defaults, validation, and evolution rules are documented in the authoritative configuration reference. TLS certificate verification remains the client's responsibility; the server never sends a key and treats certificate validation as proof of server identity.
Credential and session stores can be configured to use file-backed storage. The existing clients array seeds an empty credential store; once credentials are present in the store, the stored allowlist is loaded instead (the array may then be omitted). The file backend stores client secrets as plaintext, creates files with Unix mode 0600, and rejects credential files with broader Unix permissions. Protect the store directory and files accordingly. Finalized session metadata expires after 15 minutes; the file backend is intended for a single server process, not distributed coordination.
On Debian/Linux, protect the file before starting the service:
sudo install -o root -g root -m 600 mehs/config/mehs-server.example.json /etc/mehs/server.json
sudo MEHS_SERVER_CONFIG=/etc/mehs/server.json /usr/local/bin/mehs-server
Generate each client secret with openssl rand -base64 32 and edit the file under umask 077. The process refuses a config file readable by group or other users on Unix, and refuses client secrets shorter than 16 bytes or left as the example placeholder. Outbound flows to private, loopback, link-local, and other non-public destinations are blocked by default (outbound_block_private: true); set it to false explicitly only for deployments that need them, and prefer an outbound_allow_cidrs allowlist. The example is not secure until the client secret, listen address, and TLS paths are set for the deployment. Keep the private key in a root-readable location and provision certificates through the host's established certificate-management process.
The server binds the configured address and exposes:
GET /health— liveness only; it does not prove that a client can establish a tunnel.GET /metrics— Prometheus text exposition on the same TLS 1.3 control listener. No extra configuration is required; see Operational metrics.POST /v1/authorize— JSON fieldsclient_id,secret,mode,tcp_connections, andudp_connections. Returns the temporarysession_id, effective server policy, and structured corrections.POST /v1/finalize— JSON fieldssession_idandselected_mode. Returns a freshsession_epochand effective carrier counts. A token is consumed on the first finalize attempt, including a rejected selection.- A MEHS TLS data listener at
data_listen(default: the control address's port + 1). The client must finalize atcp_tlssession, then authenticate the data connection using its client ID, secret, and returned epoch. See the authenticated TCP data session and flow-open documentation for wire behavior. - Optionally, a QUIC data listener at
quic_data_listen(UDP, ALPNmehs-data/1). The client must finalize aquicsession and authenticate the first bidirectional stream with the same client ID, secret, and epoch rules as TCP. Usemehs --transport quic --data HOST:PORTto connect. - Optionally,
transport_profile(server config) or--transport-profile(client) names the transport profile. Omitted meansmehs-data/1; unknown names fail closed.transport_profiles(array) enables several at once, e.g.["mehs-data/1","xhttp","trusttunnel","notmehs"], dispatched by ALPN;http_pathsets the XHTTP/NOTMEHS path. No profile fallback occurs.outbound_block_private(defaulttrue) refuses loopback, private, link-local, and other non-public destinations, including client LAN addresses.
Both POST endpoints require HTTPS/TLS 1.3 and are limited to 64 KiB request bodies. The data listener also requires TLS 1.3 and caps concurrent data connections (max_data_connections, default 32; every link of every client counts, including a client's standby link, so size it as clients × (pool size + 1)) and each session at max_flows_per_session concurrent flows (default 1024; closed flows free their slot). Credential values and negotiation tokens are not written to logs. Configure the firewall and outbound network policy: authenticated clients can request TCP or UDP connections to target IP addresses reachable from the server.
Operational metrics
GET /metrics is always enabled and returns Prometheus text format at the control listener address (for example, https://vpn.example.com:8443/metrics). It is served over the same TLS 1.3 certificate as the control API and does not require additional configuration. The endpoint has no authentication of its own; restrict network access to the control listener with the host firewall or deployment network policy, especially because it also serves authorization endpoints.
The metric names and semantics are stable and intentionally have no client, destination, profile, or other high-cardinality or secret-bearing labels:
| Metric | Type | Meaning |
|---|---|---|
mehs_authorize_requests_total | counter | Valid JSON requests reaching the authorize handler. |
mehs_authorize_failures_total | counter | Authorize requests rejected by policy, credentials, or an external auth failure. |
mehs_finalize_requests_total | counter | Valid JSON requests reaching the finalize handler. |
mehs_finalize_failures_total | counter | Finalize requests rejected or failed. |
mehs_data_auth_failures_total | counter | Data authentication handshakes that fail before authentication succeeds. |
mehs_outbound_connect_failures_total | counter | Flow-open attempts that fail to resolve, pass outbound policy, or connect before the timeout. |
mehs_active_data_connections | gauge | Data connections currently holding a server connection slot, including handshakes in progress; bounded transport labels are tcp_tls and quic. |
mehs_active_flows | gauge | Successfully opened target flows that have not yet closed. |
For example, a Prometheus scrape can use the server CA file:
scrape_configs:
- job_name: mehs
scheme: https
static_configs:
- targets: ["vpn.example.com:8443"]
tls_config:
ca_file: /etc/prometheus/certs/mehs-ca.pem
Use standard Prometheus counter functions for rates, for example rate(mehs_authorize_requests_total[5m]) or rate(mehs_outbound_connect_failures_total[5m]).
The TLS/TCP and optional QUIC data paths carry TCP and UDP target flows (SOCKS5 UDP ASSOCIATE, TUN-mode UDP/DNS, and remote DNS are all served by the same flow machinery; the server needs no raw-IP gateway and no special privileges). Session state is in-memory by default, with optional file-backed stores. Outbound CIDR allowlists and private-address blocking are configurable. The wire protocol is not frozen between releases and no independent security audit has been completed. Every field is documented in the configuration reference.
On shutdown, both data transports stop accepting new sessions, send a session-level Close to active sessions, and wait up to shutdown_grace_secs for them to finish before aborting remaining tasks. A rejected data authentication closes that connection; a failed flow-open returns a failure result for that flow while leaving the authenticated session available.
Running as a service (systemd)
# /etc/systemd/system/mehs.service
[Unit]
Description=MEHS server
After=network-online.target
Wants=network-online.target
[Service]
Environment=MEHS_SERVER_CONFIG=/etc/mehs/server.json
ExecStart=/opt/mehs/mehs-server
Restart=always
RestartSec=2
LimitNOFILE=1048576
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
Open TCP 8443 (control) and 8444 (data), and UDP 8444 when quic_data_listen is set. The server runs unprivileged; it does not need root unless it binds a port below 1024. Raise LimitNOFILE for many concurrent connections. Give the server IPv6 connectivity if TUN clients should reach IPv6 destinations.