Skip to content

Health Endpoint

By default Chronicle serves its health check on the main port (35000) — the same port that carries gRPC, the Workbench, the REST API and OAuth. That single port always uses TLS, because Kestrel can only multiplex HTTP/2 (gRPC) and HTTP/1.1 on one port when ALPN negotiates the protocol per connection, and ALPN requires TLS. In development that TLS certificate is self-signed and generated automatically.

That is exactly where orchestrator and load-balancer probes run into trouble. A health probe that has to speak TLS — and sometimes validate the certificate — against a port that may be serving a self-signed certificate is awkward at best and broken at worst. The dedicated health port removes that friction: it publishes the health endpoint on its own HTTP/1.1 port where you can turn TLS off.

Set a health section with a port to expose the health endpoint on a dedicated port. When port is not set, nothing changes — the health endpoint stays on the main port.

{
"port": 35000,
"healthCheckEndpoint": "/health",
"health": {
"port": 8080,
"tls": false
}
}
Terminal window
Cratis__Chronicle__Health__Port=8080
Cratis__Chronicle__Health__Tls=false
PropertyTypeDefaultDescription
health.portnumbernullDedicated port for the health endpoint. When not set, the health endpoint is served on the main port.
health.tlsbooleantrueWhether the dedicated health port uses TLS. Only applies when health.port is set.
health.exclusivebooleanfalseWhether the dedicated health port serves only the health endpoint, answering 404 for everything else. Only applies when health.port is set.

The endpoint path is the shared healthCheckEndpoint (default /health), so with the configuration above the health endpoint is reachable at http://<host>:8080/health.

  • health.port not set — the health endpoint is served on the main port (35000) over TLS, alongside all other traffic. This is the default.
  • health.port set — the health endpoint is additionally served on that dedicated HTTP/1.1 port. gRPC is never exposed on it, because gRPC needs HTTP/2 and the dedicated port serves HTTP/1.1 only.
  • health.tls false — the dedicated port serves the health endpoint in cleartext. The main port still requires TLS regardless of this setting; disabling TLS here only affects the dedicated health port.
  • health.exclusive true — the dedicated port answers only the health endpoint; every other path returns 404 on that port. Off by default, so enabling it never changes an existing deployment silently.
  • health.port equal to the main port — treated as not set, since a second listener cannot bind the port the main listener already owns.

Restricting the dedicated port to health only

Section titled “Restricting the dedicated port to health only”

The dedicated port is an additional listener on the same application rather than a separate, health-only server. By default the other HTTP/1.1 endpoints (Workbench, REST API, OAuth) are therefore reachable on it too, so the default posture is to treat it as an internal, cluster-local probe port and not expose it publicly, especially with TLS disabled.

Set health.exclusive to true to close that off. The port then serves the health endpoint and nothing else — every other path gets a 404:

{
"health": {
"port": 8080,
"tls": false,
"exclusive": true
}
}

The restriction keys on the local port the connection was accepted on, which a client cannot influence. It is not derived from the Host header or any X-Forwarded-* header, so a request arriving on the main port cannot dress itself up as a health-port request or vice versa. The main port is never restricted, and the option does nothing unless a dedicated health.port is configured and differs from the main port.

It defaults to false so that enabling a dedicated health port never silently removes endpoints from an existing deployment; turn it on deliberately when the probe port is reachable from somewhere the Workbench and API should not be.

Why a plaintext health port helps in Kubernetes

Section titled “Why a plaintext health port helps in Kubernetes”

Kubernetes probes and self-signed certificates do not always get along, and the details are easy to get wrong.

The kubelet’s own HTTP probes, when configured with scheme: HTTPS, skip TLS certificate verification — so a self-signed certificate technically works for kubelet liveness, readiness and startup probes. If the kubelet were the only thing probing Chronicle, the main TLS port would be fine.

In practice it usually is not the only thing. Many deployments front the pod with a cloud load balancer, ingress controller, service mesh or managed health check, and those probers commonly do validate the certificate — and reject a self-signed one. Probes also cannot present a client certificate, so mutual TLS is not an option for them. The result is that “the pod is healthy but the probe fails” class of problem, caused entirely by certificate validation rather than by Chronicle’s actual health.

Publishing the health endpoint on a dedicated plaintext port sidesteps certificate validation for every kind of prober:

{
"health": {
"port": 8080,
"tls": false
}
}
livenessProbe:
httpGet:
path: /health
port: 8080
scheme: HTTP
readinessProbe:
httpGet:
path: /health
port: 8080
scheme: HTTP

gRPC clients keep using the TLS main port — gRPC is never served on the plaintext port — while the probes target the dedicated port.