Set up end-to-end encryption
MEHS offers optional application-layer encryption between the MEHS client and MEHS server using ML-KEM-768 and AES-256-GCM. Enable it on both ends with the settings below. TLS/QUIC encryption and certificate verification remain required.
Understand the endpoints: this protects MEHS traffic between the client and server, not from the server to the destination website or application. The MEHS server can access forwarded traffic; use HTTPS or the destination application's encryption for protection beyond it. The additional key exchange relies on the existing TLS/QUIC server identity: it does not protect against an active TLS-terminating intermediary that can alter that exchange, and does not replace certificate validation or pinning.
1. Prepare both endpoints
Use matching client and server packages from MEHS releases that
support mlkem768-aes256. First establish a working connection with a valid client ID,
secret, and trusted server certificate. Keep certificate and hostname checks enabled. Back up
your current configuration securely before editing it; it contains credentials.
These instructions modify existing schema-version-1 configurations. JSON fragments below show settings to merge, not complete replacements for your files. Preserve your listeners, credentials, certificate paths, and other policy settings. No manually generated KEM key files are needed; the endpoints negotiate the encryption keys when connecting.
2. Set the server policy
In the server JSON file selected by MEHS_SERVER_CONFIG (commonly
/etc/mehs/server.json), set e2e_crypto inside the existing
policy object:
{
"policy": {
"e2e_crypto": "required"
}
}
Merge this field into your existing policy; do not remove its other fields. Protect the configuration with permissions appropriate to the service account (on Unix it must not be readable by group or other users). Restart the server to apply the change. For deployments using the example systemd service:
sudo systemctl restart mehs.service
sudo systemctl status mehs.service --no-pager
A restart interrupts existing sessions. Use your actual service name or process manager if it differs from this example.
Server policy.e2e_crypto | Behavior |
|---|---|
off (default) | Rejects clients requesting the additional encryption. |
optional | Allows clients with or without the additional encryption; enables it only when requested. |
required | Rejects clients that do not request the supported suite. Use this to enforce encryption. |
For a staged rollout, start with optional, update and verify every client, then
switch to required and restart the server. With optional, a successful
connection alone does not demonstrate that the additional encryption was used.
3. Enable the client suite
Command-line setup
Keep the client secret in MEHS_CLIENT_SECRET rather than command-line arguments.
In Bash, you can enter it without echoing it or putting its value in shell history:
read -r -s -p 'MEHS client secret: ' MEHS_CLIENT_SECRET
printf '\n'
export MEHS_CLIENT_SECRET
mehs --control vpn.example.com:8443 --client-id laptop \
--ca-file ca.pem --transport tcp \
--e2e-crypto mlkem768-aes256 \
--socks-bind 127.0.0.1:1080 --remote-dns
Replace the endpoint, client ID, and CA file with your deployment values. Run the client in a
terminal and leave it running while testing. The same encryption setting works with
--transport quic or --transport auto when the server permits and
enables those transports; retain any required --data HOST:PORT setting.
Session configuration
Alternatively, set the top-level client e2e_crypto field in your existing session
JSON file:
{
"schema_version": 1,
"e2e_crypto": "mlkem768-aes256"
}
Load that file when starting the client:
mehs --control vpn.example.com:8443 --client-id laptop \
--ca-file ca.pem --session-config mehs-client.json \
--socks-bind 127.0.0.1:1080 --remote-dns
The --e2e-crypto argument overrides the JSON field. Remove an existing
--e2e-crypto off override if you want to use the file's setting. Disconnect and start
a new client session after changing the configuration.
TUN and embedded applications
For device-wide routing, add --e2e-crypto mlkem768-aes256 to your working TUN command,
or set the same JSON field. Existing TUN permissions, route exclusions, and platform requirements
still apply. For an embedded application, pass the option through its runtime argument list or
session file, and keep the secret in the dedicated secret parameter. See
TUN examples and the
application integration guide.
4. Verify traffic and policy enforcement
With the server set to required, start the client with
mlkem768-aes256. After it becomes ready, use another terminal to test traffic:
curl --fail --show-error --socks5-hostname 127.0.0.1:1080 https://example.com/
Then stop the test client normally with Ctrl-C and repeat the same connection command with
--e2e-crypto off. This connection must fail before it can carry flow
traffic against a server enforcing required. A policy or crypto negotiation
failure is expected; the documented crypto error is code 9,
crypto_negotiation. Restore --e2e-crypto mlkem768-aes256 and reconnect.
If both attempts succeed, check that you restarted the intended server, edited the active
configuration, and connected to the expected endpoint. An HTTPS request succeeding through a
server with optional policy is not proof of enforcement.
Troubleshooting
| Symptom | What to check |
|---|---|
crypto_negotiation / error 9 | Confirm the client requests mlkem768-aes256, the server policy is optional or required, and both packages support the same suite/version. There is no silent fallback to unprotected MEHS framing. |
| Unknown flag or rejected setting | Check the spelling and installed release on both endpoints. The client accepts off or mlkem768-aes256; optional and required are server policy values only. |
| Certificate or hostname failure | Correct the certificate, trust bundle, hostname, or system clock. Do not disable verification to make the connection work. |
| Authentication failure | Check the client ID and secret independently of the encryption settings. Enabling encryption does not replace client credentials. |
| Settings have no effect | Check the active server configuration path and client --session-config path, remove conflicting command-line overrides, and restart/reconnect. |
Add --verbose to the client command when troubleshooting, and redact sensitive
deployment details before sharing diagnostics. Keep secrets and private keys out of support
reports. Review the security policy; this feature is not a
claim of an independent security audit or protection against compromised endpoints.
Further reference: configuration settings, E2E protection and trust boundaries, and server administration.