Server Only TLS

Prerequisites

Have certificates on hands:

Have a mir deployment ready to be used:

Steps

If you have your own, skip to Step 2.

Step 1: Generate certificates

This will:

  • generate a CA private key and certificate
    • the certificate must be installed on Mir clients (CLI, Devices, Modules)
  • generate a Server private key and certificate.
    • must be passed on Nats Message Bus
  • sign the Server certificate with the CA
# Generating CA private key
openssl genrsa -out ca.key 4096

# Generating CA certificate
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
    -subj "/C=US/ST=CA/L=San Francisco/O=NATS Demo/OU=Certificate Authority/CN=NATS Root CA" \
    2>/dev/null

# Generating Server private key
openssl genrsa -out tls.key 4096

# Generating Server certificate
openssl req -new -key tls.key -out server.csr \
    -subj "/C=US/ST=CA/L=San Francisco/O=NATS Demo/OU=NATS Server/CN=localhost" \
    2>/dev/null

# Create extensions file for SAN (Subject Alternative Names)
# Make sure to put the address of your server
cat > server-ext.cnf <<EOF
subjectAltName = DNS:localhost,DNS:*.localhost,DNS:local-nats,IP:127.0.0.1,IP:::1
EOF

# Sign the server certificate
openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key \
    -CAcreateserial -out tls.crt -extfile server-ext.cnf 2>/dev/null

Step 2: Configure Nats Server

Docker

Copy tls.crt and tls.key under ./mir-compose/natsio/certs.

In the ./mir-compose/natsio/config.conf, update with the following:

# TLS/Security
tls: {
  cert_file: "/etc/nats/certs/tls.crt"
  key_file: "/etc/nats/certs/tls.key"
  timeout: 2
}

Update Compose to pass the certificates:

services:
  nats:
    ...
    volumes:
      ...
      - ./certs:/etc/nats/certs
    ...

Start server docker compose up.

Kubernetes

Create a Kubernetes Secret with the TLS:

# Directly to the cluster
kubectl create secret tls mir-tls-secret --cert=tls.crt --key=tls.key
# As file
kubectl create secret tls mir-tls-secret --cert=tls.crt --key=tls.key -o yaml --dry-run=client > mir-tls.secret.yaml

Update values file:

## Nats
nats:
  config:
    nats:
      tls:
        enabled: true
        secretName: mir-tls-secret # Secret name
        dir: /etc/nats-certs/nats
        cert: tls.crt
        key: tls.key

Step 3: Install Root Certificate on Module

If the CA Certificate is public and installed in the Trusted Store of your container, you can skip this step.

Docker

Let's launch the server with the RootCA file. Edit ./mir-compose/mir/local-config.yaml and set the path of the credentials files under mir.rootCA. Edit ./mir-compose/mir/compose.yaml to mount the file.

# Restart server
docker compose down
docker compose up

Kubernetes

Create a Kubernetes Secret with the RootCA:

# Directly to the cluster
kubectl create secret generic mir-rootca-secret --from-file=ca.crt
# As file
kubectl create secret generic mir-rootca-secret --from-file=ca.crt -o yaml --dry-run=client > mir-rootca.secret.yaml

Update values file:

## MIR
caSecretRef: mir-rootca-secret # Secret name

Step 4: Install the Root Certificate on the Clients

If the CA Certificate is public and installed in the Trusted Store of your machine, you can skip this step.

Via the Trusted Store

If installed in the Trusted Store, Applications automaticly use them to identify servers.

Each OS has different install location and steps. Describing each one of them is out the scope of this documentaton.

Steps for ArchLinux:

# 1. Copy CA to anchors
sudo cp ca.crt /etc/ca-certificates/trust-source/anchors/nats-ca.crt
# 2. Ensure correct permissions
sudo chmod 644 /etc/ca-certificates/trust-source/anchors/nats-ca.crt
# 3. Update trust database
sudo update-ca-trust extract
# 4. Verify
trust list | grep "NATS Root CA"

Via Configuration

If you prefer not too use the Trusted Store, you can pass the CA certificate directly in the Mir applications.

CLI

Edit CLI configuration file to add the RootCA mir tools config edit:

- name: local
  target: nats://localhost:4222
  grafana: localhost:3000
  rootCA: <path>/ca.crt

Device

There are a few options to load the RootCA file with the DeviceSDK.

# Using Builder with fix path
device := mir.NewDevice().
    WithTarget("nats://nats.example.com:4222").
    WithRootCA("/<path>/ca.crt").
    WithDeviceId("dev1").
    Build()
# Using Builder with default lookup
#   ./ca.crt
#   ~/.config/mir/ca.crt
#   /etc/mir/ca.crt
device := mir.NewDevice().
    WithTarget("nats://nats.example.com:4222").
    DefaultRootCA().
    Build()

It is also possible to load the RootCA from the config file:

# Using Builder with config file
device := mir.NewDevice().
    WithTarget("nats://nats.example.com:4222").
    DefaultConfigFile().
    Build()
mir:
  rootCA: "<path>/ca.crt"
  device:
    id: "dev1"

Run the device and no TLS errors should be displayed. Now run mir dev ls and you should see:

➜ mir dev ls
NAMESPACE/NAME                                DEVICE_ID        STATUS     LAST_HEARTHBEAT      LAST_SCHEMA_FETCH    LABELS
default/dev1                                  dev1             online     2025-09-18 16:16:27  2025-09-18 16:15:18

Completed

Congratulation, you now have ServerOnly TLS configured.