evanw/esbuild · error

Must specify both key and certificate for HTTPS

Error message

Must specify both key and certificate for HTTPS

What it means

Returned by internalContext.Serve when exactly one of Keyfile / Certfile is set but not the other. HTTPS requires both a private key and a certificate; setting only one is treated as a misconfiguration and rejected before any listener is created. The XOR check is (Keyfile != "") != (Certfile != "").

Source

Thrown at pkg/api/serve_other.go:746

}

func (ctx *internalContext) Serve(serveOptions ServeOptions) (ServeResult, error) {
	ctx.mutex.Lock()
	defer ctx.mutex.Unlock()

	// Ignore disposed contexts
	if ctx.didDispose {
		return ServeResult{}, errors.New("Cannot serve a disposed context")
	}

	// Don't allow starting serve mode multiple times
	if ctx.handler != nil {
		return ServeResult{}, errors.New("Serve mode has already been enabled")
	}

	// Don't allow starting serve mode multiple times
	if (serveOptions.Keyfile != "") != (serveOptions.Certfile != "") {
		return ServeResult{}, errors.New("Must specify both key and certificate for HTTPS")
	}

	// Validate the "servedir" path
	if serveOptions.Servedir != "" {
		if absPath, ok := ctx.realFS.Abs(serveOptions.Servedir); ok {
			serveOptions.Servedir = absPath
		} else {
			return ServeResult{}, fmt.Errorf("Invalid serve path: %s", serveOptions.Servedir)
		}
	}

	// Validate the "fallback" path
	if serveOptions.Fallback != "" {
		if absPath, ok := ctx.realFS.Abs(serveOptions.Fallback); ok {
			serveOptions.Fallback = absPath
		} else {
			return ServeResult{}, fmt.Errorf("Invalid fallback path: %s", serveOptions.Fallback)
		}

View on GitHub (pinned to 6ff1d8b0d8)

Solutions

  1. Provide both Keyfile and Certfile, or neither (for plain HTTP).
  2. Validate that both TLS file env vars are present before calling Serve().
  3. Check for typos: the option names are Keyfile and Certfile (note: key then cert).
  4. Log the resolved key/cert paths right before Serve() to catch empty values.

Example fix

// before
ctx.Serve({ port: 443, Keyfile: './key.pem' }); // Certfile missing

// after
ctx.Serve({ port: 443, Keyfile: './key.pem', Certfile: './cert.pem' });
Defensive patterns

Strategy: validation

Validate before calling

function validTLS(opts) { return (opts.Keyfile ? 1 : 0) + (opts.Certfile ? 1 : 0) !== 1; }
if (!validTLS(serveOpts)) throw new Error('Provide both key and cert, or neither');

Type guard

function hasBothTLSFiles(opts) { return !!opts.Keyfile && !!opts.Certfile; }

Try / catch

try { await ctx.Serve(opts); } catch (e) { if (/Must specify both key and certificate/.test(e.message)) { /* supply the missing file */ } throw e; }

Prevention

When it happens

Trigger: Calling ctx.Serve() with ServeOptions where Keyfile is set but Certfile is empty, or vice versa. The XOR guard at serve_other.go:745 returns the error.

Common situations: A config loader that reads TLS_KEY from env but TLS_CERT is missing; typo in option names; copying a config where one path was left as the placeholder; a conditional that sets the keyfile only under some flag.

Related errors


AI-assisted analysis of evanw/esbuild@6ff1d8b0d8 (2026-08-03). Data as JSON: /data/errors/46cc4ba95a4f5de3.json. Report an issue: GitHub.