Skip to main content
Version: 5.x

Upgrading from MTE Relay v4 to v5

MTE Relay v5 replaces the v4 wire protocol, so this is a coordinated upgrade of servers and clients. This page covers what changed, how v4 settings and error codes map to v5, the steps for each client and server type, a cutover plan that keeps v4 running until you are done, and the v4 support policy.

The current v5 release is 5.0.5 for both MTE Relay Server (MRS) and MTE API Relay (MAR).

Why v5​

  • Every frame is verified before any plaintext is released. v5 carries each request and response as frames, and each DATA frame is one complete MTE or MKE operation checked before its bytes move on. In the v4 MKE streaming path, decrypted chunks were passed along before the final integrity check.
  • The method, status, and end of a body are bound to the ciphertext. The encrypted metadata repeats the HTTP method and the response status that the frame also carries in plain, and every DATA frame encrypts a copy of its flags, so the end of a body cannot be forged. The relay refuses a request whose encrypted and plain values disagree with 483 header_mismatch, and the client refuses such a response. Request and response headers travel in the encrypted metadata, except headers you choose to send plain and, while forward_browser_cookies is on (the default), the browser's Cookie and the origin's Set-Cookie, which ride the plain HTTP request and response.
  • Kyber 1024 by default. v4 used Kyber 512. v5 accepts 512, 768, or 1024 per deployment.
  • Errors your infrastructure can carry. Relay errors move from v4's custom 559 to 570 range to 470 to 490, each with a reason token, a registered client action, and an X-MTE-Relay-Error header that survives a replaced body.
  • Clients read the relay instead of hardcoding it. A discovery document at auth advertises the relay's limits, timers, and per domain settings.
  • Idle clients stay paired. A keepalive endpoint refreshes every pair of a client in one call. v4 had no keepalive.
  • Guard rails. Rate limits on auth, pairing, and keepalive, a request body cap, and bounded frames. v4 had no rate limits and no body cap.
  • A control plane. An optional admin panel and API configure a whole fleet, with live updates, history, and rollback.

See What is new in v5 for the full list.

The breaking change​

v5 speaks frame version 2, a binary protocol v4 does not understand, and there is no dual stack mode: a v5 relay serves only v5 clients, and a v4 relay serves only v4 clients.

  • A v4 client starts every session with HEAD /api/mte-relay. A v5 relay answers that probe with 478 frame_version_unsupported and never issues a token, so the v4 client never gets a session. A response to HEAD carries no body, so the error arrives only as the status and the X-MTE-Relay-Error header. The relay logs the minimum SDK versions at warn level.
  • An auth request without frameVersions=2, or a request body in the frame version 1 layout used by the earlier v5 betas, gets 478 frame_version_unsupported, and the message names the minimum SDK versions.
  • A v5 client cannot pair with a v4 relay either.
  • The same holds between relays: a v4 MAR cannot chain to a v5 MAR.

So servers and clients upgrade together, per application. The cutover plan keeps both versions running side by side on different hostnames so each application moves when its new client ships.

Settings: v4 to v5​

v5 routes everything through DOMAIN_MAP. The v4 single upstream mode and its variables are gone. See the environment variables reference for every v5 setting.

v4 settingv4 defaultv5Notes
UPSTREAMnoneremovedUse a DOMAIN_MAP entry with upstream.
CLIENT_ID_SECRETnoneclient_id_secret per domainSet the same value on every replica.
CORS_ORIGINSnonecors_origins per domain
CORS_METHODSGET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT, TRACEcors_methods per domainThe v5 default is GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD.
PASS_THROUGH_ROUTESnonepass_through_routes per domainv4 entries were regular expressions. v5 entries are exact paths or prefixes ending in *.
HEADERSnoneheaders per domainv5 sets these on upstream requests only. v4 also set them on responses to the client.
OUTBOUND_TOKEN, OUTBOUND_TOKEN_HEADERnone, x-mte-outbound-tokenremovedAn outbound domain is a DOMAIN_MAP entry with outbound: true.
DOMAIN_MAPnoneDOMAIN_MAPSame idea, many more fields. outbound_token is removed. Keys and the request Host now compare case insensitively, and a map with two keys that differ only by letter case is refused. See DOMAIN_MAP.
POOL_SIZE600, capped at 1000DEFAULT_POOL_SIZE 250, capped by MAX_POOL_SIZE_LIMIT 100000
OUTBOUND_PAIR_COUNT1024CLIENT_INITIAL_PAIRS 2, CLIENT_MIN_PAIRS 25v5 creates a few pairs up front and fills to the minimum in the background.
OUTBOUND_PAIR_GROW_COUNT256CLIENT_REFILL_THRESHOLD 10v5 refills when available pairs fall to the threshold or below, or when requests are waiting for a pair, and sizes each refill to the requests waiting.
OUTBOUND_MAX_PAIR_COUNT8192CLIENT_MAX_PAIRS 1024 Since 5.0.1The ceiling changed. Both versions hold one pair per request in flight, so this is the concurrency one outbound replica carries through one outbound host. Size it to your peak, and set MAX_PAIRS_PER_CLIENT on the inbound relay to at least the same value. Relay 5.0.0 shipped with 100; see Limits and scaling for the 5.0.0 settings.
OUTBOUND_ACQUIRE_TIMEOUT5sCLIENT_PAIR_ACQUIRE_WAIT_MS 1000 Since 5.0.1Milliseconds in v5. Past the wait the caller gets 485 upstream_capacity with Retry-After. Relay 5.0.0 shipped with 250.
FORWARD_HOST_HEADERtrueFORWARD_HEADERS truev5 turns X-Forwarded-Host, X-Forwarded-Proto, and X-Forwarded-For on or off together, and the setting is live.
RESEED_THRESHOLD90removed
BILLING_STRATEGY, BILLING_PROVIDER, BILLING_PRODUCT, BILLING_REPORT_INTERVALsame names, set by the imageThe AWS Marketplace images set the first three for their listing; leave them as the image sets them. The other images ship with billing off. BILLING_REPORT_INTERVAL has no effect on the relay.
PERF_STATS_INTERVAL10sPERF_STATS_INTERVAL 10A v4 duration such as 10s is not an integer, so v5 ignores it and uses 10 seconds. Write plain seconds.
COMPANY_NAME, LICENSE_KEYrequiredsame namesv5 falls back to a development license when they are unset. Set your production license.
PORT8080LISTEN_PORT 8080PORT still works as an alias, so a 4.x file needs no change. LISTEN_PORT wins when both are set, and a disagreement is logged once. The port must be from 1 to 65535.
HOSTNAME0.0.0.0retiredIt never bound anything in 4.x or 5.x. v5 ignores it with a warning, except the pod or container name a runtime sets, which never warns. To bind one interface, set the new LISTEN_HOST.
LOG_LEVELinfosame nameAccepts trace, debug, info, warn, warning, error, fatal, panic, or disabled, in any letter case. An unknown value now logs a warning and falls back to info, where v4 refused to start.
USE_CONSOLE_LOGS, REDIS_URL, HTTP_MAX_IDLE_CONNS_PER_HOST, ENABLE_PPROF, PPROF_PORT, MUTEX_PROFILE_FRACTION, BLOCK_PROFILE_RATE, ENABLE_PERF_STATSsame namesBooleans accept true, 1, yes, on and false, 0, no, off in any letter case. A value that does not parse logs a warning and takes the default.

New in v5, with no v4 equivalent: LISTEN_HOST, MAX_REQUEST_BODY_MIB, MAX_CONTROL_BODY_MIB, KYBER_STRENGTH, USE_MKE, SEQUENCE_WINDOW, TIME_WINDOW, SESSION_TIMEOUT_MINUTES, HTTP_CLIENT_TIMEOUT_SECONDS, the protocol bounds and timers (MAX_PAIR_BYTES among them, with MAX_PAIR_MIB as an alias), RATE_LIMITS, TRUSTED_PROXIES, the Redis tuning variables, LOCAL_STATE_CACHE, HEALTH_CHECK_PATH, and the control plane and probe settings. Every setting v5 shares with SocketX has the same name, default, and parsing rule in both products.

Converting a v4 configuration​

A v4 single upstream configuration such as:

UPSTREAM=https://api.example.com
CLIENT_ID_SECRET=REPLACE_WITH_A_LONG_RANDOM_SECRET
CORS_ORIGINS=https://app.example.com
PASS_THROUGH_ROUTES=/health,/version

becomes one DOMAIN_MAP entry keyed by the host clients call:

{
"relay.example.com": {
"upstream": "https://api.example.com",
"client_id_secret": "REPLACE_WITH_A_LONG_RANDOM_SECRET",
"cors_origins": ["https://app.example.com"],
"pass_through_routes": ["/health", "/version"]
}
}

Check every v4 pass through entry. v4 matched each entry as a regular expression anywhere in the path, so /health also matched /v1/health/live. v5 matches /health exactly, and a prefix needs a trailing *: ^/public/.* becomes /public/*. An entry v5 cannot express as an exact path or a prefix needs one entry per path.

For MTE API Relay, a v4 outbound entry keyed on outbound_token becomes an outbound domain. Your service stops sending x-mte-upstream and x-mte-outbound-token and calls the outbound relay directly; the entry's upstream names the next relay.

Error codes: v4 to v5​

v4 4.8.0 sends 560 to 563, 565, 566, and 569 from the relay, plus 503 when an outbound pair pool is exhausted. It defines 559, 564, 567, and 568 but never sends them, and 570 covers an item that is neither a string nor bytes, which no v4 code path passes. v5 never sends any of those custom codes. Move alerts and client handling to the v5 codes:

v4 codev4 meaningClosest v5 answer
560State not found470 pair_not_found, or 475 invalid_client_id when the relay holds no pairs for the client
561Failed to encode474 encode_failed
562Failed to decode473 decode_failed
563Failed to get state, raised when saving encoder or decoder state503 state_store_write_failed
564DRBG reseed required, never sentnone; the browser engine retires a pair before its reseed limit
565MTE status not successful, raised when restoring state, and on an MAR when the pair pool cannot grow or the next relay fails471 decoder_restore_failed or 472 encoder_restore_failed; on an MAR chain, 502 with the next relay's code as its cause
566Invalid client ID header475 invalid_client_id, or 475 invalid_token for an expired token
569Missing required header477 invalid_frame or 490 invalid_request; v5 has no required plaintext relay headers
570Item was not a byte array or string500 internal
503 (MAR)MTE pair pool exhausted485 upstream_capacity

The mapping is by meaning, not one to one. v5 codes carry a reason token and a registered client action, listed on Errors and recovery.

Upgrading clients​

Every client platform needs a v5 SDK.

Web (JavaScript)​

  1. Install the v5 package:

    npm install mte-relay-browser-public-client@5.0.0

    It bundles the mte-relay-browser engine 5.0.0, which speaks frame version 2.

  2. Keep your mteFetch calls. The request URL still names the relay origin.

  3. Remove v4 per request options. v5 mteFetch accepts only encodeType and unencryptedHeaders in its third argument. The v4 options encodeUrl, encodeHeaders, pathPrefix, and useStreaming are gone: the path and every header are encrypted unless listed in unencryptedHeaders, and responses stream.

  4. Move startup options to configureMteRelay(options), called once before the first mteFetch.

  5. Update error handling to the v5 error classes, which carry the registry code, reason, and recovery action.

The JavaScript client page has the full v5 API.

Angular​

Install mte-relay-angular@5.0.0, which runs on the mte-relay-browser engine 5.0.0 and speaks frame version 2. See the Angular client.

iOS, Android, and Flutter​

v5 iOS, Android, and Flutter clients speak frame version 2, and a v4 mobile SDK cannot connect to a v5 relay. Install the release for your client:

Ship a new app build. Apps already in users' hands keep using v4 until they update, which is the main reason to run v4 and v5 side by side during the cutover.

MTE API Relay callers​

A MAR chain has no client SDK. Upgrade both relays of the chain to the v5 MAR image together, convert the outbound entries to outbound: true domains, and change the calling service to send ordinary requests to the outbound relay, with no x-mte-* headers.

Upgrading servers​

Before you start​

  • Get access to the v5 images: 321186633847.dkr.ecr.us-east-1.amazonaws.com/customer/on-prem/mte-relay-server:5.0.5 for MTE Relay Server and 321186633847.dkr.ecr.us-east-1.amazonaws.com/customer/on-prem/mte-api-relay:5.0.5 for MTE API Relay.
  • Convert the configuration with the tables above.
  • Set an explicit client_id_secret for every domain.
  • Give v5 its own Redis instance or database when you run two or more replicas. v4 pair state cannot be used by v5, so there is nothing to migrate; clients pair again.

Docker and Docker Compose​

  1. Add a v5 service beside the v4 one, on its own host port or hostname, with the v5 image and a DOMAIN_MAP.
  2. Check its health from outside the container, for example from a load balancer or monitor: the probe listener on port 9091 (GET /healthz and GET /readyz), or on the relay port GET /api/mte-echo or the path you set in HEALTH_CHECK_PATH. The image is distroless, so a compose healthcheck command cannot run inside it.
  3. Start it and run the validation checklist.

See the MTE Relay Server on premise guide and the MTE API Relay on premise guide for complete files.

Kubernetes and OpenShift​

  1. Deploy v5 as a separate Deployment and Service, not as a new image on the v4 Deployment, because a rolling update would mix protocol versions behind one Service.
  2. Use the probe listener: readiness on GET /readyz and liveness on GET /healthz, port 9091.
  3. Expose it on a new hostname with its own Ingress or Route.
  4. Optionally add the control plane, in which case the workers need only CONTROL_PLANE_URL and CONTROL_PLANE_TOKEN.

AWS, Azure, GCP, and Oracle Cloud​

The v5 relay runs as the same container on every platform. Deploy a new v5 service beside the v4 one on your container platform (ECS, EKS, AKS, GKE, OKE, or a container instance service) behind its own hostname, then follow the cutover plan. The platform guides under MTE Relay Server and MTE API Relay have examples.

WAF, CORS, and monitoring​

  • Allow application/octet-stream request bodies on the v5 hostname. v4 put ciphertext in the URL and custom headers; v5 sends a binary POST /. See CORS and WAF.
  • If a domain customizes cors_allowed_headers, include Content-Type and X-MTE-Relay-Route. The v4 headers x-mte-relay and x-mte-relay-eh are gone.
  • Check each cors_origins entry against the v5 origin rules: matching is case insensitive, *.example.com accepts any scheme, and https://*.example.com requires the scheme to match, as in v4. v4 refused a disallowed Origin with 403; v5 answers it without CORS headers, which a browser cannot read, and curl and native clients are unaffected. See CORS and WAF.
  • Move alerts from 559 to 570 to 470 to 490 and the X-MTE-Relay-Error header. Keep 500, 502, and 503 as true fault signals.
  • Health checks need no change. GET /api/mte-echo answers as in v4, the message is still a JSON string, and v5 also takes HEAD /api/mte-echo. For a load balancer or a security scanner that can only reach the relay port, the new optional HEALTH_CHECK_PATH mounts a 200 {"status":"ready"} answer at a path you pick, for any Host and outside every rate limit. It is off by default. See CORS and WAF.

Cutover plan​

The low risk path runs v5 beside v4 and moves clients over one application at a time.

  1. Stand up v5 on a separate hostname, for example relay-v5.example.com, in front of the same upstream, with its own replicas and Redis. Leave v4 untouched.
  2. Validate v5 with the checklist below and a v5 client build pointed at the new hostname.
  3. Ship clients that use the v5 SDK and the v5 hostname. Web applications switch when you deploy. Mobile applications switch as users update, so v4 keeps serving older app versions meanwhile.
  4. Watch both. On v5, watch the 470 to 490 codes, latency, and upstream errors. On v4, watch traffic fall.
  5. Retire v4 when its traffic has stopped, and before October 1, 2027, when v4 support ends.

For an MTE API Relay chain, stand up a v5 pair of relays beside the v4 pair, point the calling service at the v5 outbound relay, and retire the v4 pair once traffic has moved.

Reusing the v4 hostname for v5 in one step is possible, but every client of that hostname must switch at the same moment, and any v4 client left behind fails until it updates.

Validation checklist​

Replace the host and ports with your own. Send the Host header your DOMAIN_MAP expects.

  • The relay answers its echo endpoint:

    curl -s http://localhost:8080/api/mte-echo
    # {"message":"true","timestamp":"2026-01-04T10:30:45.123456789Z"}
  • The worker is ready:

    curl -s http://localhost:9091/readyz
    # {"status":"ready","phase":"ready"}
  • Auth returns a v5 discovery document with the values you expect:

    curl -s -H "Host: relay-v5.example.com" \
    "http://localhost:8080/api/mte-relay?frameVersions=2" | jq .relay

    Check frameVersions ([2]), kyberStrength, maxRequestBodyBytes, and passThroughRoutes.

  • The same request without ?frameVersions=2 returns 478, which proves the host reaches a v5 relay.

  • A v5 client request reaches the upstream, and the upstream logs show the expected X-Forwarded-Host.

  • Browser clients show no CORS errors and no MteRelaySetupError.

  • With two or more replicas, traffic spreads across them without a rise in 475 or 470.

  • Dashboards and alerts see the 470 to 490 codes and the X-MTE-Relay-Error header.

  • With the control plane, every worker shows as in sync on the Fleet page.

Rollback​

  • Before clients move, rollback is removing the v5 service. v4 never stopped.
  • Web clients roll back by redeploying the previous v4 build, which still points at the v4 hostname.
  • Mobile clients cannot be rolled back once installed, and a v5 client cannot talk to a v4 relay. Keep the v5 relay running and fix forward, and ship mobile clients only after the v5 relay passes validation.
  • A bad v5 configuration rolls back from the control plane History page, or by redeploying the previous environment values.
  • A bad v5 release rolls back by redeploying the previous v5 image tag.

v4 support policy​

  • MTE Relay v4 is supported until October 1, 2027.
  • v4 images stay available at least until then.
  • v4 images get no updates after October 1, 2027.
  • The v4 documentation stays online: MTE Relay v4 documentation.