Server and client configuration
What lives in server.conf, users.conf and client.conf, which settings are pushed, and which remain local.
Three files, one flat-INI model
/etc/qeli/server.conf stores global sections and transport profiles. /etc/qeli/users.conf stores users and groups. Device-side client.conf holds connection data and local client behaviour. A comment is valid only on its own line.
$ sudo qeli check-config --config /etc/qeli/server.conf
$ sudo qeli check-config --client --config /etc/qeli/client.conf
What the link, push and client file carry
The link defines connection and handshake data. Server push changes network settings without reissuing the link. Device behaviour stays on the client.
| Source | Carries | How it changes |
|---|---|---|
qeli:// | Address, port, credentials, mode, pinning/REALITY, handshake parameters and optional MTU/name | Reissue the link when any of these values changes |
| Server push | Client and server IPs, pool prefix, MTU, DNS, keepalive and routes | At the next connection; the link stays unchanged |
client.conf | gateway, route_local, kill_switch, include/exclude, dns, persist_tun | Changed on the device or in Client manager |
users.conf | profiles, max_sessions, allowed_networks, route, client_subnet, quotas | Most fields apply on reload without dropping sessions |
Server profile keys
Each [profile:name] has its own port, transport, TUN and pool. One service can run multiple profiles at once.
| Key | Default | Purpose |
|---|---|---|
bind.address | 0.0.0.0 | Profile listener address |
bind.port | 443 | TCP/UDP port; must not collide with another profile |
bind.transport | tcp | Carrier transport: tcp or udp |
tun.address | 10.9.0.1 | Server IP inside the tunnel and the usual next hop |
tun.name | vpn0 | TUN name; must be unique across profiles |
tun.mtu | 1400 | Profile MTU, pushed to clients |
tun.queues | 0 | 0 = CPU count, 1 = single-threaded pump, N = explicit queue count |
pool.cidr | 10.9.0.0/24 | Single source for the TUN prefix and address pool |
pool.reservation.<user> | — | Reserves a pool IP for a user |
obf.mode | fake-tls | Wire mode: plain, fake-tls, obfs or reality-tls |
route | — | Repeatable route pushed to profile clients |
routing.nat.enabled | false | Source NAT for client egress through the server |
routing.nat.interface | eth0 | NAT egress interface; at the default value the server tries to detect it automatically |
routing.forward_private | true | Allows forwarding to private networks behind the server |
routing.client_to_client | false | Allows traffic between VPN clients in the profile |
dns.enabled | false | Starts the built-in DNS proxy |
dns.push_servers | — | Explicitly sets the DNS address clients receive |
perf.connection.max_clients | 128 | Profile-wide active session limit |
Built-in DHCP: when it is needed and what it guarantees
DHCP is enabled per profile only for TAP/L2 scenarios. A normal L3 TUN receives its address during AUTH, so dhcp.enabled=false remains the correct default.
| Key | Default | Purpose |
|---|---|---|
dhcp.enabled | false | Starts DHCPv4 on the profile actual TUN/TAP interface |
dhcp.listen | empty | Logical address and port; empty uses tun.address:67. Explicit 0.0.0.0 is rejected |
dhcp.pool_start / pool_end | — | Optional range inside the usable part of pool.cidr; otherwise selected automatically |
dhcp.lease_time_secs | 86400 | Lease lifetime in seconds |
dhcp.domain_name | vpn | Domain name sent to clients |
0.8.1 tightens the lease lifecycle: the server distinguishes SELECTING/INIT-REBOOT/RENEWING/REBINDING REQUEST states, sends NAK only when allowed by state and authoritative server identity, frees addresses on RELEASE, quarantines DECLINE and reaps expired leases. Relay and broadcast follow DHCP rules.
dns.push_servers; an empty list means no DNS option, not silently selected public resolvers.0.0.0.0:67, but Qeli binds it through SO_BINDTODEVICE to the profile actual interface only. An unsafe or malformed listen address is rejected at startup.Users, groups and limits
A user's own values override its group. Per-user route entries replace profile routes for that user.
| Key | Default | Purpose |
|---|---|---|
password_hash | — | Argon2id hash used for authentication |
password_enc | — | Encrypted copy used to reissue a link; the key is stored in /var/lib/qeli |
enabled | true | Allows or blocks new connections |
profiles | all | List of permitted profiles |
group | — | Limits and ACL template from [group:name] |
max_sessions | 0 | Concurrent device limit; 0 = group value or unlimited |
static_ip | — | Fixed user address from pool.cidr |
allowed_networks | any | Destination CIDR allowlist; empty = unrestricted |
route | — | Per-user route pushed to the client |
client_subnet | — | Inbound route to the network behind this client, analogous to iroute |
bandwidth.limit_mbps | 0 | Bandwidth limit; 0 = unlimited or group value |
data_limit_gb | 0 | Lifetime download quota; upload is accounted separately |
expire_at | — | Unix time when the account expires |
Connection and local behaviour keys
Connection fields normally come from importing qeli://. Gateway, DNS, routing and device-behaviour policies are not included in the link; an explicit MTU is the documented exception.
| Key | Source | Purpose |
|---|---|---|
server, user, pass | qeli:// | Address and credentials |
proto, mode | qeli:// | Transport and mode must match the profile |
key, reality_sid, sni | qeli:// | Pinning and REALITY/H-1 parameters |
gateway | local | true enables full tunnel; false keeps split tunnel |
route_local | local | Also sends private networks through the tunnel |
include, exclude | local | Additional CIDRs routed through or around the tunnel |
route_file | Windows/macOS | CIDR file that locally extends include in split-tunnel mode |
kill_switch | local | Blocks full-tunnel leaks while the connection is down |
allow_ipv6_leak | local | Allows IPv6 over the physical path only when the negotiated full tunnel lacks IPv6; this explicitly accepts a leak |
apps, apps_mode | platform-specific | Per-app routing on Windows, macOS and Android |
allow_lan | Android | Keeps the home LAN and local multicast outside the VPN |
dns | local | Resolver management mode: tunnel, off, or the system alias |
mtu | link/push/local | 0 accepts push/auto-selection; a value above 0 overrides it |
persist_tun | local | Keeps the adapter and routes between reconnects |
keepalive, tcp_nodelay | local | Client TCP socket settings |
recv_buffer_size | local | Initial UDP receive buffer size; automatic growth is capped by sysctl |
forward | local | Site-to-site forwarding without NAT; applied on Linux, Windows and macOS |
gateway_nat, lan_subnet | local | Client gateway mode for the local network |
exit_node | Linux | Sends other clients' traffic through this node's physical WAN |
dev_attach | Linux CLI | Attaches an existing TUN whose addressing and routing are owned by an external controller |
When reload is enough
reload covers users and some runtime settings. Anything that changes a listener, transport or TUN device requires a full restart and briefly drops sessions.
| Change | Action | Effect |
|---|---|---|
| Users, groups, ACLs and limits | sudo systemctl reload qeli | No active-session drop when the field supports runtime reload |
web.allowed_ips, allowed_origins, public_host | reload | Reloaded live |
bind.*, web.bind/port/tls/base_path | sudo systemctl restart qeli | The socket changes, so a full restart is required |
tun.*, pool.cidr, obf.* | restart | The profile and TUN are recreated |
rotate-identity | restart | Clients must update the pinned key |
Where to find the complete list
This page covers the operator keys used most often. Rare transport, shaping, multipath, logging, hook and platform-specific settings are listed in the full reference.