schollz/croc · error · Error
Relay rejected the connection
Error message
Relay rejected the connection: ${response} What it means
During connectRelay the client completes a weak-key PAKE with the relay, derives a session key, and sends the relay password. The relay's reply must contain the '|||' separator between banner and external IP; anything else means the relay refused the handshake, and the raw decrypted response is included in the message. In practice the embedded text is the relay's rejection reason (e.g. 'incorrect password').
Solutions
- Read the response text inside the message; 'incorrect password' means the relay password is wrong
- Set settings.relayPassword to the exact passphrase the relay was started with (empty for none)
- Confirm the relay is a croc relay, not an arbitrary WebSocket endpoint
Example fix
// before
const settings = { relayAddress: "relay.example.com:9009", relayPassword: "" };
// relay started with: croc relay --passphrase hunter2
// after
const settings = { relayAddress: "relay.example.com:9009", relayPassword: "hunter2" }; Defensive patterns
Strategy: try-catch
Try / catch
try {
relay = await connectRelay(settings, room, port, signal);
} catch (e) {
if (/Relay rejected/.test(e.message)) {
throw new Error("Relay password incorrect. Update the relay password in settings.", { cause: e });
}
throw e;
} Prevention
- Store the relay password alongside the relay address so they change together
- Show the relay's embedded response text to the user; it states the reason
- Smoke-test credentials with the croc CLI before wiring them into the web app
When it happens
Trigger: settings.relayPassword not matching the relay's --passphrase; connecting to a relay that requires a password while sending the default empty one (or vice versa); the peer endpoint not actually being a croc relay.
Common situations: Self-hosted relays started with croc relay --passphrase; users switching between public and private relays without updating the password field; typos in the stored password.
Related errors
- Relay returned an invalid port list
- Gateway URL must use ws:// or wss://
- Message is too large
- not enough open ports to run local relay
- Relay connection closed while sending
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/c212895e181a563d.
Report an issue: GitHub.
Appendix: source
Thrown at web/src/protocol/client.ts:123
port: string,
signal?: AbortSignal,
) {
const engine = wasm();
const socket = await CrocSocket.connect(settings.gatewayURL, port, signal);
try {
const pake = await engine.pakeInit(WEAK_RELAY_KEY, 0, "siec");
await socket.send(pake.bytes);
const peer = await socket.receive();
const finished = await engine.pakeUpdate(pake.handle, peer);
const salt = randomBytes(8);
const key = await engine.deriveKey(finished.key, salt);
await socket.send(salt);
await socket.send(await engine.encrypt(textEncoder.encode(settings.relayPassword), key));
const response = textDecoder.decode(
await engine.decrypt(await socket.receive(), key),
);
const separator = response.indexOf("|||");
if (separator < 0) throw new Error(`Relay rejected the connection: ${response}`);
const banner = response.slice(0, separator);
const externalIP = response.slice(separator + 3);
await socket.send(await engine.encrypt(textEncoder.encode(room), key));
const confirmation = textDecoder.decode(
await engine.decrypt(await socket.receive(), key),
);
if (confirmation !== "ok") {
throw new Error(`Relay could not open the room: ${confirmation}`);
}
return { socket, banner, externalIP } satisfies RelayConnection;
} catch (error) {
socket.close();
throw error;
}
}
async function sendControl(
socket: CrocSocket,View on GitHub (pinned to e25f1bdc04)