You are currently viewing the beta documentation for Headplane. Go to the stable docs →
Skip to content

TLS & Certificates

Everything Headplane does with TLS lives on this page: terminating HTTPS in-process, trusting private certificate authorities for outbound connections, and the interactions with cookies and the bundled Docker healthcheck.

Custom Certificate Authorities

If you front any of the services Headplane talks to — your Headscale server, your OIDC provider, an HTTPS Docker daemon, etc. — with a private or self-signed certificate authority, Headplane needs to trust that CA. The cleanest way to do that is the standard Node.js NODE_EXTRA_CA_CERTS environment variable, which is honoured for every outbound TLS connection in the process (OIDC discovery, Headscale API, Docker, anything else that uses fetch or https).

Provide it as a path to a PEM-encoded bundle of one or more CA certificates:

bash
NODE_EXTRA_CA_CERTS=/etc/headplane/extra-cas.pem

In Docker, bind-mount the bundle and pass the variable through:

yaml
services:
  headplane:
    image: ghcr.io/tale/headplane:latest
    environment:
      NODE_EXTRA_CA_CERTS: /etc/headplane/extra-cas.pem
    volumes:
      - "./internal-ca.pem:/etc/headplane/extra-cas.pem:ro"
      - "./config.yaml:/etc/headplane/config.yaml"
      - "./headplane-data:/var/lib/headplane"

The bundle is added on top of the system trust store, so public certificates (Let's Encrypt, ZeroSSL, etc.) keep working.

headscale.tls_cert_path is a narrower knob that pins Headscale's API connection to exactly one certificate, bypassing the rest of the trust store. It still has its place if you want Headplane to refuse anything other than that specific cert for Headscale, but NODE_EXTRA_CA_CERTS is the right tool whenever you simply want to add a CA to the trusted set.

TLS Termination

Headplane can terminate TLS itself when both server.tls_cert_path and server.tls_key_path are set in the configuration file. Both must point to PEM-encoded files that Headplane can read.

yaml
server:
  port: 443
  tls_cert_path: "/var/lib/headplane/tls/fullchain.pem"
  tls_key_path: "/var/lib/headplane/tls/privkey.pem"

When TLS is configured Headplane serves HTTPS/1.1 on server.port. HTTP/2 and HTTP/3 are intentionally not supported in-process — terminate those at a reverse proxy (e.g. Caddy or Traefik) and forward to Headplane over HTTP/1.1 if you need them today.

server.cookie_secure is forced to true whenever TLS is enabled (browsers refuse Secure-less cookies over HTTPS); a warning is logged if your config had it set to false.

For most deployments we still recommend terminating TLS at a reverse proxy (see Reverse Proxying) so you can share certificates with Headscale and other services. Built-in TLS is meant for the simpler "Headplane on a single box" scenarios.

Healthcheck

The bundled Docker healthcheck picks up the right scheme and port automatically — Headplane writes its loopback URL to /tmp/headplane-listen when it starts, and the healthcheck reads it from there. So flipping TLS on or off requires no healthcheck-specific configuration; everything just works.