Files
konduit-public/docs/server-quickstart.md
E. Kaparulin c61664ca70 docs: correct the self-hosted server path and document server.toml
Audit of the public repo against the actual server and konduit-ctl source.
The happy path was documented; the failure modes were not.

- server.toml had no reference at all. Add docs/server-config.md covering
  every key, [tls], the control API, and key handling.
- The mantra could not recover the identity key on its own:
  derive_from_passphrase needs the [kdf] salt, which is 32 random bytes
  generated once at bootstrap. bootstrap says so in the file it generates
  (core/src/config.rs:403); the quickstart claimed the opposite. Fixed, with
  a backup step added before it is needed rather than after.
- konduit-server never appeared in a download step, yet
  konduit-server.service expects /opt/konduit/konduit-server - a path no
  instruction created. Added the install section and moved setcap after the
  copy, since capabilities do not survive every cross-filesystem copy and a
  binary that silently lost cap_net_bind_service fails at bind time.
- Hot config reload was a headline feature with no documented mechanism. It
  is a control API on 127.0.0.1:3001, and add-client only edits the file on
  disk, so a new peer is not live until something reloads it. Documented,
  including that /reload does not disconnect revoked peers but /sync does.
- [tls] native termination was entirely undocumented; the key must be PKCS#8.
- Dropped a README claim about a konduit-admin-ui unit that does not exist,
  and fixed a systemd copy path that only resolved if this repo had been
  cloned to /opt/konduit.

#k-ops/#1/#211
2026-10-10 22:43:47 +03:00

7.3 KiB

Konduit Server Quickstart

Running your own Konduit server takes three binaries: konduit-server (the VPN server), konduit-ctl (provisioning), and konduit (a client, for testing).

1. Install

Download konduit-server-<version>-linux-x86_64 and konduit-ctl from the Releases page, plus konduit-<version>-linux-x86_64 if you want to test the tunnel from the same host.

sudo mkdir -p /opt/konduit
sudo cp konduit-server konduit-ctl konduit /opt/konduit/
cd /opt/konduit
sudo chmod +x konduit-server konduit-ctl konduit

Run everything from /opt/konduit — konduit-ctl writes server.toml into the current directory, and the systemd unit expects the config at /opt/konduit/server.toml.

Capabilities

Grant the binary permission to create TUN devices and bind port 443 without running as root:

sudo setcap cap_net_admin,cap_net_bind_service=+ep /opt/konduit/konduit-server

Do this after copying the binary. Capabilities are stored as an extended attribute on the file, and a copy made across filesystems can drop them — a konduit-server that quietly lost cap_net_bind_service fails with a permission error the moment it tries to bind 443. The unit in step 6 runs as User=root, where capabilities are irrelevant; this matters for running it by hand.

To confirm what the binary actually ended up with:

getcap /opt/konduit/konduit-server

2. Prerequisites

IP forwarding

sudo sysctl -w net.ipv4.ip_forward=1
# Persist: add 'net.ipv4.ip_forward=1' to /etc/sysctl.conf

TUN device

ls /dev/net/tun || sudo modprobe tun

Without it the server cannot create its interface and exits at startup.

3. Bootstrap

Konduit derives its identity key from a mantra (secret phrase) using Argon2id.

cd /opt/konduit
echo "your secret mantra phrase here" | ./konduit-ctl bootstrap -l vpn.example.com:443 -p -

-l sets both the port the server binds and the hostname written into every client config, so use the name you will actually serve. Bootstrap refuses to overwrite an existing server.toml unless you pass --force.

It prints the server's public identity key — clients need this, and it is already inside the generated .client.toml files.

Behind a reverse proxy (HAProxy on 443, konduit on 8443):

echo "your secret mantra phrase here" | \
  ./konduit-ctl bootstrap -l vpn.example.com:8443 --public-port 443 -p -

--public-port sets the port written into client configs, so they connect to 443 even though konduit listens on 8443 — useful when running behind any TCP-passthrough reverse proxy (e.g. HAProxy, nginx stream module). Konduit can also terminate TLS itself; see [tls] in the server configuration reference.

Back up server.toml now, before you need it. It holds the server's private identity key in plaintext, and its [kdf] salt_hex is 32 random bytes generated once at bootstrap. The mantra is only half the key material — if you lose the file you lose the salt, and the mantra alone cannot regenerate the key. See Key handling.

sudo install -m 600 server.toml /root/konduit-server.toml.bak

4. Add a Client

./konduit-ctl add-client my-laptop

Creates my-laptop.client.toml and appends the peer to server.toml. Transfer the .client.toml to the client device, or scan the QR payload with the mobile app.

The running server does not see this yet. add-client only edits the file on disk; the live peer list updates on the next reload. Either add --reload to the command, or reload separately:

./konduit-ctl add-client my-laptop --reload
# or, on an already-running server:
./konduit-ctl reload

--reload talks to the control API on 127.0.0.1:3001. Running konduit-ctl on a different machine than the server needs --control-url, and the API is bound to loopback — see Reloading before changing that.

5. TUN Interface Name

Set a fixed TUN interface name so firewall rules are stable:

# server.toml
[network]
tun_name = "konduit0"

The default is tun0, but konduit falls back to the first free tunN if that name is taken. On a host that also runs WireGuard or OpenVPN the name then shifts underneath you and any firewall rule keyed to it stops matching. The masquerade rule in step 7 uses this name.

6. Run

./konduit-server --config server.toml

Check the file before starting for real:

./konduit-server --config server.toml --validate

For persistent operation, use the provided systemd units in docs/systemd/. Copy konduit-server.service to /etc/systemd/system/, then:

sudo systemctl daemon-reload
sudo systemctl enable --now konduit-server
sudo journalctl -u konduit-server -f

7. NAT / Masquerade

VPN clients send packets with source IP 10.10.0.x — the /24 implied by the default server_ip = "10.10.0.1". Masquerade rewrites this to the server's public IP so internet hosts can reply. If you changed server_ip, match the rule to the new subnet.

firewalld (RHEL, Fedora, recent Ubuntu)

# Trust the VPN interface
sudo firewall-cmd --zone=trusted --add-interface=konduit0 --permanent

# Enable masquerade on the public zone
sudo firewall-cmd --zone=public --add-masquerade --permanent

sudo firewall-cmd --reload

Important: iptables commands are silently ignored on firewalld systems. Always use firewall-cmd.

iptables (Debian/Ubuntu without firewalld)

IFACE=$(ip route get 1.1.1.1 | awk '{print $5; exit}')
sudo iptables -t nat -I POSTROUTING 1 -s 10.10.0.0/24 -o $IFACE -j MASQUERADE
sudo iptables -I FORWARD 1 -i konduit0 -o $IFACE -j ACCEPT
sudo iptables -I FORWARD 2 -i $IFACE -o konduit0 -m state --state RELATED,ESTABLISHED -j ACCEPT

# Persist
sudo apt install iptables-persistent && sudo netfilter-persistent save

8. Verify

# Connect a client and confirm the tunnel comes up
./konduit --config my-laptop.client.toml

# Is the peer actually connected?
./konduit-ctl peers

# Check konduit logs
journalctl -u konduit-server -f

./konduit-ctl peers returning your peer's address is the check worth having — it distinguishes "the client authenticated" from "the client is routing traffic", and only the second one proves NAT is set up.

Troubleshooting

Symptom Cause
Permission denied binding 443 cap_net_bind_service was lost in a copy. Re-run setcap against the final path and confirm with getcap.
No such file or directory on startup /dev/net/tun missing — sudo modprobe tun.
Tunnel connects, but no internet Masquerade missing or scoped to the wrong subnet. Check step 7 matches your server_ip.
iptables rules look right but do nothing firewalld is running and ignoring them. Use firewall-cmd.
New client cannot connect Peer was added to server.toml but never reloaded. Run konduit-ctl reload.
Revoked client still has access /reload does not disconnect live sessions. Restart the service.
IP pool exhausted (10.10.0.10-254) The /24 is full. Widen netmask or move server_ip to another subnet.

For more detail than the table, set RUST_LOG=debug:

sudo systemctl edit konduit-server
# [Service]
# Environment=RUST_LOG=debug