How to Install NATS on Ubuntu 24.04 VPS: High-Performance Messaging for Microservices
Modern microservice architectures live or die by the speed and reliability of their messaging layer. NATS is a cloud-native messaging system written in Go that routinely handles millions of messages per second per node with sub-millisecond latency, all from a single 20 MB binary with zero external dependencies. This tutorial walks you through installing NATS on an Ubuntu 24.04 VPS, from first SSH connection to a hardened three-node cluster with JetStream persistence, NKEY/JWT authentication, and TLS.
Looking for a fast, affordable VPS to host NATS? The CloudCore Starter plan gives you enough power to handle production-grade NATS workloads for the price of a cup of coffee per month.
Table of Contents
What is NATS?
NATS is an open-source, high-performance messaging system that powers the nervous systems of countless cloud-native applications. Originally created at Cloud Foundry and now a graduated project in the Cloud Native Computing Foundation (CNCF), NATS was designed from day one for speed, simplicity, and operational sanity. The core server is a single Go binary, typically less than 25 MB, with no external dependencies, no JVM, no Zookeeper, and no broker state to back up when you are running in core mode.
NATS supports a rich set of messaging patterns out of the box. The core protocol gives you publish/subscribe with subject-based hierarchical routing (orders.eu.fr.new), request/reply with automatic inbox handling for synchronous RPC-style exchanges, and queue groups for load-balanced work distribution across consumer instances. On top of that core, JetStream adds durable streaming with at-least-once and exactly-once delivery semantics, message replay, stream mirroring, and built-in key-value and object storage layers that reuse the same persistence engine.
The typical use cases for NATS include service-to-service messaging in microservice architectures, IoT telemetry ingestion at the edge, real-time event streaming for analytics pipelines, job queues for background workers, distributed configuration and feature flags via the KV store, large binary artifact distribution via the Object Store, and command and control for fleets of devices or edge gateways. Companies like Walmart, Tinder, GE, Ericsson, Mastercard, and the U.S. Department of Defense run NATS in production across tens of thousands of nodes.
Why Self-Host NATS on Your VPS?
Running NATS on your own VPS instead of a managed messaging service gives you real, measurable advantages:
- Extreme performance per dollar -- A single NATS node on a modest VPS can push 5-10 million small messages per second through core pub/sub. No managed service comes close on a price-per-throughput basis.
- Flat-rate cost -- A CloudCore Starter VPS costs the same whether you send one message a day or 100 million. No per-message, per-GB, or per-connection billing.
- Zero vendor lock-in -- NATS is Apache 2.0 licensed. The same binary runs identically on your laptop, a Raspberry Pi, and a 96-core production server.
- No external dependencies -- Unlike Kafka (needs ZooKeeper or KRaft plus substantial tuning) or RabbitMQ (needs Erlang VM), the NATS server is a single static binary. Backups are trivial.
- Sub-millisecond latency -- On the same VPS, NATS routinely delivers messages in 50-200 microseconds end to end. That is fast enough for in-process replacement of direct HTTP calls between services.
- Built-in KV and Object Store -- You get a Redis-like KV store and an S3-like Object Store for free, backed by the same JetStream engine. One piece of infrastructure, three capabilities.
- Edge-ready with leaf nodes -- Deploy NATS at the edge (retail stores, factories, IoT gateways) and bridge securely back to a central cluster with leaf nodes over a single TCP connection.
- Data sovereignty -- Your messages, stream data, and KV contents never touch a third-party service. Important for GDPR, HIPAA, financial, and defense workloads.
NATS vs Kafka vs RabbitMQ
All three are excellent messaging systems, but they optimize for different trade-offs. Understanding the differences saves you weeks of regret later.
| Feature | NATS (with JetStream) | Apache Kafka | RabbitMQ |
|---|---|---|---|
| Primary model | Subject-based pub/sub + streams | Partitioned log | Broker with exchanges/queues |
| Typical latency | 50-500 microseconds | 2-10 ms | 1-5 ms |
| Binary size | ~22 MB, single binary | ~400 MB + JVM + ZK/KRaft | Erlang VM + several packages |
| External deps | None | JVM, historically ZooKeeper | Erlang/OTP |
| Persistence | JetStream (optional) | Always (log-structured) | Optional per queue |
| Request/reply | First-class | Manual (two topics) | Via RPC pattern |
| Delivery guarantees | At-most, at-least, exactly once | At-least, exactly once | At-least once |
| KV store included | Yes (JetStream KV) | Via Kafka Streams/KTables | No |
| Object store included | Yes (JetStream Object) | No | No |
| Protocol | Plain text, ~30 verbs | Binary | AMQP 0-9-1 (complex) |
| Edge / IoT story | Leaf nodes, MQTT bridge | Weak | MQTT plugin |
| Multi-tenancy | Accounts (native) | Via ACLs and naming | Vhosts |
| Operational complexity | Low | High | Medium |
| Best for | Microservices, IoT, edge, cloud-native | High-throughput log, stream processing | Traditional enterprise queueing |
Choose Kafka when you need long-term log retention (weeks to years), complex stream processing with Kafka Streams or Flink, or when you are already deep into the Confluent/Kafka ecosystem. See the companion Kafka install guide.
Choose RabbitMQ when you need the AMQP protocol, complex routing topologies (topic/headers/fanout exchanges), or legacy enterprise integration. See the companion RabbitMQ install guide.
For in-memory caching and simple pub/sub, also consider Redis.
Prerequisites
Before you start, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access
- SSH access to the server
- At least 2 GB of RAM for a single node (4 GB+ recommended for JetStream workloads)
- At least 10 GB of free disk space (more if you plan heavy JetStream persistence)
- Ports 4222 (client), 6222 (cluster), 7422 (leaf), 8222 (monitoring) available
Recommended Plan: CloudCore Starter>
For a single-node NATS deployment that comfortably handles tens of thousands of messages per second, plus JetStream persistence for microservice workloads, we recommend the CloudCore Starter plan:>
- 4 vCPU cores
- 6 GB RAM
- 100 GB NVMe SSD
- Unmetered bandwidth>
For a 3-node cluster, provision three of these VPS in the same region for low inter-node latency.
Connect to your server:
ssh root@your-server-ipStep 1: Update System Packages
Start with a fresh package index and security updates.
sudo apt update && sudo apt upgrade -yInstall the handful of utilities we will use in the rest of this guide:
sudo apt install -y curl wget unzip ufw ca-certificatesIf the kernel was upgraded, reboot:
sudo rebootReconnect once the server is back online.
Step 2: Install the NATS Server Binary
NATS is distributed as a single static binary on GitHub. There is no official apt repository, but installation is a three-command affair.
Fetch the latest release (check github.com/nats-io/nats-server/releases for the newest version and update NATS_VERSION accordingly):
NATS_VERSION="2.10.22"
cd /tmp
wget https://github.com/nats-io/nats-server/releases/download/v${NATS_VERSION}/nats-server-v${NATS_VERSION}-linux-amd64.zip
unzip nats-server-v${NATS_VERSION}-linux-amd64.zip
sudo install -m 755 nats-server-v${NATS_VERSION}-linux-amd64/nats-server /usr/local/bin/nats-serverVerify the install:
nats-server --versionExpected output:
nats-server: v2.10.22Create a dedicated system user so NATS does not run as root:
sudo useradd --system --home /var/lib/nats --shell /usr/sbin/nologin nats
sudo mkdir -p /var/lib/nats /etc/nats /var/log/nats
sudo chown -R nats:nats /var/lib/nats /var/log/natsStep 3: Create a systemd Service
Create a unit file so NATS starts automatically on boot and restarts on failure.
sudo tee /etc/systemd/system/nats.service > /dev/null <<'EOF' [Unit] Description=NATS Server After=network-online.target ntp.service Wants=network-online.target[Service] Type=simple User=nats Group=nats ExecStart=/usr/local/bin/nats-server -c /etc/nats/nats.conf ExecReload=/bin/kill -HUP $MAINPID Restart=always RestartSec=5 LimitNOFILE=800000 LimitNPROC=64000
[Install] WantedBy=multi-user.target EOF
The LimitNOFILE setting matters -- NATS can easily handle hundreds of thousands of open connections and the default systemd limit of 1024 will throttle you almost immediately.
Reload systemd so it picks up the new unit:
sudo systemctl daemon-reloadDo not start the service yet -- we still need to write nats.conf in the next step.
Step 4: Configure nats.conf with JetStream
Create the main configuration file. This enables JetStream (durable persistence), the monitoring endpoint, and sensible defaults.
sudo tee /etc/nats/nats.conf > /dev/null <<'EOF'
Server identity
server_name: "nats-01"
listen: 0.0.0.0:4222HTTP monitoring endpoint
http: 0.0.0.0:8222Logging
log_file: "/var/log/nats/nats-server.log"
logtime: true
debug: false
trace: falseClient connection limits
max_connections: 100000
max_payload: 8MB
max_pending: 256MB
write_deadline: "10s"JetStream - durable persistence engine
jetstream {
store_dir: "/var/lib/nats/jetstream"
max_memory_store: 1GB
max_file_store: 20GB
}
EOFFix permissions so the nats user can read the config and write the store:
sudo chown -R nats:nats /etc/nats /var/lib/nats
sudo chmod 640 /etc/nats/nats.confOpen firewall ports:
sudo ufw allow 4222/tcp comment 'NATS client'
sudo ufw allow 8222/tcp comment 'NATS monitoring'
sudo ufw --force enableStart NATS and enable it on boot:
sudo systemctl enable --now nats
sudo systemctl status natsExpected output (abbreviated):
● nats.service - NATS Server
Loaded: loaded (/etc/systemd/system/nats.service; enabled; preset: enabled)
Active: active (running) since Wed 2026-04-16 10:00:00 UTC; 3s ago
Main PID: 2345 (nats-server)Confirm the monitoring endpoint responds:
curl http://localhost:8222/varz | head -40You should see a JSON document describing the server version, uptime, connection counts, and JetStream status.
Step 5: Install the NATS CLI
The nats CLI is the Swiss Army knife for interacting with a NATS server: publishing, subscribing, managing streams, inspecting key-value buckets, benchmarking, and more.
NATS_CLI_VERSION="0.1.5"
cd /tmp
wget https://github.com/nats-io/natscli/releases/download/v${NATS_CLI_VERSION}/nats-${NATS_CLI_VERSION}-linux-amd64.zip
unzip nats-${NATS_CLI_VERSION}-linux-amd64.zip
sudo install -m 755 nats-${NATS_CLI_VERSION}-linux-amd64/nats /usr/local/bin/natsVerify:
nats --versionCreate a default context so you do not have to pass -s on every command:
nats context save default --server=nats://127.0.0.1:4222 --selectQuick health check:
nats server check connectionExpected output:
OK Connection OK: connected to nats://127.0.0.1:4222 in 412.8µsStep 6: Pub/Sub and Req/Rep Basics
Open two SSH sessions to the server so you can see pub/sub in action.
In terminal A, subscribe to a subject:
nats sub "orders.>"The > is a multi-level wildcard -- this subscription will receive messages on any subject that starts with orders.. For single-token wildcards use (e.g., orders..new matches orders.eu.new but not orders.eu.fr.new).
In terminal B, publish:
nats pub orders.eu.fr.new '{"id":"o-4711","sku":"VPS-CC-STARTER","qty":1}'
nats pub orders.us.ca.new '{"id":"o-4712","sku":"VPS-CC-PRO","qty":2}'Terminal A will print both messages.
Request/Reply
NATS supports synchronous RPC patterns with automatic inbox handling. In terminal A, run a responder:
nats reply "time.now" --command "date -u +%FT%TZ"In terminal B:
nats request "time.now" ""Expected output:
10:42:17 Sending request on "time.now"
10:42:17 Received with rtt 612.5µs
2026-04-16T10:42:17ZQueue Groups (Load Balancing)
Run two subscribers in the same queue group to distribute work across them. Terminal A:
nats sub "jobs" --queue workersTerminal B (second subscriber, same queue):
nats sub "jobs" --queue workersTerminal C (publisher):
for i in $(seq 1 10); do nats pub jobs "job-$i"; doneEach message is delivered to exactly one of the two subscribers. This is how you horizontally scale stateless workers.
Step 7: Streams and Consumers with JetStream
Core pub/sub is fire-and-forget -- subscribers miss messages published while they were offline. JetStream adds durable streams that retain messages for replay and guaranteed delivery.
Create a Stream
nats stream add ORDERS \
--subjects "orders.>" \
--storage file \
--retention limits \
--max-age 7d \
--max-msgs=-1 \
--max-bytes=1GB \
--discard old \
--replicas 1 \
--dupe-window 2m \
--defaultsInspect it:
nats stream info ORDERSExpected output (abbreviated):
Information for Stream ORDERSConfiguration: Subjects: orders.> Storage: File Retention: Limits Max Age: 7d0h0m0s Max Bytes: 1.0 GiB Replicas: 1
State: Messages: 0 Bytes: 0 B
Publish into it -- any message matching orders.> is persisted:
nats pub orders.eu.fr.new '{"id":"o-5001","sku":"VPS-CC-STARTER"}'
nats pub orders.us.ca.new '{"id":"o-5002","sku":"VPS-CC-PRO"}'Replay Messages
nats stream view ORDERSYou can replay from the beginning, by sequence, by time, or by last-per-subject. That ability alone is a game-changer versus stateless pub/sub.
Create a Durable Consumer
Consumers track delivery state for a specific subscriber. Two flavors:
Pull consumer (worker pulls messages at its own pace -- recommended for most workloads):
nats consumer add ORDERS order-processor \
--pull \
--deliver all \
--ack explicit \
--max-deliver 5 \
--wait 30s \
--filter "orders.>" \
--replay instant \
--defaultsConsume:
nats consumer next ORDERS order-processor --count 10Each fetched message must be ack-ed to confirm processing. Unacknowledged messages are redelivered up to --max-deliver times, then moved to a dead-letter pattern you can subscribe to on $JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES.>.
Push consumer (NATS delivers to a subject the client subscribes to -- good for always-on services):
nats consumer add ORDERS billing-service \
--target billing.incoming \
--deliver new \
--ack explicit \
--defaultsThen subscribe as usual: nats sub billing.incoming.
Exactly-Once Semantics
Set a Nats-Msg-Id header when publishing to deduplicate within the stream's --dupe-window:
nats pub orders.eu.fr.new --header "Nats-Msg-Id:o-5001" '{"id":"o-5001"}'
Re-publishing the same ID within 2m is silently discarded
nats pub orders.eu.fr.new --header "Nats-Msg-Id:o-5001" '{"id":"o-5001"}'Combined with consumer acknowledgement tokens (Nats-Ack), you get true exactly-once processing.
Step 8: Key-Value and Object Store
JetStream's persistence engine powers two higher-level abstractions: a Redis-like Key-Value Store and an S3-like Object Store. Neither requires a separate service.
Key-Value Store
Create a bucket:
nats kv add config --history 5 --ttl 24h --replicas 1Put, get, and watch keys:
nats kv put config feature.new_checkout "enabled"
nats kv put config feature.ai_assistant "disabled"
nats kv get config feature.new_checkout
nats kv ls configLive-watch changes (great for hot-reloading configuration in services):
nats kv watch configIn another terminal:
nats kv put config feature.new_checkout "disabled"The watch immediately prints the update. Every NATS client library exposes the same watcher API, so your Go, Node.js, Python, or Rust service can swap feature flags instantly with no polling.
Object Store
Create an object bucket:
nats object add artifacts --replicas 1Upload and list:
echo "Build output from CI" > /tmp/build.log
nats object put artifacts /tmp/build.log --name=build-4711.log
nats object ls artifactsDownload:
nats object get artifacts build-4711.log --output=/tmp/restored.logThe Object Store automatically chunks large files (default 128 KB) so you can stream multi-gigabyte artifacts without blowing out memory. It is ideal for distributing firmware images, container layers, or ML model weights across a fleet of leaf nodes.
Step 9: NKEY and JWT Authentication
By default, NATS accepts anonymous connections. For production, switch to NKEY (Ed25519 keypair auth) or JWT (decentralized account-based auth).
NKEY Authentication (Simple)
Install the NATS nsc and nk tools:
curl -L https://raw.githubusercontent.com/nats-io/nsc/main/install.sh | sudo sh
go install github.com/nats-io/nkeys/nk@latest 2>/dev/null || \
sudo wget -O /usr/local/bin/nk https://github.com/nats-io/nkeys/releases/latest/download/nk-linux-amd64 && \
sudo chmod +x /usr/local/bin/nkGenerate a user keypair:
nk -gen user -puboutOutput:
SUAEL6GG2L2HIF7DUGZJGMRUFKXELGGYFMHF2H6RY2CHKLMVS5H6O7XU3I # seed (private)
UDXU4RCSJNZOKIYLCNGWMOL6GIXELBAAZCQEKOAS6OJUVRZWTW6CSYFU # publicSave the seed to a local file (keep it secret):
echo "SUAEL6GG2L2HIF7DUGZJGMRUFKXELGGYFMHF2H6RY2CHKLMVS5H6O7XU3I" > ~/user.seed
chmod 600 ~/user.seedAdd the public key to nats.conf:
sudo tee -a /etc/nats/nats.conf > /dev/null <<'EOF'authorization { users: [ { nkey: "UDXU4RCSJNZOKIYLCNGWMOL6GIXELBAAZCQEKOAS6OJUVRZWTW6CSYFU" } ] } EOF
sudo systemctl reload nats
Connect with the seed:
nats --nkey=~/user.seed sub "orders.>"JWT Authentication (Decentralized, Multi-Tenant)
For production multi-tenant setups, use nsc to manage a hierarchy of Operator -> Account -> User JWTs. Run once on your admin workstation:
nsc add operator --generate-signing-key --sys --name MyCorp
nsc add account --name AppTeam
nsc add user --account AppTeam --name svc-orders
nsc describe user svc-ordersThen configure the NATS server to trust the operator and resolve accounts via a resolver block. The full flow is documented at docs.nats.io/running-a-nats-service/configuration/securing_nats/auth_intro/jwt. JWT auth lets you rotate credentials without restarting the server and isolates tenants into fully separate accounts with their own subject namespace, streams, and KV buckets.
Step 10: Enable TLS
Encrypt client connections with TLS. Use Let's Encrypt for a public-facing server or your own internal CA for private clusters.
Install certbot and issue a certificate (replace the hostname):
sudo apt install -y certbot
sudo certbot certonly --standalone -d nats.yourdomain.comGrant the nats user read access to the certs:
sudo setfacl -R -m u:nats:rX /etc/letsencrypt/live /etc/letsencrypt/archiveUpdate nats.conf to enable TLS:
sudo tee -a /etc/nats/nats.conf > /dev/null <<'EOF'tls { cert_file: "/etc/letsencrypt/live/nats.yourdomain.com/fullchain.pem" key_file: "/etc/letsencrypt/live/nats.yourdomain.com/privkey.pem" timeout: 3 verify: false } EOF
sudo systemctl restart nats
Confirm clients must now use TLS:
nats --tlsca=/etc/letsencrypt/live/nats.yourdomain.com/fullchain.pem \
--server=tls://nats.yourdomain.com:4222 \
server check connectionFor mutual TLS (client cert required), set verify: true and supply a ca_file -- every client must then present a cert signed by that CA.
Step 11: Build a 3-Node Cluster
A single node is fine for dev, but production wants replication. NATS clustering is stateless routing -- add more nodes and they mesh automatically. JetStream replicates streams across the cluster using Raft consensus.
Provision two more CloudCore Starter VPS instances and repeat Steps 1-4 on each. Use these server names and private IPs (adjust to your actual addresses):
| Node | server_name | IP |
|---|---|---|
| Node 1 | nats-01 | 10.0.0.11 |
| Node 2 | nats-02 | 10.0.0.12 |
| Node 3 | nats-03 | 10.0.0.13 |
cluster block to /etc/nats/nats.conf:sudo tee -a /etc/nats/nats.conf > /dev/null <<'EOF'
cluster { name: "prod-cluster" listen: 0.0.0.0:6222 routes: [ "nats-route://10.0.0.11:6222" "nats-route://10.0.0.12:6222" "nats-route://10.0.0.13:6222" ] } EOF
Also update each node's server_name to match the table above (nats-01, nats-02, nats-03). Each node must have a unique name.
Open the cluster port on every node:
sudo ufw allow 6222/tcp comment 'NATS cluster'
sudo systemctl restart natsVerify the cluster formed:
nats server listExpected output:
╭──────────────────────────────────────────────────────────────────────────────╮
│ Server Overview │
├─────────┬───────────────┬─────────────┬─────────┬──────┬───────┬────────────┤
│ Name │ Cluster │ Host │ Version │ Conn │ Subs │ JetStream │
├─────────┼───────────────┼─────────────┼─────────┼──────┼───────┼────────────┤
│ nats-01 │ prod-cluster │ 10.0.0.11 │ 2.10.22 │ 0 │ 56 │ yes │
│ nats-02 │ prod-cluster │ 10.0.0.12 │ 2.10.22 │ 0 │ 56 │ yes │
│ nats-03 │ prod-cluster │ 10.0.0.13 │ 2.10.22 │ 0 │ 56 │ yes │
╰─────────┴───────────────┴─────────────┴─────────┴──────┴───────┴────────────╯Create a replicated stream -- JetStream will place one leader and two followers:
nats stream add ORDERS-HA \
--subjects "orders.>" \
--storage file \
--retention limits \
--max-age 30d \
--replicas 3 \
--defaultsCheck stream placement:
nats stream info ORDERS-HAYou will see Replicas: 3 and a cluster section listing the leader and followers. You can now kill any one node -- publishes and consumes continue without interruption. JetStream uses Raft, so a 3-node cluster tolerates the loss of one node.
Important: Always use an odd number of JetStream replicas (3 or 5) to avoid split-brain. Never run with replicas=2.
Step 12: Leaf Nodes for Edge Deployments
Leaf nodes let you deploy a small NATS server at the edge (a branch office, a factory floor, an IoT gateway) that bridges securely back to your central cluster over a single outbound TCP connection. Subjects transparently span the leaf and the hub, with subject-level permissions controlling what flows which way.
On the hub cluster, enable leaf node listener (add to nats.conf on each hub node):
sudo tee -a /etc/nats/nats.conf > /dev/null <<'EOF'leafnodes { listen: 0.0.0.0:7422 } EOF
sudo ufw allow 7422/tcp comment 'NATS leaf node' sudo systemctl restart nats
On the edge leaf node (a separate VPS or on-premise device), install NATS as in Steps 1-3, but use this minimal config:
sudo tee /etc/nats/nats.conf > /dev/null <<'EOF' server_name: "edge-factory-01" listen: 0.0.0.0:4222 http: 0.0.0.0:8222jetstream { store_dir: "/var/lib/nats/jetstream" max_file_store: 5GB }
leafnodes { remotes: [ { url: "nats-leaf://hub.yourdomain.com:7422" # Optional credentials for the hub # credentials: "/etc/nats/leaf.creds" } ] } EOF
sudo systemctl restart nats
Local clients connect to edge-factory-01:4222 as usual and can publish/subscribe to any subject that the hub's permissions allow. Messages are queued locally if the WAN link goes down and forwarded when connectivity returns, making this pattern ideal for intermittent edge links.
Performance Tuning
Squeeze more throughput out of your NATS deployment with these adjustments.
File Descriptor Limits
Already set to 800,000 in the systemd unit from Step 3. For dense edge nodes or brokers with millions of connections, bump LimitNOFILE higher.
TCP Tuning
Add to /etc/sysctl.d/99-nats.conf:
sudo tee /etc/sysctl.d/99-nats.conf > /dev/null <<'EOF'
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.core.netdev_max_backlog = 65535
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_tw_reuse = 1
EOF
sudo sysctl --systemJetStream Storage
- Put
store_diron NVMe SSD (CloudCore Starter already uses NVMe). Mechanical drives cripple JetStream throughput. - Size
max_file_storeto about 70% of your available disk -- JetStream writes compacted blocks and needs overhead for compaction. - Tune
max_memory_storebased on your hottest short-lived streams. Memory-only streams are blazingly fast but lost on restart.
Benchmark Your Node
nats bench foo --pub 2 --sub 2 --msgs 1000000 --size 128Typical result on a CloudCore Starter (4 vCPU, 6 GB RAM):
Pub stats: 4,123,456 msgs/sec ~ 503.37 MB/sec
Sub stats: 8,246,912 msgs/sec ~ 1006.74 MB/secFor JetStream benchmark:
nats bench foo --js --pub 2 --sub 2 --msgs 200000 --size 512 --stream=BENCHTroubleshooting
| Problem | Cause | Solution |
|---|---|---|
nats: no servers available for connection | Server not listening on expected address | Check systemctl status nats and verify listen in nats.conf. |
JetStream not enabled for account | JetStream disabled globally or per-account | Confirm the jetstream {} block is present and restart. For JWT setups, enable JS on the account. |
maximum payload exceeded | Message larger than max_payload | Increase max_payload in config or chunk the payload client-side. |
| Cluster not forming | Port 6222 blocked or server_name collisions | Check UFW/iptables on cluster port; ensure unique server_name per node. |
| High memory use | Too many loaded streams / subscriptions | Review nats stream list, set max_memory_store limits, prune unused streams. |
| Slow JetStream writes | Shared or HDD disk | Move store_dir to local NVMe. |
| Leaf node disconnecting | WAN instability or TLS mismatch | Check nats server report connections; verify cert trust chain on both sides. |
Permission denied writing to store_dir | Wrong ownership | sudo chown -R nats:nats /var/lib/nats. |
| Clients hitting 1024 FD limit | systemd default limits | Confirm LimitNOFILE in the unit file and rerun daemon-reload. |
Viewing Logs
sudo journalctl -u nats -fOr the dedicated log file:
sudo tail -f /var/log/nats/nats-server.logLive monitoring:
watch -n 1 'curl -s http://localhost:8222/varz | jq "{conns: .connections, mem: .mem, subs: .subscriptions, msgs_in: .in_msgs, msgs_out: .out_msgs}"'FAQ
How is NATS different from Kafka for event streaming?
Both can persist and replay messages, but they optimize for different scenarios. Kafka stores everything in partitioned logs and shines when you need weeks or months of retention plus heavy stream processing frameworks like Kafka Streams or Flink. NATS JetStream favors operational simplicity, lower latency (sub-millisecond versus single-digit milliseconds), and a unified messaging+KV+Object model. For microservice-to-microservice events, command buses, and IoT telemetry, NATS is usually the simpler and cheaper choice. For data-lake-scale streaming into analytics, Kafka still wins. Many teams run both: NATS for service messaging, Kafka for long-term log pipelines. Our Kafka install guide covers the other side of the fence.
Do I need JetStream for simple pub/sub?
No. Core NATS pub/sub is fire-and-forget and works great for telemetry, metrics fan-out, and stateless RPC where losing occasional messages is acceptable. Enable JetStream when you need durability (survive restarts), replay (new consumer catches up on history), at-least-once delivery, or the KV/Object stores. JetStream has minimal overhead when idle, so it is safe to enable globally and opt-in per stream.
How do I scale NATS horizontally?
Add more cluster nodes. NATS cluster membership is peer-to-peer -- any client can connect to any node and messages route automatically. For JetStream, scale by sharding streams (create multiple streams with different subjects) or by using mirroring/sourcing to distribute read load. For cross-region, use gateways (full-mesh between clusters) or leaf nodes (hub-and-spoke). A typical production setup runs 3-5 hub nodes and dozens to thousands of leaf nodes.
Can I use NATS as a replacement for Redis?
For pub/sub and simple key-value use cases, yes -- JetStream KV is a capable Redis alternative with built-in replication and watchers. NATS will not replace Redis for sorted sets, geospatial queries, Lua scripting, or sub-millisecond in-memory operations at extreme scale. A common pattern is Redis for hot per-request caching plus NATS KV for cluster-wide configuration, feature flags, and session state. If you want Redis specifically, see our Redis install guide.
How do I integrate NATS with my application?
Every major language has a first-class client. For Node.js: npm install nats. For Go: go get github.com/nats-io/nats.go. For Python: pip install nats-py. For Rust: cargo add async-nats. Java, .NET, Ruby, PHP, Elixir, Deno, and WebSocket browser clients are also supported. The official docs at docs.nats.io have example code for every client. All clients share the same mental model: connect to nats://host:4222, then publish, subscribe, request, or open JetStream/KV/Object contexts.
Is NATS production-ready for financial or critical workloads?
Yes. NATS is used in production by Mastercard for payments, by several defense contractors for command and control, and by IoT platforms handling billions of daily messages. With JetStream replication across 3 or 5 nodes, TLS, NKEY/JWT auth, and accounts for multi-tenancy, it meets the requirements of regulated industries. Pair it with off-site JetStream mirrors for disaster recovery and you have a resilient message backbone.
Next Steps
Now that NATS is running on your VPS, here is what to build next:
- Add a dashboard with NATS Surveyor -- nats-surveyor exposes Prometheus metrics that plug straight into Grafana. Get per-account, per-subject, and JetStream-level visibility in minutes.
- Monitor with Uptime Kuma -- Point Uptime Kuma at your
:8222/healthzendpoint for uptime alerts on Slack, email, or Telegram. - Bridge MQTT for IoT -- NATS speaks MQTT natively. Enable the
mqtt {}block innats.confto accept connections from ESP32, mosquitto clients, and industrial PLCs. - Add a WebSocket listener -- Enable
websocket {}on port 443 so browser clients can connect directly to NATS without an intermediary. - Mirror streams off-site -- Configure a stream mirror on a second cluster in a different region for disaster recovery.
- Pair with Redis, Kafka, or RabbitMQ -- NATS plays well with other infrastructure. See the Redis, Kafka, and RabbitMQ install guides.
- Read the official docs -- The NATS documentation is outstanding; deep-dive on gateways, super-clusters, and resource limits when you are ready.
Need a reliable, affordable VPS for NATS?>
The CloudCore Starter plan is the perfect starting point for a single NATS node or a 3-node cluster. Fast NVMe storage, unmetered bandwidth, and a clean Ubuntu 24.04 install mean you can be running production NATS in under 15 minutes.>
Browse VPS Plans