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

> Learn how code-server manages TLS certificates. Discover the difference between self-signed and custom certificates for secure remote development environments.

- Repository: [Coder/code-server](https://github.com/coder/code-server)
- Tags: how-to-guide
- Published: 2026-03-01

---

**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](https://github.com/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`](https://github.com/coder/code-server/blob/main/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`](https://github.com/coder/code-server/blob/main/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`](https://github.com/coder/code-server/blob/main/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`](https://github.com/coder/code-server/blob/main/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`](https://github.com/coder/code-server/blob/main/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

```bash
code-server --cert

```

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

### Self-signed certificate for custom hostname

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

```

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

### Custom CA-signed certificate

```bash
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

```typescript
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`](https://github.com/coder/code-server/blob/main/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`](https://github.com/coder/code-server/blob/main/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`](https://github.com/coder/code-server/blob/main/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`](https://github.com/coder/code-server/blob/main/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`](https://github.com/coder/code-server/blob/main/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`](https://github.com/coder/code-server/blob/main/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`.