Configuration
mqvpn supports both INI and JSON config files. If the file content starts with {, it is parsed as JSON; otherwise as INI. CLI arguments override config values.
INI Format
Server
# /etc/mqvpn/server.conf
[Interface]
Listen = 0.0.0.0:443
Subnet = 10.0.0.0/24
Subnet6 = 2001:db8:1::/112
# MTU = 1280
[TLS]
Cert = /etc/mqvpn/server.crt
Key = /etc/mqvpn/server.key
[Auth]
Key = mPyVpoQWcp/5gr404xvS19aRC03o0XS2mrb2tZJ1Ii4=
User = alice:<ALICE_PSK>
User = bob:<BOB_PSK>
[Multipath]
Scheduler = wlb
# CC = bbr2 # Congestion control (bbr2|bbr|cubic|none)Client
# /etc/mqvpn/client.conf
[Server]
Address = 203.0.113.1:443
[Auth]
Key = mPyVpoQWcp/5gr404xvS19aRC03o0XS2mrb2tZJ1Ii4=
[Interface]
TunName = mqvpn0
DNS = 1.1.1.1, 8.8.8.8
LogLevel = info
# MTU = 1280
[Multipath]
Scheduler = wlb
# CC = bbr2 # Congestion control (bbr2|bbr|cubic|none)
Path = eth0
Path = wlan0JSON Format
JSON config is useful for structured management and automation tooling.
Server
{
"mode": "server",
"listen": "0.0.0.0:443",
"tun_name": "mqvpn0",
"log_level": "info",
"subnet": "10.0.0.0/24",
"subnet6": "2001:db8:1::/112",
"cert_file": "/etc/mqvpn/server.crt",
"key_file": "/etc/mqvpn/server.key",
"auth_key": "<YOUR_PSK_HERE>",
"users": [
{ "name": "alice", "key": "<ALICE_PSK>" },
{ "name": "bob", "key": "<BOB_PSK>" }
],
"max_clients": 64,
"scheduler": "wlb",
"cc": "bbr2"
}Client
{
"mode": "client",
"server_addr": "203.0.113.1:443",
"tun_name": "mqvpn0",
"log_level": "info",
"auth_key": "<YOUR_PSK_HERE>",
"insecure": false,
"dns": ["1.1.1.1", "8.8.8.8"],
"kill_switch": false,
"reconnect": true,
"reconnect_interval": 5,
"scheduler": "wlb",
"cc": "bbr2",
"paths": ["eth0", "wlan0"]
}Multi-User Authentication
The server can authenticate multiple users, each with their own PSK. In JSON config, add a users array where each entry is either an object ({"name":"alice","key":"..."}) or a shorthand string ("alice:key"). In INI config, use repeatable User = NAME:KEY lines in the [Auth] section. You can also use the Control API to manage users at runtime.
When both auth_key (global key) and users are set, clients can authenticate with either. To restrict access to named users only, remove auth_key from the config.
Removing a user via the Control API also disconnects any active sessions authenticated with that username.
Monitoring requires per-user keys
Sharing a single auth_key across multiple clients works for the VPN data plane, but the Control API and the Prometheus exporter identify clients by their user label. Sessions authenticated with the global auth_key are reported as user="(global)", so multiple clients collide on the same label and the Prometheus scrape is dropped. For multi-client monitoring give each client its own entry under users (or register them at runtime via add_user).
Running with Config Files
sudo mqvpn --config /etc/mqvpn/server.conf
sudo mqvpn --config /etc/mqvpn/server.jsonConfig Reference
[Server] (client only)
| Key | Description | Default |
|---|---|---|
Address | Server address (HOST:PORT, e.g. [2001:db8::1]:443 for IPv6) | Required |
Insecure | Skip TLS certificate verification | false |
[Interface]
| Key | Description | Default |
|---|---|---|
Listen | Listen address (HOST:PORT, server only) | 0.0.0.0:443 |
Subnet | Client IPv4 pool (server only) | 10.0.0.0/24 |
Subnet6 | Client IPv6 pool (server only) | — |
TunName | TUN device name | mqvpn0 |
DNS | DNS servers (comma-separated) | — |
LogLevel | Log level (debug, info, warn, error) | info |
KillSwitch | Block traffic outside the VPN tunnel (client only) | false |
Reconnect | Enable automatic reconnection (client only) | true |
ReconnectInterval | Seconds between reconnection attempts | 5 |
MTU | TUN MTU (1280–9000). Client: cap — if the negotiated MTU is lower, the negotiated value is used. Server: sets the TUN MTU directly. | auto (client ~1382 negotiated, server 1382) |
[TLS] (server only)
| Key | Description | Default |
|---|---|---|
Cert | TLS certificate path (PEM) | Required |
Key | TLS private key path (PEM) | Required |
[Auth]
| Key | Description | Default |
|---|---|---|
Key | Pre-shared key (base64, generate with mqvpn --genkey) | Required unless User is set |
User | Per-user PSK in NAME:KEY format (repeatable) | — |
MaxClients | Maximum concurrent clients (server only) | 64 |
[Multipath]
| Key | Description | Default |
|---|---|---|
Scheduler | Scheduler algorithm (minrtt, wlb, wlb_udp_pin, or backup_fec) | wlb |
CC | Congestion control algorithm (bbr2, bbr, cubic, or none) | bbr2 |
Path | Network interface to bind (repeatable) | Default interface |
See Multipath for scheduler details.
backup_fecis experimental and requires both peers to run mqvpn ≥ 0.4.0 with FEC build enabled (-DXQC_ENABLE_FEC=ON -DXQC_ENABLE_XOR=ON). See Multipath.
CC = none(no congestion control) requires xquic built with-DXQC_ENABLE_UNLIMITED=ON.
MTU Guidelines
Default (auto) — most deployments
For most setups, leave MTU unset. The auto-negotiated value (~1382) works on standard Ethernet (1500), PPPoE (1492), and mobile networks.
When to set MTU explicitly
| Scenario | Recommendation |
|---|---|
| Standard Ethernet / mobile | Leave unset (auto ~1382) |
| Deeply nested tunnels (mqvpn → WG → another tunnel) | Calculate remaining MTU; set if near 1280 |
On the client, if MTU is set, mqvpn uses min(config MTU, negotiated MTU); a warning is logged when the config value exceeds the negotiated value. On the server, MTU sets the TUN MTU directly (default 1382).
TIP
On the client, setting MTU above the negotiated value (~1382) has no effect — the negotiated value is always the upper bound. On the server, the configured value is applied to the TUN device as-is; packets exceeding a client's negotiated MSS are answered with ICMP Packet Too Big so that the sender's Path MTU Discovery can adjust.
How mqvpn determines TUN MTU
mqvpn negotiates the TUN MTU from the QUIC DATAGRAM Maximum Segment Size (MSS) at connection time. With the default max_pkt_out_size of 1400, the overhead breakdown is:
max_pkt_out_size 1400 bytes
− QUIC short header 13 bytes
− DATAGRAM frame header 3 bytes
− MASQUE datagram header 2 bytes
─────────
= TUN MTU 1382 bytesThis negotiation happens at connection time on the client, and the client TUN MTU follows it. The server sets its TUN MTU once at startup (1382 by default, or the configured value). When a packet exceeds a particular client's negotiated MSS, the server returns ICMP Packet Too Big to the original sender, carrying that client's MSS as the MTU value; the sender's Path MTU Discovery then lowers its packet size. The shared server TUN MTU never needs to shrink for individual clients.
Running other tunnels inside mqvpn
When running a tunnel protocol (WireGuard, IPsec, GRE, etc.) inside the mqvpn tunnel, the inner tunnel's overhead reduces the effective MTU. Verify that the remaining MTU meets the inner protocol's requirements.
Example: WireGuard inside mqvpn
mqvpn TUN MTU 1382 bytes
− WireGuard overhead (IPv6) 80 bytes
─────────
= WireGuard inner MTU 1302 bytes
→ IPv6 minimum (1280) ✓
→ QUIC/HTTP3 UDP payload 1254 bytes > 1200 ✓Constraints
| Constraint | Value | Source |
|---|---|---|
| Config minimum | 1280 | IPv6 minimum MTU (RFC 8200) |
| Config maximum | 9000 | Jumbo frame MTU |
| QUIC minimum UDP payload | 1200 | RFC 9000 §14 (handshake requirement) |
| Auto value | ~1382 (client: negotiated; server: fixed default) | Derived from max_pkt_out_size (1400) |
Control API
A running server can be managed at runtime over a local TCP socket using JSON commands. This is useful for adding or removing users without restarting the server.
Enable
sudo mqvpn --mode server ... --control-port 9090The control API binds to 127.0.0.1 by default. It has no authentication, so only bind to trusted interfaces.
Enable from config file
The control API can also be enabled from /etc/mqvpn/server.conf:
[Control]
Listen = 127.0.0.1:9090…or from the JSON equivalent:
{
"control_listen": "127.0.0.1:9090"
}CLI flags (--control-port, --control-addr) override the config-file values per field. --control-port 0 explicitly disables the API even if [Control] Listen is set in the config.
Commands
Add a user:
echo '{"cmd":"add_user","name":"carol","key":"carol-secret"}' | nc 127.0.0.1 9090Remove a user:
echo '{"cmd":"remove_user","name":"carol"}' | nc 127.0.0.1 9090Removing a user also disconnects any active sessions authenticated with that username.
List users:
echo '{"cmd":"list_users"}' | nc 127.0.0.1 9090Get stats:
echo '{"cmd":"get_stats"}' | nc 127.0.0.1 9090Get detailed status (per-client, per-path):
echo '{"cmd":"get_status"}' | nc 127.0.0.1 9090Or use the built-in status command for human-readable output:
mqvpn --status --control-port 9090All commands return a JSON response with an "ok" field. Each connection handles one command, then the server closes the connection.
systemd
If you installed via the deb package or install.sh, the systemd units are already in place. For source builds, install manually:
sudo cmake --install build --prefix /usr/localServer
If you used install.sh, /etc/mqvpn/server.conf is already generated. To configure manually, copy the example:
sudo cp /etc/mqvpn/server.conf.example /etc/mqvpn/server.conf
sudo vi /etc/mqvpn/server.conf # edit cert paths, auth key, etc.
sudo systemctl enable --now mqvpn-serverClient (template unit)
The client uses a template unit — the instance name maps to the config file:
sudo cp /etc/mqvpn/client.conf.example /etc/mqvpn/client-home.conf
sudo vi /etc/mqvpn/client-home.conf # edit server address, auth key, etc.
sudo systemctl enable --now mqvpn-client@home
# → reads /etc/mqvpn/client-home.confINFO
The systemd units expect INI .conf files. The server unit's NAT helper scripts also parse the INI config directly, so JSON cannot be used with the standard units as-is.