Server setup

This guide covers running bibavpn-server, the TLS + WebSocket entrypoint. You need a reachable host (VPS or home server), an open TCP port, and consistent token + psk with every client.

For every flag, see AGENTS.md.

Before you start

  1. Choose a version — same tag as your clients (Releases or Docker tag :vX.Y.Z).
  2. Generate secrets (do not commit them):

bash export BIBA_VPN_TOKEN="$(openssl rand -hex 16)" export BIBA_VPN_PSK="$(openssl rand -hex 32)" export BIBA_INVITE_PASSPHRASE="$(openssl rand -base64 24)"

  1. Pick a hostname clients will use for TLS verification (SNI), e.g. vpn.example.com or your VPS IP. Point DNS A/AAAA at your server IP when using a name.
  2. Open the firewall for your listen port (commonly 443 or 8443).

TLS certificates (recommended: file-based self-signed)

The server must have TLS material. Modes:

Mode Flags Use
File self-signed (recommended for VPS without CA) --cert + --key Stable cert on disk; invite embeds pin_cert_pem — clients verify pin, no --insecure
PEM from CA --cert + --key Let’s Encrypt or commercial CA
In-memory self-signed --self-signed-san NAME Lab only — new cert each restart; invite sets insecure: true

Without --cert and --key, or without --self-signed-san, the server exits with an error.

Do not use --self-signed-san on a public VPS. Generate a cert once, mount it into Docker, and use --cert/--key so --print-invite-uri includes pinning.

Generate self-signed cert (once on the VPS)

Replace TLS_SAN with the same value clients use for --sni (domain or IP):

sudo mkdir -p /etc/bibavpn/certs
cd /etc/bibavpn/certs
export TLS_SAN="203.0.113.10"   # or vpn.example.com

sudo openssl req -x509 -newkey rsa:4096 -sha256 -days 825 -nodes \
  -keyout privkey.pem -out fullchain.pem \
  -subj "/CN=${TLS_SAN}" \
  -addext "subjectAltName=DNS:${TLS_SAN},IP:${TLS_SAN}" 2>/dev/null || \
sudo openssl req -x509 -newkey rsa:4096 -sha256 -days 825 -nodes \
  -keyout privkey.pem -out fullchain.pem \
  -subj "/CN=${TLS_SAN}" \
  -addext "subjectAltName=DNS:${TLS_SAN}"

sudo chmod 600 privkey.pem
sudo chmod 644 fullchain.pem

Run with Docker Hub image (secure self-signed)

source ~/bibavpn-secrets.env
export BIBAVPN_TAG=vX.Y.Z
export BIBA_PUBLIC="203.0.113.10:443"
export BIBA_SNI="203.0.113.10"

docker run -d --name bibavpn-server --restart unless-stopped \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=32m \
  -p 443:443 \
  -v /etc/bibavpn/certs/fullchain.pem:/certs/fullchain.pem:ro \
  -v /etc/bibavpn/certs/privkey.pem:/certs/privkey.pem:ro \
  eljaja/bibavpn-server:${BIBAVPN_TAG} \
  --listen 0.0.0.0:443 \
  --cert /certs/fullchain.pem \
  --key /certs/privkey.pem \
  --token "$BIBA_VPN_TOKEN" \
  --psk "$BIBA_VPN_PSK" \
  --ws-path /ws \
  --proto-domain default \
  --pad-mode adaptive \
  --decoy-max 24 \
  --max-pad 64 \
  --max-ws-binary 262144 \
  --ws-ping-secs 25 \
  --ack-profile balanced \
  --handshake-timeout-secs 15 \
  --mux-connect-timeout-secs 10 \
  --max-concurrent-sessions 512 \
  --auth-max-failures 10 \
  --auth-failure-window-secs 60 \
  --auth-ban-secs 300 \
  --log-level info \
  --print-invite-uri \
  --invite-passphrase "$BIBA_INVITE_PASSPHRASE" \
  --invite-public "$BIBA_PUBLIC" \
  --invite-sni "$BIBA_SNI"

Capture the invite:

docker logs bibavpn-server 2>&1 | grep 'biba://'

Share BIBA_INVITE_PASSPHRASE separately from the biba:// line.

Run a native binary on Linux

./bibavpn-server \
  --listen 0.0.0.0:8443 \
  --cert /etc/bibavpn/certs/fullchain.pem \
  --key /etc/bibavpn/certs/privkey.pem \
  --token "$BIBA_VPN_TOKEN" \
  --psk "$BIBA_VPN_PSK" \
  --ws-path /ws \
  --pad-mode adaptive \
  --decoy-max 24 \
  --max-pad 64 \
  --max-ws-binary 262144 \
  --ws-ping-secs 25 \
  --auth-max-failures 10 \
  --max-concurrent-sessions 512

Quick lab only (--self-signed-san, not for public VPS):

./bibavpn-server \
  --listen 0.0.0.0:8443 \
  --self-signed-san biba-server \
  --token "$BIBA_VPN_TOKEN" \
  --psk "$BIBA_VPN_PSK"

Biba Control Plane

If you use Biba Control Plane, provisioning applies this same model automatically (per-instance cert dir, pinned invite from server logs). See control-plane-provisioning.md.

Local Docker lab with invite (start.sh)

From a cloned repo on Linux/macOS/WSL:

bash start.sh --build

This uses in-memory self-signed for localhost lab only. For VPS, use file-based certs above.

HTTP camouflage (optional)

  • --camouflage-dir /var/www/cover
  • --camouflage-url http://127.0.0.1:8080

WebSocket upgrades still use --ws-path (default /ws).

Reverse proxy considerations

The upstream bibavpn-server process expects to perform the TLS handshake itself. If you terminate TLS in nginx, Caddy, or HAProxy, forward opaque TLS to the backend (TCP stream / ssl_preread) or run BibaVPN on the TLS-terminating tier.

Operational checklist

  • [ ] token and psk match on server and every client
  • [ ] --proto-domain matches on both sides (default default)
  • [ ] Firewall allows your listen port
  • [ ] SNI / --invite-sni matches cert SAN and client --sni
  • [ ] Clients use pin (from invite or --pin-cert), not --insecure
  • [ ] No --legacy-path-auth on production servers

What not to use in production

Avoid Why
--self-signed-san on VPS Unstable cert, no pin in invite
--insecure on clients Disables TLS verification
--legacy-path-auth Weaker auth path
Default token change-me Known default