obsidian/wiki/concepts/nodejs-ssl-system-trust-store.md
2026-04-19 22:03:39 +01:00

4.2 KiB

title aliases tags sources created updated
Node.js SSL — System Trust Store Is Ignored
nodejs-ssl
nodejs-trust-store
node-tls-reject
nodejs-self-signed
nodejs
ssl
tls
systemd
lxc
docker
homelab
daily/2026-04-19.md
2026-04-19 2026-04-19

Node.js SSL — System Trust Store Is Ignored

Node.js does not use the operating system's certificate trust store. Adding a self-signed certificate to /etc/ssl/certs/ or running update-ca-certificates has no effect on Node.js TLS verification. The only reliable fix is to set NODE_TLS_REJECT_UNAUTHORIZED=0 as an environment variable on the Node.js process itself — and for systemd-managed services, this must be in the unit file, not in a shell profile.

Key Points

  • Node.js bundles its own CA store (Mozilla's root CAs compiled in) — it does not read /etc/ssl/certs/ at runtime
  • update-ca-certificates and adding certs to the Linux trust store does not affect Node.js
  • NODE_TLS_REJECT_UNAUTHORIZED=0 must be set in the process environment, not a login shell profile — for systemd services this means the unit file
  • Setting the env var via shell (export NODE_TLS_REJECT_UNAUTHORIZED=0) only affects that shell and its children — not a systemd service started separately
  • For self-signed or internal CA certificates, the proper long-term fix is NODE_EXTRA_CA_CERTS=/path/to/ca.crt — this tells Node.js to trust additional CAs without disabling verification entirely

Details

Why System Trust Store Doesn't Work

Unlike Python (certifi or system certs), Go (uses system certs by default), or curl (uses system certs), Node.js compiles Mozilla's CA bundle at build time and uses it exclusively. This is a deliberate design decision for portability. Even in a Docker container or LXC where the system certs are managed by the distro, Node.js won't see them.

The consequence: any application talking to an internal service with a self-signed or internal-CA-signed certificate will get CERT_HAS_EXPIRED, UNABLE_TO_VERIFY_LEAF_SIGNATURE, or SELF_SIGNED_CERT_IN_CHAIN errors even after you've properly configured the OS trust store.

The systemd Fix

For services managed by systemd, the env var must be in the unit file:

# /etc/systemd/system/homepage.service
[Service]
Environment=NODE_TLS_REJECT_UNAUTHORIZED=0
ExecStart=/usr/bin/node /opt/homepage/server.js

After editing:

systemctl daemon-reload
systemctl restart homepage

A drop-in override is cleaner than editing the unit directly:

mkdir -p /etc/systemd/system/homepage.service.d/
cat > /etc/systemd/system/homepage.service.d/tls.conf << 'EOF'
[Service]
Environment=NODE_TLS_REJECT_UNAUTHORIZED=0
EOF
systemctl daemon-reload && systemctl restart homepage

The Proper Fix (NODE_EXTRA_CA_CERTS)

NODE_TLS_REJECT_UNAUTHORIZED=0 disables all certificate validation — dangerous on public-facing services. The correct approach for internal CAs:

# Export Proxmox's self-signed certificate
openssl s_client -connect 192.168.1.100:8006 -showcerts </dev/null 2>/dev/null \
  | openssl x509 -outform PEM > /etc/ssl/private/proxmox-ca.crt

# Tell Node.js to trust it
Environment=NODE_EXTRA_CA_CERTS=/etc/ssl/private/proxmox-ca.crt

This adds the cert to Node.js's trusted set without disabling verification for other connections.

Context: Homepage in LXC (2026-04-19)

Homepage (a self-hosted dashboard) running in LXC 103 needed to call the Proxmox API at https://192.168.1.100:8006. Proxmox uses a self-signed certificate. The Proxmox cert was added to the system trust store inside LXC 103 — no effect. NODE_TLS_REJECT_UNAUTHORIZED=0 was set in the shell — no effect (systemd started the service separately). Only adding it to the systemd unit file resolved the issue.

Sources

  • daily/2026-04-19.md — Homepage LXC 103 calling Proxmox API; cert added to system trust store had no effect; NODE_TLS_REJECT_UNAUTHORIZED=0 in systemd unit file was the only fix that worked