Security tutorial

Self-signed mTLS with OpenSSL

Create separate local certificate authorities for RESP clients and Raft peers, then configure Nexir and redis-cli.

Controlled environments onlyUse this workflow for development, testing, or a controlled private environment. For production, use certificates issued and rotated by a trusted internal or public CA and follow your organization's key-management policy.

Prerequisites

  • OpenSSL 1.1.1 or newer.
  • A private hostname and IP for the node. The examples cover localhost, nexir-node1, 127.0.0.1, and 10.0.0.11.
  • A protected working directory.
umask 077
mkdir -p nexir-tls/{client,cluster}
cd nexir-tls

1. Create the client CA

openssl genrsa -out client/client-ca.key 4096
openssl req -x509 -new -key client/client-ca.key -sha256 -days 3650 \
  -subj "/CN=nexir-development-client-ca" \
  -out client/client-ca.pem

2. Create the RESP listener certificate

openssl genrsa -out client/redis-listener.key 2048
openssl req -new -key client/redis-listener.key \
  -subj "/CN=nexir-node1" \
  -out client/redis-listener.csr

cat > client/redis-listener.ext <<'EOF'
basicConstraints=CA:FALSE
keyUsage=digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=DNS:localhost,DNS:nexir-node1,IP:127.0.0.1,IP:10.0.0.11
EOF

openssl x509 -req -in client/redis-listener.csr \
  -CA client/client-ca.pem -CAkey client/client-ca.key -CAcreateserial \
  -out client/redis-listener.pem -days 825 -sha256 \
  -extfile client/redis-listener.ext

Replace SAN values with every hostname or IP clients actually use. A hostname mismatch causes the client to reject the server certificate.

3. Create a RESP client certificate

openssl genrsa -out client/redis-client.key 2048
openssl req -new -key client/redis-client.key \
  -subj "/CN=nexir-development-client" \
  -out client/redis-client.csr

cat > client/redis-client.ext <<'EOF'
basicConstraints=CA:FALSE
keyUsage=digitalSignature,keyEncipherment
extendedKeyUsage=clientAuth
EOF

openssl x509 -req -in client/redis-client.csr \
  -CA client/client-ca.pem -CAkey client/client-ca.key -CAcreateserial \
  -out client/redis-client.pem -days 825 -sha256 \
  -extfile client/redis-client.ext

4. Create a separate cluster CA

openssl genrsa -out cluster/cluster-ca.key 4096
openssl req -x509 -new -key cluster/cluster-ca.key -sha256 -days 3650 \
  -subj "/CN=nexir-development-cluster-ca" \
  -out cluster/cluster-ca.pem

5. Create a Raft peer certificate

The same peer credential is used inbound and outbound, so include both extended key usages.

openssl genrsa -out cluster/cluster-node.key 2048
openssl req -new -key cluster/cluster-node.key \
  -subj "/CN=nexir-node1" \
  -out cluster/cluster-node.csr

cat > cluster/cluster-node.ext <<'EOF'
basicConstraints=CA:FALSE
keyUsage=digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth,clientAuth
subjectAltName=DNS:localhost,DNS:nexir-node1,IP:127.0.0.1,IP:10.0.0.11
EOF

openssl x509 -req -in cluster/cluster-node.csr \
  -CA cluster/cluster-ca.pem -CAkey cluster/cluster-ca.key -CAcreateserial \
  -out cluster/cluster-node.pem -days 825 -sha256 \
  -extfile cluster/cluster-node.ext

6. Inspect and protect files

openssl verify -CAfile client/client-ca.pem \
  client/redis-listener.pem client/redis-client.pem
openssl verify -CAfile cluster/cluster-ca.pem cluster/cluster-node.pem
openssl x509 -in client/redis-listener.pem -noout -text

chmod 0600 client/*.key cluster/*.key
chmod 0644 client/*.pem cluster/*.pem

Store CA private keys away from database hosts after issuance. Copy only the required leaf keys and trust bundles to each host.

7. Configure and start Nexir

NEXIR__RAFT__NODE_ID=1 \
NEXIR__SERVER__REDIS_BIND=127.0.0.1:16379 \
NEXIR__SERVER__GRPC_BIND=127.0.0.1:15051 \
NEXIR__STORAGE__PATH=./data/tls-node1 \
NEXIR__SERVER__TLS__CLIENT__MODE=require \
NEXIR__SERVER__TLS__CLIENT__CERT_FILE="$PWD/client/redis-listener.pem" \
NEXIR__SERVER__TLS__CLIENT__KEY_FILE="$PWD/client/redis-listener.key" \
NEXIR__SERVER__TLS__CLIENT__CA_BUNDLE="$PWD/client/client-ca.pem" \
NEXIR__SERVER__TLS__CLUSTER__MODE=require \
NEXIR__SERVER__TLS__CLUSTER__CERT_FILE="$PWD/cluster/cluster-node.pem" \
NEXIR__SERVER__TLS__CLUSTER__KEY_FILE="$PWD/cluster/cluster-node.key" \
NEXIR__SERVER__TLS__CLUSTER__CA_BUNDLE="$PWD/cluster/cluster-ca.pem" \
nexir

8. Connect with redis-cli

redis-cli --tls -h localhost -p 16379 \
  --cacert "$PWD/client/client-ca.pem" \
  --cert "$PWD/client/redis-client.pem" \
  --key "$PWD/client/redis-client.key" \
  --raw PING
# PONG

Initialize using the same TLS client flags and an https:// peer address:

redis-cli --tls -h localhost -p 16379 \
  --cacert "$PWD/client/client-ca.pem" \
  --cert "$PWD/client/redis-client.pem" \
  --key "$PWD/client/redis-client.key" \
  --raw INIT 1 https://127.0.0.1:15051 127.0.0.1:16379

Common TLS errors

SymptomCheck
Unknown CACorrect CA bundle on both sides and complete certificate chain
Hostname mismatchConnect with a DNS/IP value present in the server certificate SAN
Peer sent no certificatesClient certificate and key were supplied; a one-off warning may occur during membership TLS preflight
Invalid private keyPEM encoding, readable permissions, and public key match
Peer connection refusedhttps:// address, private firewall rules, and peer listener state
Startup rejects shared CAUse distinct client and cluster CA bundle files

Rotation

Nexir does not hot reload TLS. Use overlapping trust bundles and rolling restarts, one node at a time. Monitor nexir_tls_cert_not_after_timestamp_seconds{surface="client"} and {surface="cluster"} and alert before expiry.