Skip to main content

Overview

NATS Server provides native WebSocket connectivity, enabling web browsers and other WebSocket clients to communicate using the NATS protocol. This makes it possible to build real-time web applications with direct NATS messaging from the browser.
WebSocket support is built directly into NATS Server with no additional proxy or gateway required.

Why WebSocket?

WebSocket connectivity unlocks NATS for web applications:
  • Browser Support: Native NATS messaging directly from JavaScript
  • Real-Time Web Apps: Build interactive dashboards, chat, notifications
  • No HTTP Polling: Persistent bidirectional connection
  • Single Port: WebSocket and HTTP monitoring can share a port
  • TLS Support: Secure WebSocket (WSS) with TLS certificates
  • Per-Message Compression: Optional compression for bandwidth reduction

WebSocket Protocol

NATS implements the RFC 6455 WebSocket Protocol with extensions:
  • Binary and Text Frames: Support for both message types
  • Compression: Optional per-message deflate compression (RFC 7692)
  • Masking: Configurable frame masking for clients
  • Control Frames: PING/PONG for keepalive, CLOSE for clean shutdown

Frame Types

Implemented WebSocket opcodes (websocket.go:41-49):
Frame Types
wsTextMessage   = 0x1  // UTF-8 text data
wsBinaryMessage = 0x2  // Binary data
wsCloseMessage  = 0x8  // Connection close
wsPingMessage   = 0x9  // Keepalive ping
wsPongMessage   = 0xA  // Keepalive pong

WebSocket Extensions

Per-Message Compression (websocket.go:89-93):
Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover
Compression is negotiated during the WebSocket handshake and applied per-frame.

Configuration

Basic WebSocket Setup

Configure WebSocket by specifying a port:
websocket.conf
websocket {
    port: 8080
    no_tls: true
    
    # Optionally bind to specific host
    # host: "0.0.0.0"
    
    # Enable compression (optional)
    compression: true
}
Clients connect to: ws://localhost:8080

WebSocket with TLS

Secure WebSocket (WSS) requires TLS configuration:
websocket-tls.conf
websocket {
    port: 443
    
    tls {
        cert_file: "/path/to/server-cert.pem"
        key_file: "/path/to/server-key.pem"
        # Optional CA for client cert verification
        ca_file: "/path/to/ca.pem"
        verify: true
    }
    
    # Compression recommended for WAN clients
    compression: true
}
Clients connect to: wss://yourserver.com
Always use WSS (TLS) in production. Unencrypted WebSocket connections expose credentials and message content.

Shared Port with HTTP Monitoring

WebSocket can share a port with HTTP monitoring:
shared-port.conf
http_port: 8222

websocket {
    port: 8222
    no_tls: true
    
    # Same port serves both /varz and WebSocket
}
The server automatically detects WebSocket upgrade requests on the HTTP port.

Advanced Configuration

websocket-advanced.conf
websocket {
    port: 8080
    
    # Listen address
    host: "0.0.0.0"
    
    # Compression settings
    compression: true
    
    # TLS configuration
    tls {
        cert_file: "/path/to/cert.pem"
        key_file: "/path/to/key.pem"
        timeout: 2.0  # TLS handshake timeout
    }
    
    # Authentication
    # Inherits from server auth configuration
    # Can be overridden per WebSocket connection
    
    # CORS and origin restrictions
    same_origin: false
    allowed_origins: [
        "https://app.example.com",
        "https://dashboard.example.com"
    ]
    
    # Handshake timeout
    handshake_timeout: 2s
    
    # No masking (server to client)
    # Server responses can skip masking for performance
    no_masking: true
}
port
int
required
WebSocket listener port
host
string
default:"0.0.0.0"
Listen address for WebSocket connections
compression
bool
default:"false"
Enable per-message deflate compression
same_origin
bool
default:"false"
Require connections from same origin as server
allowed_origins
array
List of allowed origins for CORS
handshake_timeout
duration
default:"2s"
Maximum time to complete WebSocket handshake
no_masking
bool
default:"false"
Disable masking for server-to-client frames (performance optimization)

Browser Clients

JavaScript/TypeScript

Connect from the browser using the NATS WebSocket client:
Browser Example
import { connect } from '@nats-io/nats.ws';

// Connect to NATS via WebSocket
const nc = await connect({
    servers: ['ws://localhost:8080'],
    // Or secure: ['wss://nats.example.com']
});

// Subscribe to messages
const sub = nc.subscribe('updates');
(async () => {
    for await (const msg of sub) {
        console.log('Received:', new TextDecoder().decode(msg.data));
    }
})();

// Publish messages
nc.publish('events', new TextEncoder().encode('Hello from browser!'));

// Request-reply
const response = await nc.request(
    'api.user.info',
    new TextEncoder().encode('user123'),
    { timeout: 1000 }
);

React Application

React Hook
import { useEffect, useState } from 'react';
import { connect, NatsConnection } from '@nats-io/nats.ws';

function useNATS(url: string) {
    const [nc, setNc] = useState<NatsConnection | null>(null);
    
    useEffect(() => {
        let connection: NatsConnection;
        
        (async () => {
            connection = await connect({ servers: [url] });
            setNc(connection);
        })();
        
        return () => {
            connection?.close();
        };
    }, [url]);
    
    return nc;
}

function Dashboard() {
    const nc = useNATS('ws://localhost:8080');
    const [messages, setMessages] = useState<string[]>([]);
    
    useEffect(() => {
        if (!nc) return;
        
        const sub = nc.subscribe('dashboard.>');
        (async () => {
            for await (const msg of sub) {
                const text = new TextDecoder().decode(msg.data);
                setMessages(prev => [...prev, text]);
            }
        })();
        
        return () => sub.unsubscribe();
    }, [nc]);
    
    return (
        <div>
            {messages.map((msg, i) => <div key={i}>{msg}</div>)}
        </div>
    );
}

Web Client Support

Authentication

WebSocket clients support all NATS authentication methods:
Authentication
// Token authentication
await connect({
    servers: ['ws://localhost:8080'],
    token: 'your-secret-token'
});

// Username/password
await connect({
    servers: ['ws://localhost:8080'],
    user: 'webapp',
    pass: 'secret'
});

// JWT/NKey authentication
await connect({
    servers: ['ws://localhost:8080'],
    authenticator: jwtAuthenticator(jwt, seed)
});

// From cookie (set by server)
await connect({
    servers: ['ws://localhost:8080']
    // Server can set auth via cookie
});
NATS supports passing authentication via cookies (websocket.go:119-123):
Cookie Auth
// Server sets cookies in HTTP response:
// - jwt: JWT token
// - username/password: Basic auth
// - token: Token auth

// Client connects without explicit credentials
const nc = await connect({
    servers: ['ws://localhost:8080']
});
// Credentials automatically extracted from cookies

Compression

Enable compression for bandwidth-constrained clients:
Compression
const nc = await connect({
    servers: ['ws://localhost:8080'],
    // Compression negotiated automatically if server supports it
});
Compression is applied when message size exceeds threshold (websocket.go:62).

MQTT over WebSocket

MQTT clients can connect via WebSocket using the /mqtt path:
MQTT WebSocket
import Paho from 'paho-mqtt';

const client = new Paho.Client(
    'ws://localhost:8080/mqtt',  // Note: /mqtt path
    'client-' + Math.random()
);

client.connect({
    onSuccess: () => {
        console.log('MQTT connected via WebSocket');
        client.subscribe('sensors/#');
    }
});
See MQTT for more details on MQTT support (mqtt.go:191).

Implementation Details

WebSocket Handshake

The server performs standard WebSocket upgrade (websocket.go:102-103):
  1. Client sends HTTP Upgrade request with Sec-WebSocket-Key
  2. Server validates and computes accept hash using GUID
  3. Server responds with 101 Switching Protocols
  4. Connection upgraded to WebSocket protocol

Frame Processing

WebSocket frames are processed efficiently (websocket.go:125-193):
  • Masking: Client-to-server frames must be masked per RFC 6455
  • Fragmentation: Large messages can be fragmented across frames
  • Control Frames: PING/PONG handled automatically
  • Browser Optimization: Frame size limited to 4KB for better browser performance (websocket.go:61)

Connection Detection

The server detects WebSocket clients via the ws field (websocket.go:197-199):
Client Detection
func (c *client) isWebsocket() bool {
    return c.ws != nil
}

Performance Optimization

Disable Masking

Use no_masking: true to skip server-to-client masking for better performance (websocket.go:85-86).

Compression Threshold

Compression only applied for messages larger than 64 bytes (websocket.go:62).

Frame Size

Server uses 4KB frames for optimal browser performance (websocket.go:61).

Connection Pooling

Reuse WebSocket connections for multiple subscriptions to reduce overhead.

Security

TLS Best Practices

1

Always Use WSS in Production

Unencrypted WebSocket exposes all traffic including credentials.
2

Verify Certificates

Set verify: true in TLS configuration to validate client certificates.
3

Restrict Origins

Configure allowed_origins to prevent unauthorized domains from connecting.
4

Use Strong Authentication

Prefer JWT/NKey authentication over username/password for web clients.

Origin Restrictions

Origin Control
websocket {
    port: 443
    
    # Only allow specific domains
    allowed_origins: [
        "https://app.example.com",
        "https://dashboard.example.com"
    ]
    
    # Or require same origin
    same_origin: true
}

X-Forwarded-For

The server respects X-Forwarded-For headers for client IP tracking (websocket.go:87):
X-Forwarded-For: 203.0.113.195, 198.51.100.178
Useful when behind a reverse proxy or load balancer.

Monitoring

Monitor WebSocket connections via /connz:
WebSocket Monitoring
curl http://localhost:8222/connz | jq '.connections[] | select(.kind == "Websocket")'
Check WebSocket configuration in /varz:
Server Info
curl http://localhost:8222/varz | jq '.websocket'

Use Cases

Real-Time Dashboard

Dashboard Example
import { connect } from '@nats-io/nats.ws';

const nc = await connect({ servers: ['wss://nats.example.com'] });

// Subscribe to metrics
const sub = nc.subscribe('metrics.*');
for await (const msg of sub) {
    const metric = JSON.parse(new TextDecoder().decode(msg.data));
    updateChart(metric);
}

Chat Application

Chat Example
const nc = await connect({ servers: ['ws://localhost:8080'] });

// Join room
const room = 'chat.room.general';

// Receive messages
const sub = nc.subscribe(room);
for await (const msg of sub) {
    displayMessage(msg);
}

// Send messages
function sendMessage(text: string) {
    nc.publish(room, new TextEncoder().encode(JSON.stringify({
        user: currentUser,
        text: text,
        timestamp: Date.now()
    })));
}

Live Notifications

Notifications
const nc = await connect({ servers: ['wss://nats.example.com'] });

// Subscribe to user-specific notifications
const userSub = nc.subscribe(`notifications.${userId}`);
for await (const msg of userSub) {
    const notification = JSON.parse(new TextDecoder().decode(msg.data));
    showNotification(notification);
}

Troubleshooting

Check that WebSocket port is configured and server is listening:
netstat -an | grep 8080
Ensure client origin is in allowed_origins or disable origin checking in development.
Verify certificate paths and ensure certificates are valid. Check server logs for TLS errors.
Both client and server must support and negotiate permessage-deflate extension.

Next Steps

Client SDKs

Explore NATS WebSocket client libraries

Security

Configure authentication and TLS

MQTT

MQTT clients can also use WebSocket

Monitoring

Monitor WebSocket connections

Build docs developers (and LLMs) love