# Running Your Own koridor Server koridor has no server component. It is a client that speaks standard SSH to a server you already operate — a VPS, a NAS, a home box, anything running `sshd`. There is nothing to install on the far end and no koridor-operated backend. This is the whole server story for normal TCP traffic. One optional feature — UDP port forwarding — does provision something on your server, and it is covered separately in step 6 because it changes your `authorized_keys`. ## 1. What you need - An SSH server you control, reachable from wherever the device will be. - An SSH keypair. **You supply the key** — koridor does not generate one for the tunnel, so create it first. ```bash ssh-keygen -t ed25519 -f ~/.ssh/koridor_key ``` The private key is what you paste into the app. It can have a passphrase; koridor prompts for it and keeps it in the OS credential store. ## 2. Server requirements (TCP) `AllowTcpForwarding yes` — and that is the entire requirement. It is **on by default** in OpenSSH, so most servers need no configuration at all. Check what yours is actually set to: ```bash sudo sshd -T | grep -i allowtcpforwarding ``` If it says `no`, enable it in `/etc/ssh/sshd_config`: ``` AllowTcpForwarding yes ``` ```bash sudo sshd -t && sudo systemctl reload sshd ``` `sshd -t` validates the config before the reload — run it, or a typo takes SSH out remotely. ### What you do *not* need This trips up people who have set up a VPN server before. None of the following applies to koridor: - **No `net.ipv4.ip_forward`.** koridor does not forward packets through the server; `sshd` opens each outgoing connection itself. - **No iptables or nftables masquerade rules.** Each TCP flow is an ordinary SSH `direct-tcpip` channel, so the server makes its own outbound connections and the kernel never routes between interfaces. - **No `/dev/net/tun` and no TUN interface on the server.** The TUN device lives on the client only. - **No root.** A normal unprivileged SSH user is enough. - **No konduit-style provisioning, bootstrap, or peer list.** There is no control API and nothing to reload. The cost on the server is one SSH session per active TCP flow. A busy browser tab set will show more `sshd` sessions than you might expect — that is expected, not a leak. ## 3. Add the profile In the app, create a connection profile: | Field | Value | |---|---| | Host | Your server's hostname or IP | | Port | SSH port, `22` unless you changed it | | Username | The SSH user whose key you installed | | Private key | Contents of the `.pub`-less private key file (PEM) | | Passphrase | Only if the key has one | The host may be a `~/.ssh/config` alias — koridor resolves it and pins the host key against whatever the alias resolves to, not the alias name. ## 4. Trust the host key The first connection to a new server does **not** connect. koridor shows you the host key fingerprint and waits for you to accept it. This is deliberate: the key is established once, on your say-so, and never silently assumed. Compare the fingerprint against the one SSH already knows: ```bash ssh-keygen -lf /etc/ssh/ssh_ed25519_key.pub ``` If the server is remote, compare against the fingerprint `ssh` shows you on first connect from a machine you trust. Once pinned, koridor checks the key on every connection. If it later changes, koridor stops and tells you — it does not retry or fall back, because a changed host key means you are no longer talking to the same machine. ## 5. Connect Toggle the profile on. Traffic routes through the tunnel, and DNS lookups go through the server's own resolver over the tunnel rather than to a third-party resolver. If the tunnel cannot recover after a drop, koridor tears the route down instead of leaving traffic to leak in the open. That is the intended behaviour, not a bug. Transient failures retry with exponential backoff up to roughly two minutes. Failures that retrying cannot fix — a private key that will not decode, a wrong passphrase, a rejected public key — fail immediately instead of sitting out that schedule. ## 6. Optional: UDP port forwarding **Off by default.** SSH has no UDP channel type, so koridor relays specific allow-listed UDP ports through a second, restricted SSH connection. Enabling it **provisions itself on your server**, automatically, the first time you turn it on: - Installs a managed script at `~/.koridor/udp-relay.sh` for the SSH user. - Adds one line to that user's `~/.ssh/authorized_keys`, marked `# koridor-udp-relay`, restricted with a forced command so the key can only run that relay. - Requires **`socat`** on the server. ```bash sudo apt install socat # or: dnf install socat ``` Both changes are idempotent and reversible — clearing the allow-list removes the `authorized_keys` line, leaving every other key in the file untouched. Every other line in `authorized_keys` is preserved across re-syncs. Ports are gated by destination port only, not destination IP: any host on an allow-listed port can be relayed to. Datagrams are piped through `socat` rather than framed, so two datagrams written back to back are not guaranteed to arrive as two separate reads — fine for WireGuard and similar retrying protocols, not fine as a general UDP transport. The main use case is running another UDP-based tool, most often a second VPN like WireGuard, through koridor. ## 7. Optional: TCP local forwards For per-app routing without the TUN, a profile can carry classic SSH local forward rules in `LOCAL_PORT:IP:PORT` form. These are ordinary `-L` forwards and need nothing extra on the server. ## Troubleshooting | Symptom | Cause | |---|---| | Refuses to connect, no fingerprint shown | A profile with no pinned host key. Accept the fingerprint on first connect. | | "host key changed" error | The server's key really changed — a rebuild, or you are not talking to the same host. Investigate before accepting the new one; koridor will not retry. | | Connects, but no traffic | Check `AllowTcpForwarding` with `sshd -T`. | | Works with `ssh`, fails in the app | Usually the key. koridor wants the private key contents, not a path, and the key must be PEM. | | Sits retrying for two minutes then fails | A fatal error being treated as transient: bad key, wrong passphrase, or rejected public key. These normally fail at once — if yours does not, the error text is the useful part. | | Second VPN (WireGuard) comes up but passes no traffic | WireGuard is UDP and UDP forwarding is off. See step 6. | | UDP forwarding fails at setup | `socat` missing on the server, or the SSH user cannot write to its own `~/.ssh/`. | Logs follow the usual pattern: connection failures retry on a backoff schedule, and anything that retrying cannot fix is reported immediately rather than silently repeated.