2. Systemd Service
Save to /usr/lib/systemd/system/nats-server-hardened.service:
[Unit]
Description=NATS Server
After=network-online.target ntp.service
[Service]
Type=simple
EnvironmentFile=-/etc/default/nats-server
ExecStart=/usr/sbin/nats-server -c /etc/nats-server.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s SIGUSR2 $MAINPID
User=nats
Group=nats
Restart=on-failure
RestartSec=5
TimeoutStopSec=150
LimitNOFILE=800000
CapabilityBoundingSet=
LockPersonality=true
MemoryDenyWriteExecute=true
NoNewPrivileges=true
PrivateDevices=true
PrivateTmp=true
PrivateUsers=true
ProcSubset=pid
ProtectClock=true
ProtectControlGroups=true
ProtectHome=true
ProtectHostname=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectSystem=strict
ReadOnlyPaths=
RestrictAddressFamilies=AF_INET AF_INET6
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
UMask=0077
InaccessiblePaths=/etc/ssh
ProtectProc=invisible
PrivateIPC=true
ReadWritePaths=/var/lib/nats
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now nats-server-hardened.service
sudo systemctl status nats-server-hardened.service
3. Server Configuration
/etc/nats-server.conf — single-node baseline with JetStream and monitoring:
server_name: "nats-prod-01"
host: "0.0.0.0"
port: 4222
max_connections: 65536
max_payload: 8388608 # 8 MB
logtime: true
debug: false
trace: false
http_port: 8222
jetstream {
store_dir: "/var/lib/nats"
max_mem: 2G
max_file: 50G
}
# Authentication (uncomment for non-clustered single-node)
# authorization {
# users: [
# { user: "admin", password: "$2a$11$your_bcrypt_hash_here" }
# ]
# }
sudo systemctl restart nats-server-hardened.service


4. Clustering
4.1 Set Hostnames (once per node)
hostnamectl set-hostname nats-prod-1
hostnamectl set-hostname nats-prod-2
hostnamectl set-hostname nats-prod-3
4.2 Append Cluster Block to /etc/nats-server.conf
Update server_name per node (nats-prod-1, nats-prod-2, nats-prod-3). Append:
cluster {
name: "PRODUCTION-CLUSTER" # Must be identical across all nodes
listen: "0.0.0.0:6222"
routes: [
"nats://192.168.1.45:6222",
"nats://192.168.1.46:6222",
"nats://192.168.1.48:6222"
]
}
sudo systemctl restart nats-server-hardened.service
4.3 Verify Routes
curl -s http://localhost:8222/routez | grep -A 5 "routes"
5. Account & Security Model
Append to /etc/nats-server.conf on all nodes. Replaces any authorization {} block.
system_account: SYS
no_auth_user: anonymous
accounts {
SYS {
users: [
{ user: "vinz", password: "shamballa" }
]
}
APP {
users: [
{ user: "anonymous" }
]
jetstream: enabled
}
}
Note: Replace plaintext passwords with BCrypt hashes in production.
Generate a hash:nats server passwd
sudo systemctl restart nats-server-hardened.service
6. CLI Contexts
nats context add prod-cluster \
--server 127.0.0.1:4222 \
--user vinz \
--password shamballa \
--select
nats context add app-dev \
--server 127.0.0.1:4222 \
--select
| Task | Context |
|---|---|
nats server info |
prod-cluster |
nats stream ls |
app-dev |
nats kv / nats obj |
app-dev |
nats context list
nats context select prod-cluster
Runbook7. Validation & Monitoring
# Server vitals
curl http://localhost:8222/varz
# JetStream stats
curl http://localhost:8222/jsz
# Cluster routes
curl -s http://localhost:8222/routez | grep -A 5 "routes"
# Server identity (admin context)
nats server info --context prod-cluster


nats-top
curl -sf https://binaries.nats.dev/nats-io/nats-top@latest | sh
sudo mv nats-top /usr/local/bin/
nats-top

8. KV & Object Store
Distributed Key-Value Store
# Create a bucket with 5 revision history entries
nats kv add MY_CONFIG --history 5
# Write
nats kv put MY_CONFIG db_url "postgres://localhost:5432"
# Read
nats kv get MY_CONFIG db_url
# Watch for real-time changes
nats kv watch MY_CONFIG

Distributed Object Store
# Create a bucket
nats obj add SYSTEM_FILES
# Upload
nats obj put SYSTEM_FILES /etc/nats-server.conf
# List
nats obj ls SYSTEM_FILES
# Download
nats obj get SYSTEM_FILES /etc/nats-server.conf --output /tmp/downloaded-config.conf

9. mTLS Hardening
9.1 Generate Certificates
mkdir -p /tmp/nats-certs && cd /tmp/nats-certs
Save as gen-certs.sh and run:
#!/bin/bash
# Root CA
openssl req -x509 -newkey rsa:4096 -days 3650 -nodes \
-keyout ca-key.pem -out ca.pem \
-subj "/O=NATS/CN=NATS-Root-CA"
# Server certificate with SANs for all cluster node IPs
openssl req -newkey rsa:4096 -nodes \
-keyout server-key.pem -out server.csr \
-subj "/O=NATS/CN=nats-server"
cat > extfile.cnf << EOF
subjectAltName = DNS:localhost, IP:127.0.0.1, IP:192.168.1.45, IP:192.168.1.46, IP:192.168.1.48
EOF
openssl x509 -req -in server.csr -days 3650 \
-CA ca.pem -CAkey ca-key.pem -CAcreateserial \
-out server.pem -extfile extfile.cnf
# Client certificate (admin)
openssl req -newkey rsa:4096 -nodes \
-keyout client-key.pem -out client.csr \
-subj "/O=NATS/CN=vinz-admin"
openssl x509 -req -in client.csr -days 3650 \
-CA ca.pem -CAkey ca-key.pem -CAcreateserial \
-out client.pem
9.2 Deploy Certificates (all nodes)
sudo mkdir -p /etc/nats/certs
sudo cp /tmp/nats-certs/ca.pem /tmp/nats-certs/server*.pem /etc/nats/certs/
sudo chown -R nats:nats /etc/nats/certs
sudo chmod 400 /etc/nats/certs/*key.pem
9.3 Update Server Configuration
Replace the cluster and base config on each node:
server_name: "nats-prod-1" # Change per node
host: "0.0.0.0"
port: 4222
http_port: 8222
# Client mTLS
tls {
ca_file: "/etc/nats/certs/ca.pem"
cert_file: "/etc/nats/certs/server.pem"
key_file: "/etc/nats/certs/server-key.pem"
verify: true # Enforces mutual TLS for all clients
timeout: 2.0
}
cluster {
name: "PRODUCTION-CLUSTER"
listen: "0.0.0.0:6222"
tls {
ca_file: "/etc/nats/certs/ca.pem"
cert_file: "/etc/nats/certs/server.pem"
key_file: "/etc/nats/certs/server-key.pem"
verify: true
timeout: 5.0
}
routes: [
"tls://192.168.1.45:6222",
"tls://192.168.1.46:6222",
"tls://192.168.1.48:6222"
]
}
sudo systemctl restart nats-server-hardened.service
9.4 Update CLI Context for mTLS
nats context save prod-cluster \
--server tls://127.0.0.1:4222 \
--user vinz \
--password shamballa \
--tlscert /tmp/nats-certs/client.pem \
--tlskey /tmp/nats-certs/client-key.pem \
--tlsca /tmp/nats-certs/ca.pem \
--select
nats context save app-dev \
--server tls://127.0.0.1:4222 \
--tlscert /tmp/nats-certs/client.pem \
--tlskey /tmp/nats-certs/client-key.pem \
--tlsca /tmp/nats-certs/ca.pem \
--select
nats server info

At this point we have a fully operational 3-node NATS cluster with JetStream enabled across all nodes, mutual TLS enforced for both client connections and inter-node routing, account-based isolation between system and application traffic, and persistent KV and object store backed by a dedicated data volume. The cluster is managed via named CLI contexts and exposed through a hardened systemd service unit.