Application & framework security

Why do TLS certificates fail in slim Node.js containers on OrbStack?

Node.js trusts a frozen Mozilla CA snapshot, not OrbStack's local CA. Slim images also strip the OpenSSL store. Point Node at /usr/local/share/ca-certificates/orbstack-root.crt. GHSA-vg6x-rcgg-rjx6 is historical; Vite still requires server.allowedHosts for OrbStack domains.

6 min read
In short: Node.js trusts a frozen Mozilla CA snapshot, not OrbStack's local CA. Slim images also strip the OpenSSL store. Point Node at /usr/local/share/ca-certificates/orbstack-root.crt. GHSA-vg6x-rcgg-rjx6 is historical; Vite still requires server.allowedHosts for OrbStack domains.

Why do TLS certificates fail in slim Node.js containers?

Node.js trusts a Mozilla CA snapshot that is frozen at the Node release. OrbStack's local CA is not in that snapshot. Slim images also strip the distro OpenSSL store, so switching Node to OpenSSL does not help until you put the CA file back.

That is why HTTPS between containers works in Go after OrbStack 1.9+ (Go uses the system store, and OrbStack injects orbstack-root.crt) and fails in Node with self-signed certificate in certificate chain or ERR_SSL_WRONG_VERSION_NUMBER. The certificate is valid. Node is looking in the wrong store, or the store is missing.

OrbStack 1.9 (2024) started trusting its CA between containers. Current OrbStack is 2.x (2.2.x as of August 2026) and still mounts the same root at /usr/local/share/ca-certificates/orbstack-root.crt. The Node workaround has not gone away: you must opt into OpenSSL and point SSL_CERT_FILE at that path. On node:22-slim (and other *-slim tags) you must also reinstall ca-certificates and OpenSSL, or use the full node:22 image.

Use this for local HTTPS that matches production headers, cookies, and HSTS. Pair it with functional tests of security rules so the same TLS hostname you browse is the one Newman or k6 hits.

How do I point Node.js at the OpenSSL CA store?

Pass --use-openssl-ca to every node process in the container. The flag tells Node to ignore the bundled Mozilla snapshot and use OpenSSL's store, which you can change at runtime.

Set it once with NODE_OPTIONS so npm, Vitest, and your app inherit it:

docker-compose.yml
services:
web:
build:
context: .
dockerfile: apps/web/Dockerfile
environment:
NODE_OPTIONS: --use-openssl-ca
SSL_CERT_FILE: /usr/local/share/ca-certificates/orbstack-root.crt

Do not quote the flag inside the Compose value (NODE_OPTIONS: "--use-openssl-ca" is fine; NODE_OPTIONS: '"--use-openssl-ca"' is not). Confirm with docker compose exec web node -p "process.execArgv" or by logging process.env.NODE_OPTIONS.

Where does OrbStack put the root certificate?

OrbStack mounts /usr/local/share/ca-certificates/orbstack-root.crt into containers unless you set the label dev.orbstack.add-ca-certificates=false. OpenSSL does not automatically load a file that was dropped into that directory. update-ca-certificates would register it on a full Debian image. Slim images often lack that tool.

SSL_CERT_FILE is the reliable override. It points OpenSSL at one PEM file. That is enough for Node with --use-openssl-ca. Other runtimes:

  • Go: uses the system store after OrbStack 1.9. No extra env vars.
  • Python: SSL_CERT_FILE or REQUESTS_CA_BUNDLE.
  • curl: SSL_CERT_FILE or -E / --cacert.

If you need the cert in the default bundle (tools that ignore SSL_CERT_FILE), use a full Debian image, copy the file, and run update-ca-certificates in the Dockerfile.

How do I trust OrbStack HTTPS between containers?

  1. Enable OrbStack domains and HTTPS (on by default). Each container is reachable at https://<name>.orb.local.
  2. Use a full Node image, or reinstall OpenSSL and ca-certificates in slim images.
  3. Set NODE_OPTIONS=--use-openssl-ca and SSL_CERT_FILE=/usr/local/share/ca-certificates/orbstack-root.crt.
  4. Call sibling services by their https://…orb.local URL, the same hostname the browser uses.

Do not generate mkcert files, commit a development PEM, or disable rejectUnauthorized. Those were the old workarounds. After this setup, fetch('https://api.orb.local/health') from the web container succeeds.

If you still see ERR_SSL_WRONG_VERSION_NUMBER, the process is not speaking TLS to the port you think (OrbStack's HTTPS proxy is 443; your app may still listen on 3000 over HTTP behind the proxy), or the slim image has no OpenSSL. Hit https://<name>.orb.local from the host browser first. If the browser trusts it and Node does not, the CA path is wrong. If neither trusts it, HTTPS is not enabled for that container.

Why does Vite reject OrbStack custom domains?

In January 2025 Vite fixed GHSA-vg6x-rcgg-rjx6 (CVE-2025-24010). Unpatched Vite 4.x, 5.x, and 6.0.0 to 6.0.8 accepted any Host and defaulted CORS to *, so a malicious page could talk to the dev server. Current Vite (6.0.9+, 5.4.12+, 4.5.6+, and 7.x) allows localhost, *.localhost, and raw IPs only. HTTPS skips the host check. Anything else, including *.orb.local, must be listed.

The CVE is historical if you are on a patched Vite. The server.allowedHosts setting is not. OrbStack custom domains still need it.

vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
server: {
allowedHosts: ["web.orb.local", "app.orb.local"],
},
});

OrbStack does not inject the domain as an environment variable inside the container. Derive it from the Compose service name (<service>.orb.local) or a label you set. Do not set allowedHosts: true. That restores the DNS-rebinding hole the CVE closed.

If you only need Vite for local UI work, prefer localhost and skip OrbStack domains for that service. Use OrbStack HTTPS for the API and any service whose production counterpart is TLS-only. That split keeps the Vite allowlist short.

Which Node image should I use for local HTTPS?

Match the production shape where you can, then add the CA pieces development needs. Secure container deployments explains why Distroless and Wolfi are better production bases. Distroless has no OpenSSL extras and no shell, so it is a poor local-dev image if you rely on --use-openssl-ca.

ImageOrbStack HTTPS in NodeUse

node:22 (full current Debian)

Works after NODE_OPTIONS + SSL_CERT_FILE

Local services that call other *.orb.local hosts

node:22-slim

Fails until you reinstall ca-certificates and OpenSSL

Only if you add those packages in the Dockerfile
Distroless / Wolfi production tagsNo shell, minimal OpenSSL layoutProduction. Not the OrbStack Node workaround

Frequently asked questions

Why do TLS certificates fail in slim Node.js containers?

Node uses a Mozilla CA snapshot frozen at release, and slim images remove the distro OpenSSL store. OrbStack's CA is not in either place. You get self-signed certificate in certificate chain or ERR_SSL_WRONG_VERSION_NUMBER even though the cert is valid.

How do I trust OrbStack certificates in Node?

Set NODE_OPTIONS=--use-openssl-ca and SSL_CERT_FILE=/usr/local/share/ca-certificates/orbstack-root.crt. Use a full node:22 image, or reinstall ca-certificates and OpenSSL in slim images.

Is OrbStack 1.9 still required?

1.9 (2024) is when OrbStack started injecting the CA between containers. Current OrbStack is 2.x and still mounts orbstack-root.crt at the same path. The Node env vars are still required.

Why does Vite block my .orb.local domain?

After GHSA-vg6x-rcgg-rjx6 (January 2025, now patched), Vite allows localhost and IPs only. Add the OrbStack hostname to server.allowedHosts. Do not set allowedHosts: true.

Should I use Distroless for local OrbStack HTTPS?

No. Distroless and Wolfi production tags lack the OpenSSL layout this workaround needs. Use a full node:22 image locally and Distroless in production.

Application security in your code

Protect your application with Arcjet

Get rate limits, bot detection, and attack blocking in your request handlers.