Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Nats Ubuntu
GUIDEInstall Guides

How to Install NATS on Ubuntu 24.04 VPS: High-Performance Messaging for Microservices

28 min read

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?
  • Why Self-Host NATS on Your VPS?
  • NATS vs Kafka vs RabbitMQ
  • Prerequisites
  • Step 1: Update System Packages
  • Step 2: Install the NATS Server Binary
  • Step 3: Create a systemd Service
  • Step 4: Configure nats.conf with JetStream
  • Step 5: Install the NATS CLI
  • Step 6: Pub/Sub and Req/Rep Basics
  • Step 7: Streams and Consumers with JetStream
  • Step 8: Key-Value and Object Store
  • Step 9: NKEY and JWT Authentication
  • Step 10: Enable TLS
  • Step 11: Build a 3-Node Cluster
  • Step 12: Leaf Nodes for Edge Deployments
  • Performance Tuning
  • Troubleshooting
  • FAQ
  • Next Steps
  • 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.
    For most teams running 1-20 microservices or handling up to tens of thousands of messages per second, a single self-hosted NATS node on a EUR 6-10/month VPS is dramatically cheaper and simpler than managed alternatives.

    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.

    FeatureNATS (with JetStream)Apache KafkaRabbitMQ
    Primary modelSubject-based pub/sub + streamsPartitioned logBroker with exchanges/queues
    Typical latency50-500 microseconds2-10 ms1-5 ms
    Binary size~22 MB, single binary~400 MB + JVM + ZK/KRaftErlang VM + several packages
    External depsNoneJVM, historically ZooKeeperErlang/OTP
    PersistenceJetStream (optional)Always (log-structured)Optional per queue
    Request/replyFirst-classManual (two topics)Via RPC pattern
    Delivery guaranteesAt-most, at-least, exactly onceAt-least, exactly onceAt-least once
    KV store includedYes (JetStream KV)Via Kafka Streams/KTablesNo
    Object store includedYes (JetStream Object)NoNo
    ProtocolPlain text, ~30 verbsBinaryAMQP 0-9-1 (complex)
    Edge / IoT storyLeaf nodes, MQTT bridgeWeakMQTT plugin
    Multi-tenancyAccounts (native)Via ACLs and namingVhosts
    Operational complexityLowHighMedium
    Best forMicroservices, IoT, edge, cloud-nativeHigh-throughput log, stream processingTraditional enterprise queueing
    Choose NATS when you want blistering speed, dead-simple operations, native request/reply, and a unified messaging + KV + Object store stack. It is the sweet spot for most microservice deployments.

    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:

    bash
    ssh root@your-server-ip

    Step 1: Update System Packages

    Start with a fresh package index and security updates.

    bash
    sudo apt update && sudo apt upgrade -y

    Install the handful of utilities we will use in the rest of this guide:

    bash
    sudo apt install -y curl wget unzip ufw ca-certificates

    If the kernel was upgraded, reboot:

    bash
    sudo reboot

    Reconnect 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):

    bash
    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-server

    Verify the install:

    bash
    nats-server --version

    Expected output:

    text
    nats-server: v2.10.22

    Create a dedicated system user so NATS does not run as root:

    bash
    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/nats

    Step 3: Create a systemd Service

    Create a unit file so NATS starts automatically on boot and restarts on failure.

    bash
    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:

    bash
    sudo systemctl daemon-reload

    Do 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.

    bash
    sudo tee /etc/nats/nats.conf > /dev/null <<'EOF'
    

    Server identity

    server_name: "nats-01" listen: 0.0.0.0:4222

    HTTP monitoring endpoint

    http: 0.0.0.0:8222

    Logging

    log_file: "/var/log/nats/nats-server.log" logtime: true debug: false trace: false

    Client 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 } EOF

    Fix permissions so the nats user can read the config and write the store:

    bash
    sudo chown -R nats:nats /etc/nats /var/lib/nats
    sudo chmod 640 /etc/nats/nats.conf

    Open firewall ports:

    bash
    sudo ufw allow 4222/tcp comment 'NATS client'
    sudo ufw allow 8222/tcp comment 'NATS monitoring'
    sudo ufw --force enable

    Start NATS and enable it on boot:

    bash
    sudo systemctl enable --now nats
    sudo systemctl status nats

    Expected output (abbreviated):

    text
    ● 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:

    bash
    curl http://localhost:8222/varz | head -40

    You 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.

    bash
    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/nats

    Verify:

    bash
    nats --version

    Create a default context so you do not have to pass -s on every command:

    bash
    nats context save default --server=nats://127.0.0.1:4222 --select

    Quick health check:

    bash
    nats server check connection

    Expected output:

    text
    OK Connection OK: connected to nats://127.0.0.1:4222 in 412.8µs

    Step 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:

    bash
    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:

    bash
    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:

    bash
    nats reply "time.now" --command "date -u +%FT%TZ"

    In terminal B:

    bash
    nats request "time.now" ""

    Expected output:

    text
    10:42:17 Sending request on "time.now"
    10:42:17 Received with rtt 612.5µs
    2026-04-16T10:42:17Z

    Queue Groups (Load Balancing)

    Run two subscribers in the same queue group to distribute work across them. Terminal A:

    bash
    nats sub "jobs" --queue workers

    Terminal B (second subscriber, same queue):

    bash
    nats sub "jobs" --queue workers

    Terminal C (publisher):

    bash
    for i in $(seq 1 10); do nats pub jobs "job-$i"; done

    Each 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

    bash
    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 \
      --defaults

    Inspect it:

    bash
    nats stream info ORDERS

    Expected output (abbreviated):

    text
    Information for Stream ORDERS

    Configuration: 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:

    bash
    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

    bash
    nats stream view ORDERS

    You 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):

    bash
    nats consumer add ORDERS order-processor \
      --pull \
      --deliver all \
      --ack explicit \
      --max-deliver 5 \
      --wait 30s \
      --filter "orders.>" \
      --replay instant \
      --defaults

    Consume:

    bash
    nats consumer next ORDERS order-processor --count 10

    Each 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):

    bash
    nats consumer add ORDERS billing-service \
      --target billing.incoming \
      --deliver new \
      --ack explicit \
      --defaults

    Then 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:

    bash
    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:

    bash
    nats kv add config --history 5 --ttl 24h --replicas 1

    Put, get, and watch keys:

    bash
    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 config

    Live-watch changes (great for hot-reloading configuration in services):

    bash
    nats kv watch config

    In another terminal:

    bash
    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:

    bash
    nats object add artifacts --replicas 1

    Upload and list:

    bash
    echo "Build output from CI" > /tmp/build.log
    nats object put artifacts /tmp/build.log --name=build-4711.log
    nats object ls artifacts

    Download:

    bash
    nats object get artifacts build-4711.log --output=/tmp/restored.log

    The 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:

    bash
    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/nk

    Generate a user keypair:

    bash
    nk -gen user -pubout

    Output:

    text
    SUAEL6GG2L2HIF7DUGZJGMRUFKXELGGYFMHF2H6RY2CHKLMVS5H6O7XU3I   # seed (private)
    UDXU4RCSJNZOKIYLCNGWMOL6GIXELBAAZCQEKOAS6OJUVRZWTW6CSYFU     # public

    Save the seed to a local file (keep it secret):

    bash
    echo "SUAEL6GG2L2HIF7DUGZJGMRUFKXELGGYFMHF2H6RY2CHKLMVS5H6O7XU3I" > ~/user.seed
    chmod 600 ~/user.seed

    Add the public key to nats.conf:

    bash
    sudo tee -a /etc/nats/nats.conf > /dev/null <<'EOF'

    authorization { users: [ { nkey: "UDXU4RCSJNZOKIYLCNGWMOL6GIXELBAAZCQEKOAS6OJUVRZWTW6CSYFU" } ] } EOF

    sudo systemctl reload nats

    Connect with the seed:

    bash
    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:

    bash
    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-orders

    Then 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):

    bash
    sudo apt install -y certbot
    sudo certbot certonly --standalone -d nats.yourdomain.com

    Grant the nats user read access to the certs:

    bash
    sudo setfacl -R -m u:nats:rX /etc/letsencrypt/live /etc/letsencrypt/archive

    Update nats.conf to enable TLS:

    bash
    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:

    bash
    nats --tlsca=/etc/letsencrypt/live/nats.yourdomain.com/fullchain.pem \
         --server=tls://nats.yourdomain.com:4222 \
         server check connection

    For 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):

    Nodeserver_nameIP
    Node 1nats-0110.0.0.11
    Node 2nats-0210.0.0.12
    Node 3nats-0310.0.0.13
    On each node, append a cluster block to /etc/nats/nats.conf:

    bash
    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:

    bash
    sudo ufw allow 6222/tcp comment 'NATS cluster'
    sudo systemctl restart nats

    Verify the cluster formed:

    bash
    nats server list

    Expected output:

    text
    ╭──────────────────────────────────────────────────────────────────────────────╮
    │                              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:

    bash
    nats stream add ORDERS-HA \
      --subjects "orders.>" \
      --storage file \
      --retention limits \
      --max-age 30d \
      --replicas 3 \
      --defaults

    Check stream placement:

    bash
    nats stream info ORDERS-HA

    You 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):

    bash
    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:

    bash
    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:8222

    jetstream { 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:

    bash
    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 --system

    JetStream Storage

    • Put store_dir on NVMe SSD (CloudCore Starter already uses NVMe). Mechanical drives cripple JetStream throughput.
    • Size max_file_store to about 70% of your available disk -- JetStream writes compacted blocks and needs overhead for compaction.
    • Tune max_memory_store based on your hottest short-lived streams. Memory-only streams are blazingly fast but lost on restart.

    Benchmark Your Node

    bash
    nats bench foo --pub 2 --sub 2 --msgs 1000000 --size 128

    Typical result on a CloudCore Starter (4 vCPU, 6 GB RAM):

    text
    Pub stats: 4,123,456 msgs/sec ~ 503.37 MB/sec
    Sub stats: 8,246,912 msgs/sec ~ 1006.74 MB/sec

    For JetStream benchmark:

    bash
    nats bench foo --js --pub 2 --sub 2 --msgs 200000 --size 512 --stream=BENCH

    Troubleshooting

    ProblemCauseSolution
    nats: no servers available for connectionServer not listening on expected addressCheck systemctl status nats and verify listen in nats.conf.
    JetStream not enabled for accountJetStream disabled globally or per-accountConfirm the jetstream {} block is present and restart. For JWT setups, enable JS on the account.
    maximum payload exceededMessage larger than max_payloadIncrease max_payload in config or chunk the payload client-side.
    Cluster not formingPort 6222 blocked or server_name collisionsCheck UFW/iptables on cluster port; ensure unique server_name per node.
    High memory useToo many loaded streams / subscriptionsReview nats stream list, set max_memory_store limits, prune unused streams.
    Slow JetStream writesShared or HDD diskMove store_dir to local NVMe.
    Leaf node disconnectingWAN instability or TLS mismatchCheck nats server report connections; verify cert trust chain on both sides.
    Permission denied writing to store_dirWrong ownershipsudo chown -R nats:nats /var/lib/nats.
    Clients hitting 1024 FD limitsystemd default limitsConfirm LimitNOFILE in the unit file and rerun daemon-reload.

    Viewing Logs

    bash
    sudo journalctl -u nats -f

    Or the dedicated log file:

    bash
    sudo tail -f /var/log/nats/nats-server.log

    Live monitoring:

    bash
    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/healthz endpoint for uptime alerts on Slack, email, or Telegram.
    • Bridge MQTT for IoT -- NATS speaks MQTT natively. Enable the mqtt {} block in nats.conf to 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

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket