How the code-server Route System Handles Login, VS Code, Health, and Error Endpoints

The code-server route system centrally registers all HTTP and WebSocket endpoints in src/node/routes/index.ts, mounting specialized routers for login, health checks, and VS Code proxying, while attaching global error handlers last to catch unhandled exceptions.

The route system in the coder/code-server repository organizes all server-side HTTP and WebSocket traffic through a layered Express architecture. Located under src/node/routes, the system separates concerns into modular routers that handle authentication, IDE proxying, health monitoring, and error rendering. Understanding this routing architecture is essential for debugging deployment issues or extending the server's functionality.

Route Registration Architecture

The register function in src/node/routes/index.ts serves as the central configuration point for the entire application. It initializes both an Express HTTP router (app.router) and a WebSocket router (app.wsRouter), applies shared middleware, and mounts individual route modules in a specific order.

Shared Middleware Setup

Before mounting routes, the registration function disables the default x-powered-by header and installs cookie parsing middleware. It then attaches a common middleware that executes on every incoming request (except /healthz) to update the server's heartbeat state. According to lines 66‑74, this middleware calls the heartbeat update logic to track server activity. Lines 76‑80 populate essential request properties including req.args, req.heart, req.settings, req.updater, and req.cookieSessionName, making configuration and state accessible to all downstream route handlers.

Mounting Individual Routers

The registration function mounts route modules at specific paths based on the authentication configuration. Lines 54-L56 attach the health router:

app.router.use("/healthz", health.router)
app.wsRouter.use("/healthz", health.wsRouter.router)

For authentication, the system conditionally mounts the login router. When args.auth === AuthType.Password, lines 57-L59 register the login and logout endpoints:

app.router.use("/login", login.router)
app.router.use("/logout", logout.router)

If password authentication is disabled, the system redirects users instead of rendering login pages.

The VS Code proxy mounts at multiple prefixes to ensure compatibility. Lines 70-L73 loop through ["/vscode", "/"] to mount the VS Code router on both paths:

for (const routePrefix of ["/vscode", "/"]) {
  app.router.use(routePrefix, vscode.router)
  app.wsRouter.use(routePrefix, vscode.wsRouter.router)
}

Error Handler Attachment

The system mounts error handlers after all other routes to ensure they catch unhandled exceptions. Lines 75-L80 first add a catch-all route that throws a 404 error for undefined paths, then attach the HTTP error handler. Lines 80-L81 similarly attach the WebSocket error handler:

app.router.use(() => { throw new HttpError("Not Found", HttpCode.NotFound); })
app.router.use(errorHandler)
app.wsRouter.use(wsErrorHandler)

Login Endpoint Handling

The login module in src/node/routes/login.ts manages both page rendering and credential validation, implementing rate limiting to prevent brute-force attacks.

GET /login Page Rendering

When users access GET /login, the router invokes the getRoot function to serve the authentication page. Lines 28‑53 show this function reading src/browser/pages/login.html, injecting internationalization strings, and returning the final HTML:

router.get("/", async (req, res) => {
  res.send(await getRoot(req))
})

POST /login Validation and Rate Limiting

The POST handler validates credentials using a RateLimiter defined at lines 12‑25. This limiter allows 2 attempts per minute and 12 per hour. Lines 77-L80 check the limiter before processing:

if (!limiter.canTry()) { throw new Error(i18n.t("LOGIN_RATE_LIMIT")); }

Lines 86-L92 validate the password against environment variables or configuration:

const { isPasswordValid, hashedPassword } = await handlePasswordValidation(...);

Upon successful validation, lines 97-L99 set a signed session cookie and redirect the user:

res.cookie(req.cookieSessionName, hashedPassword, getCookieOptions(req));
return redirect(req, res, to, { to: undefined });

Failed attempts invoke limiter.removeToken() at lines 103-L106 before throwing an "Incorrect Password" error.

Health-Check Endpoint

The health module in src/node/routes/health.ts provides both HTTP and WebSocket interfaces for monitoring server vitality, using the Heart class instantiated during registration.

HTTP Health Probe

The HTTP endpoint at GET /healthz returns a JSON payload indicating whether the server is alive. Lines 6‑11 implement this check:

router.get("/", (req, res) => {
  res.json({
    status: req.heart.alive() ? "alive" : "expired",
    lastHeartbeat: req.heart.lastHeartbeat,
  })
})

WebSocket Health Updates

For real-time monitoring, the WebSocket endpoint at ws://…/healthz pushes health status updates whenever a client sends a message. Lines 15‑28 handle the upgrade and message events:

wsRouter.ws("/", async (req) => {
  wss.handleUpgrade(req, req.ws, req.head, (ws) => {
    ws.addEventListener("message", () => {
      ws.send(JSON.stringify({
        event: "health",
        status: req.heart.alive() ? "alive" : "expired",
        lastHeartbeat: req.heart.lastHeartbeat,
      }))
    })
    req.ws.resume()
  })
})

VS Code Proxy Endpoint

The VS Code router in src/node/routes/vscode.ts dynamically loads the VS Code web server and proxies all remaining traffic after authentication checks.

Dynamic Server Loading

The router uses ensureVSCodeLoaded middleware to lazy-load the VS Code server via dynamic import(). This occurs only when the first authenticated request arrives, reducing startup overhead. The middleware verifies the server is ready before calling vscodeServer!.handleRequest().

HTTP and WebSocket Proxying

Lines 41-L44 capture all remaining HTTP traffic using a catch-all regex:

router.all(/.*/, ensureAuthenticated, ensureVSCodeLoaded, async (req, res) => {
  vscodeServer!.handleRequest(req, res)
})

For WebSocket connections, lines 46-L52 wrap the socket and forward it to VS Code:

wsRouter.ws(/.*/, ensureOrigin, ensureAuthenticated, ensureVSCodeLoaded, async (req) => {
  const wrappedSocket = await socketProxyProvider.createProxy(req.ws)
  vscodeServer!.handleUpgrade(req, wrappedSocket as net.Socket)
  req.ws.resume()
})

Both paths enforce authentication via ensureAuthenticated, which validates the session cookie or redirects to /login.

Auxiliary Routes

The same file provides supporting endpoints:

  • GET /manifest.json (lines 74-112): Generates a Progressive Web App manifest
  • POST /mint-key (lines 13-39): Supplies a per-instance secret for web serving

Error Handling Strategy

The errors module in src/node/routes/errors.ts provides final catch-all handlers for both HTTP and WebSocket routes, ensuring consistent error formatting.

HTTP Error Handler

The errorHandler function inspects thrown errors to determine the appropriate status code. It maps filesystem errors (ENOENT, EISDIR) to 404, uses the error's statusCode property if present, and defaults to 500 for unknown errors. Lines 48‑61 render an HTML error page from src/browser/pages/error.html when the client accepts text/html:

// HTML rendering for browser requests
res.status(statusCode).send(html)

Lines 63-66 return JSON for API clients:

// JSON response for XHR/fetch requests
res.status(statusCode).json({ error: message })

WebSocket Error Handler

The wsErrorHandler (lines 70-89) sends a plain HTTP response over the upgraded socket for protocol-level errors and logs severe exceptions to the console, preventing WebSocket crashes from destabilizing the server.

Practical Usage Examples

Authenticating via Login Endpoint


# Store cookies for the session

curl -c cookie.jar -b cookie.jar \
  -X POST \
  -d "password=mySecret" \
  "https://my-code-server/login?to=%2F"

Checking Server Health


# HTTP health probe

curl https://my-code-server/healthz

# Output: {"status":"alive","lastHeartbeat":1698627200}
// WebSocket health monitoring
const ws = new WebSocket("wss://my-code-server/healthz");
ws.onopen = () => ws.send("ping");
ws.onmessage = e => console.log(JSON.parse(e.data));
// Output: {event:"health",status:"alive",lastHeartbeat:...}

Accessing VS Code After Authentication


# Use stored cookie to access IDE

curl -b cookie.jar https://my-code-server/

# Request proxied to VS Code server

Triggering Error Responses


# JSON error for API clients

curl -H "Accept: application/json" https://my-code-server/nonexistent

# Output: {"error":"Not Found"}

# HTML error page for browsers

curl -H "Accept: text/html" https://my-code-server/nonexistent

# Returns rendered error.html

Summary

  • Central Registration: The register function in src/node/routes/index.ts orchestrates all routing, mounting health checks first, login/logout when password auth is enabled, VS Code proxies on multiple paths, and error handlers last.
  • Authentication Flow: Login routes enforce rate limiting (2/min, 12/hour) and set signed session cookies, while VS Code routes use ensureAuthenticated middleware to protect IDE access.
  • Dual Protocol Health Checks: The /healthz endpoint provides stateless HTTP probes and stateful WebSocket push updates using the shared Heart instance.
  • Dynamic VS Code Loading: The VS Code router lazy-loads the web server on first request and proxies all HTTP and WebSocket traffic through authenticated middleware chains.
  • Consistent Error Rendering: Error handlers in src/node/routes/errors.ts map filesystem and HTTP errors to appropriate status codes, returning HTML for browsers and JSON for API clients.

Frequently Asked Questions

How does code-server protect the login endpoint from brute-force attacks?

The login router implements a RateLimiter class (lines 12‑25 in src/node/routes/login.ts) that restricts attempts to 2 per minute and 12 per hour. Each failed validation consumes a token via limiter.removeToken(), and depleted tokens trigger a "Login Rate Limit" error before credential validation occurs.

What distinguishes the HTTP health check from the WebSocket health check?

The HTTP endpoint at /healthz returns a static JSON snapshot of the server's alive status and last heartbeat timestamp (lines 6‑11). The WebSocket endpoint (lines 15‑28) maintains a persistent connection and pushes updated health status JSON only when the client sends a message, enabling real-time monitoring without polling overhead.

How does the route system handle requests to undefined paths?

After mounting all specific routes, src/node/routes/index.ts lines 75-L80 attach a catch-all middleware that throws an HttpError with status 404. This error propagates to the errorHandler in src/node/routes/errors.ts, which renders a friendly HTML page or returns JSON depending on the Accept header.

Why are VS Code routes mounted on both "/" and "/vscode" prefixes?

The registration loop in src/node/routes/index.ts (lines 70-L73) mounts the VS Code router on both paths to ensure backward compatibility and flexible deployment configurations. This allows users to access the IDE at the root domain or via the explicit /vscode subdirectory without changing the underlying route logic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →