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
- Choose a version — same tag as your clients (Releases or Docker tag
:vX.Y.Z). - 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)"
- Pick a hostname clients will use for TLS verification (SNI), e.g.
vpn.example.comor your VPS IP. Point DNS A/AAAA at your server IP when using a name. - Open the firewall for your listen port (commonly
443or8443).
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-sanon a public VPS. Generate a cert once, mount it into Docker, and use--cert/--keyso--print-invite-uriincludes 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-domainmatches on both sides (defaultdefault) - [ ] Firewall allows your listen port
- [ ] SNI /
--invite-snimatches cert SAN and client--sni - [ ] Clients use pin (from invite or
--pin-cert), not--insecure - [ ] No
--legacy-path-authon 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 |