decolua/9router · error · Error
MITM server failed to start. ${reason}
Error message
MITM server failed to start. ${reason} What it means
After spawning the MITM server process, startServer polls its health endpoint for up to 8 seconds (pollMitmHealth(8000, MITM_PORT)). If the server never becomes healthy, it kills the spawned process and throws 'MITM server failed to start. <reason>'. The reason is startError if one was captured during spawn/boot, otherwise a hint to check sudo password or port 443, plus the owner of port 443 if one was detected.
Source
Thrown at src/mitm/manager.js:716
});
serverProcess.on("exit", (code) => {
log(`Server exited (code: ${code})`);
serverProcess = null;
serverPid = null;
try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ }
try { fs.unlinkSync(LOCK_FILE); } catch { /* ignore */ }
// Auto-restart on unexpected exit
if (code !== 0 && !mitmIsRestarting) scheduleMitmRestart(apiKey);
});
}
const health = await pollMitmHealth(8000, MITM_PORT);
if (!health) {
if (serverProcess && !serverProcess.killed) { try { serverProcess.kill(); } catch { /* ignore */ } serverProcess = null; }
const processUsing443 = getProcessUsingPort443();
const portInfo = processUsing443 ? ` Port 443 already in use by ${processUsing443}.` : "";
const reason = startError || `Check sudo password or port 443 access.${portInfo}`;
throw new Error(`MITM server failed to start. ${reason}`);
}
if (_updateSettings) await _updateSettings({ mitmCertInstalled: true }).catch(() => { });
log(`✅ Server healthy (PID: ${serverPid || health.pid})`);
// Log DNS status per tool
const dnsStatus = checkAllDNSStatus();
for (const [tool, active] of Object.entries(dnsStatus)) {
log(`🌐 DNS ${tool}: ${active ? "✅ active" : "❌ inactive"}`);
}
await saveMitmSettings(true, sudoPassword);
if (sudoPassword) setCachedPassword(sudoPassword);
// Server is healthy — remove lock file (PID file persists as the marker)
try { fs.unlinkSync(LOCK_FILE); } catch { /* ignore */ }
View on GitHub (pinned to 90b52e06ff)
Solutions
- Read the reason suffix: if it names the port-443 owner, stop that service or retry with forceKillPort443=true (the start call's force flag / dashboard prompt 'kill and continue')
- Verify the sudo password is correct and re-save it — a bad password breaks privileged cleanup and the 443 bind on Linux/macOS
- Free port 443 manually (`sudo lsof -i :443` / `netstat -ano | findstr :443`) then retry; on Windows authorize the app to kill the owner
- Reproduce the child's boot error directly: run the runtime mitm server.js by hand from the MITM_DIR to see the real crash, fix it (reinstall if files are corrupt), and start again
Example fix
// before
await startServer(apiKey, maybeStalePassword);
// after: pre-check the port and pass a fresh password + force flag
const inUse = getProcessUsingPort443();
if (inUse) console.warn('port 443 held by', inUse);
await startServer(apiKey, await loadEncryptedPassword(), /* forceKillPort443 */ true); Defensive patterns
Strategy: try-catch
Validate before calling
const { execSync } = require('child_process');
function port443Owner() {
try {
return execSync('lsof -ti :443', { encoding: 'utf-8' }).trim() || null; // win: netstat -ano | findstr :443
} catch { return null; }
}
if (port443Owner()) throw new Error('Free port 443 (or pass forceKillPort443) before starting the MITM proxy.'); Try / catch
try {
await startServer(apiKey, password, forceKill);
} catch (e) {
if (/MITM server failed to start/.test(e.message)) {
if (/port 443/i.test(e.message)) {
await stopPort443Owner(); // stop nginx/IIS/Apache or pass forceKillPort443=true
} else if (/sudo|password/i.test(e.message)) {
await reSaveSudoPassword();
}
await startServer(apiKey, password, forceKill); // single retry after remediation
} else throw e;
} Prevention
- Reserve port 443 for 9Router — disable/stop IIS, Apache, nginx, or other HTTPS proxies on the same host
- Keep a valid saved sudo password; a bad password is a top cause of privileged-bind failure
- After a failed start, run the runtime mitm server.js manually to see the child's real boot error
- Increase tolerance on slow machines by remediating port conflicts before start rather than racing the 8s health window
When it happens
Trigger: Port 443 occupied by another process (web server, IIS, Apache, another proxy) that wasn't detected/killable; sudo password wrong so leftover-process cleanup or privileged bind failed; the server.js process crashed at boot (bad runtime files, missing Node runtime, config error); firewall/security software blocking the bind or the health probe; slow machine where 8s wasn't enough.
Common situations: Windows with IIS or `http.sys` bound to 443; Docker/nginx already on 443; wrong sudo password saved so the child couldn't bind port 443 without root; stale PID file pointing at a process that just died; corporate EDR killing the spawned mitm server.js immediately.
Related errors
- EADDRINUSE
- Health check timeout after ${HEALTH_CHECK.timeoutMs}ms
- Health check timeout after ${HEALTH_CHECK.timeoutMs}ms
- [Tailscale] health check timed out, will retry via watchdog
- Machine ID is required for Cursor API
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/4361ea9bc9ab81d4.
Report an issue: GitHub.