How TLS Certificates Are Generated and Managed in code-server: Self-Signed vs Custom

code-server supports both zero-configuration self-signed TLS certificates and production custom certificates, automatically generating self-signed credentials via the pem library when invoked with --cert alone, while requiring explicit --cert and --cert-key paths for custom deployments.

The coder/code-server project enables browser-based VS Code access, requiring robust TLS certificate generation and management to secure remote development sessions. The codebase implements a dual-mode architecture that distinguishes between automatic self-signed provisioning for local development and manual certificate loading for production environments. Understanding these implementation details ensures proper HTTPS configuration across different deployment scenarios.

TLS Certificate Modes in code-server

Self-Signed Certificate Generation (Zero-Config Mode)

When you launch code-server with the --cert flag without specifying a file path, the application enters automatic TLS provisioning mode. In this configuration, defined in src/node/cli.ts (lines 56-66), the system checks for existing certificate files and generates new ones only if absent.

Custom Certificate Deployment (Production Mode)

For production environments requiring certificates signed by a trusted Certificate Authority (CA), code-server accepts explicit file paths via --cert /path/to/cert.crt and --cert-key /path/to/key.pem. This mode, validated in src/node/cli.ts (lines 95-101), bypasses all generation logic and loads your provided cryptographic materials directly into the HTTPS server. If --cert receives a path but --cert-key is omitted, the CLI throws an explicit error: --cert-key is missing.

Certificate Generation Implementation Details

The core generation logic resides in src/node/util.ts within the generateCertificate() function (lines 89-106). This utility leverages the pem npm package to create X.509 certificates with proper Subject Alternative Names (SAN) and server authentication extensions.

The function first attempts to access existing files at paths.data (typically ~/.local/share/code-server on Linux/macOS) using fs.access(). If the files <hostname>.crt and <hostname>.key do not exist, it invokes pem.createCertificate() with a custom OpenSSL configuration that includes:

  • basicConstraints = CA:true
  • extendedKeyUsage = serverAuth
  • subjectAltName matching the --cert-host parameter (defaulting to localhost)

Once generated, the certificate and private key are persisted to the data directory, ensuring subsequent server restarts reuse the same credentials and avoid repetitive browser trust warnings.

HTTPS Server Initialization and Certificate Loading

Server creation occurs in src/node/app.ts (lines 73-81), where the application conditionally uses httpolyglot to handle both HTTP and HTTPS traffic on the same port. When args.cert is present, the server options include:

  • cert: Buffer from fs.readFile of the certificate path
  • key: Buffer from fs.readFile of the key path

For self-signed mode, these paths point to the files generated in paths.data; for custom mode, they reference the user-supplied locations from the CLI arguments.

Enforcing TLS Connections

To ensure encrypted traffic, src/node/routes/index.ts (lines 88-95) implements middleware that checks the connection encryption state. When args.cert is enabled and (req.connection as tls.TLSSocket).encrypted is falsy, the server responds with a 301 redirect to the equivalent https:// URL, preventing accidental unencrypted access.

Configuration Examples

Self-signed certificate with default localhost

code-server --cert

This generates localhost.crt and localhost.key in the data directory.

Self-signed certificate for custom hostname

code-server --cert --cert-host my.dev.local

The SAN field in the generated certificate will contain my.dev.local.

Custom CA-signed certificate

code-server \
  --cert /etc/ssl/certs/mydomain.crt \
  --cert-key /etc/ssl/private/mydomain.key \
  --bind-addr 0.0.0.0:8080

Reference implementation for certificate generation

import { promises as fs } from "fs"
import path from "path"
import { paths } from "./util"

async function generateSelfSigned(hostname: string) {
  const certPath = path.join(paths.data, `${hostname.replace(/\./g, "_")}.crt");
  const keyPath  = path.join(paths.data, `${hostname.replace(/\./g, "_")}.key`);

  try {
    await Promise.all([fs.access(certPath), fs.access(keyPath)]);
  } catch {
    const pem = require("pem");
    const { certificate, serviceKey } = await new Promise((res, rej) =>
      pem.createCertificate(
        {
          selfSigned: true,
          commonName: hostname,
          config: `
            [req]
            req_extensions = v3_req

            [ v3_req ]
            basicConstraints = CA:true
            extendedKeyUsage = serverAuth
            subjectAltName = @alt_names

            [alt_names]
            DNS.1 = ${hostname}
          `,
        },
        (err: any, result: any) => (err ? rej(err) : res(result))
      )
    );
    await fs.mkdir(paths.data, { recursive: true });
    await Promise.all([
      fs.writeFile(certPath, certificate),
      fs.writeFile(keyPath, serviceKey),
    ]);
  }
  return { cert: certPath, certKey: keyPath };
}

Summary

  • Self-signed mode: Triggered by --cert without a value; generates certificates via generateCertificate() in src/node/util.ts and stores them in paths.data.
  • Custom mode: Requires both --cert <path> and --cert-key <path>; loads existing files without generation.
  • Persistence: Generated certificates are reused across restarts to avoid trust prompt churn.
  • Server: Uses httpolyglot in src/node/app.ts to serve HTTP/HTTPS on the same port.
  • Security: Automatic HTTP-to-HTTPS redirects are enforced in src/node/routes/index.ts when TLS is active.

Frequently Asked Questions

Where does code-server store self-signed TLS certificates?

Self-signed certificates are stored in the platform-specific data directory (accessed via paths.data in src/node/util.ts), typically located at ~/.local/share/code-server on Linux and macOS systems. The files are named <hostname>.crt and <hostname>.key, with dots in hostnames converted to underscores.

Can I use a self-signed certificate for a domain other than localhost?

Yes. Specify your desired hostname using the --cert-host flag (e.g., code-server --cert --cert-host myserver.local). The generateCertificate() function incorporates this value into the certificate's Subject Alternative Name (SAN) field, allowing the certificate to validate against that specific domain.

Why does code-server require both --cert and --cert-key for custom certificates?

The CLI validation logic in src/node/cli.ts enforces this requirement to prevent misconfigurations. When providing a custom certificate path, the corresponding private key is mandatory for TLS handshakes. If --cert receives a file path but --cert-key is omitted, the application throws an explicit error: --cert-key is missing.

How does code-server handle HTTP traffic when HTTPS is enabled?

When TLS is active, the middleware in src/node/routes/index.ts inspects the req.connection.encrypted property on every request. If the connection is not encrypted, the server responds with a 301 redirect to the HTTPS equivalent URL, ensuring all traffic uses the encrypted channel provided by httpolyglot.

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 →