Install

# Linux binary
NATS_VER=2.10.21
curl -L "https://github.com/nats-io/nats-server/releases/download/v${NATS_VER}/nats-server-v${NATS_VER}-linux-amd64.tar.gz" \
    | tar -xz
sudo cp nats-server-v${NATS_VER}-linux-amd64/nats-server /usr/local/bin/

# Plus the nats CLI
NATSCLI_VER=0.1.5
curl -L "https://github.com/nats-io/natscli/releases/download/v${NATSCLI_VER}/nats-${NATSCLI_VER}-linux-amd64.zip" \
    -o /tmp/nats.zip
unzip /tmp/nats.zip -d /tmp
sudo install /tmp/nats-${NATSCLI_VER}-linux-amd64/nats /usr/local/bin/

# macOS via Homebrew
brew install nats-server nats-io/nats-tools/nats

Single-node JetStream-enabled server

# /etc/nats/nats.conf
port: 4222
http_port: 8222

jetstream {
    store_dir: /var/lib/nats/jetstream
    max_memory_store: 1GB
    max_file_store: 50GB
}

server_name: nats-1

# Optional: leaf node connection to a remote cluster (e.g. ngs.global.synadia.com)
# leafnodes {
#     remotes = [ { url: "tls://connect.ngs.global:7422" } ]
# }
sudo systemctl enable --now nats-server
nats server check connection

Plain pub/sub

NATS routes by subject — a dot-separated hierarchical string (orders.new, logs.app.error, events.user.signup). Subscribers can wildcard:

# Terminal A — subscribe
nats sub 'orders.*'                # matches one level: orders.new, orders.cancelled
# Or
nats sub 'logs.>'                  # matches arbitrary depth: logs.app.error.connection

# Terminal B — publish
nats pub orders.new '{"id":42,"amount":100}'

Plain NATS is at-most-once: subscribers connected at publish time get the message; others don't.

Request/reply

# Server: respond to math.add requests
nats reply 'math.add' '--command' 'echo $((NATS_REQUEST))'

# Client: request
nats request math.add "1+2"

Built-in pattern; no separate RPC framework needed.

JetStream: persistent streams

Plain NATS is in-memory; JetStream adds persistence + at-least-once delivery + replay.

# Create a stream that captures the orders.* subject space
nats stream add ORDERS --subjects 'orders.*' \
    --storage file --retention limits --max-age 7d --max-bytes 10GB \
    --max-msgs -1 --max-msg-size 1MB --replicas 1 \
    --discard old --dupe-window 2m \
    --no-allow-rollup --no-deny-delete --no-deny-purge

# Publish into it (any pub to orders.* now persists)
nats pub orders.new '{"id":42,"amount":100}'

# Browse messages
nats stream view ORDERS
nats stream report

Durable consumers

# Create a durable pull consumer
nats consumer add ORDERS billing-worker \
    --pull --filter 'orders.*' \
    --deliver all --ack explicit --max-deliver 5

# Worker pulls and acks
nats consumer next ORDERS billing-worker --count 10 --ack

If the worker crashes mid-process, the un-acked message redelivers (up to --max-deliver times, then dead-letters). Standard work-queue semantics.

JetStream KV: a clustered key-value store

nats kv add SETTINGS --history 5 --replicas 3 --max-bucket-size 100MB

nats kv put SETTINGS feature_x_enabled true
nats kv get SETTINGS feature_x_enabled
nats kv ls SETTINGS

# Watch for changes (push-style)
nats kv watch SETTINGS

Per-key history is configurable; watch-style change notifications come from the same connection that does the regular KV ops. Useful for distributed feature flags, dynamic configuration, leader election (via the --ttl / heartbeat pattern).

JetStream Object Store

# Create an object-store bucket
nats object add ASSETS --max-bucket-size 10GB

# Put / get / list
nats object put ASSETS ./logo.svg
nats object get ASSETS logo.svg --output /tmp/logo-restored.svg
nats object ls ASSETS

Backed by a JetStream stream under the hood. For "small object storage that's clustered with the same operational story as my messaging," this avoids standing up a separate S3-compatible service.

Clustering for HA

Three nodes, RAFT-based replication:

# Each node's nats.conf
server_name: nats-1            # nats-1 / nats-2 / nats-3

cluster {
    port: 6222
    routes = [
        nats-route://nats-1.lab:6222
        nats-route://nats-2.lab:6222
        nats-route://nats-3.lab:6222
    ]
}

jetstream {
    store_dir: /var/lib/nats/jetstream
}

Restart all three. nats server list shows the cluster. Streams / KV / Object Stores created with --replicas 3 get RAFT consensus + per-node replicas.

Auth + multi-tenant

NATS has a sophisticated account / user model. The simplest secure setup:

accounts {
    APP: {
        users: [
            { user: app-write, password: "$2a$11$..." }
            { user: app-read,  password: "$2a$11$...", permissions: { subscribe: "orders.>", publish: { deny: ">" } } }
        ]
        jetstream: enabled
    }
    SYS: {
        users: [{ user: admin, password: "$2a$11$..." }]
    }
}
system_account: SYS

Use bcrypt-hashed passwords (nats server passwd). For real production, use the NATS NKeys / JWT auth flow with a separate signing key — cryptographic identity per client, revocable centrally.

Bridges

NATS has first-class bridges into other systems:

  • MQTT broker mode — the NATS server can speak MQTT 3.1.1 on a separate port. IoT devices publish MQTT; downstream consumers use NATS. (Cleaner than running both Mosquitto and NATS; see Mosquitto tutorial for the dedicated-broker route.)
  • WebSocket support — browser clients connect via NATS-over-WebSocket, no separate gateway needed.
  • Kafka mirror — the NATS-Kafka bridge replicates between the two.

NATS vs the rest

  • vs Redis pub/sub — Redis is at-most-once + ephemeral; NATS plain is the same; JetStream adds persistence. NATS scales better cross-network.
  • vs Kafka / Redpanda (see Redpanda) — Kafka is built for high-throughput append-only logs with strong ordering; NATS JetStream covers the same shape but at smaller per-message-throughput. Kafka wins at very-large scale; NATS wins at simpler ops.
  • vs RabbitMQ — RabbitMQ has more elaborate routing (exchanges, bindings, AMQP topologies); NATS has flat subjects + filters. NATS is dramatically lighter operationally.
  • vs MQTT — MQTT is purpose-built for IoT; NATS's MQTT mode covers it plus gives you the wider NATS ecosystem in the same broker.

For internal microservices messaging on a self-hosted infrastructure, NATS hits the sweet spot of "feature-rich enough to actually use, light enough to actually operate."