certbot
.# nats.conf
websocket
{
port: 8080
no_tls: true
}
nats-server -c nats.conf
The main idea is to switch the location depending on the Upgrade header.
server {
# ...
location / {
try_files /nonexistent @$http_upgrade;
}
location @websocket {
# websocket related stuff
}
location @ {
# web related stuff
}
}
Here is the complete Nginx config of my webiste. Most of the rules are written
by certbot
. The only part that I added to enable websocket is in the location @websocket{...}
section.
# /etc/nginx/sites-available/rollingweddingpictures.conf
server {
#listen 80;
server_name rollingweddingpictures.com;
location / {
try_files /nonexistent @$http_upgrade;
}
location @websocket {
# websocket related stuff
proxy_pass http://localhost:8080; # The URL of your WebSocket server
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location @ {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://localhost:3055;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/rollingweddingpictures.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/rollingweddingpictures.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = rollingweddingpictures.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
server_name rollingweddingpictures.com;
listen 80;
return 404; # managed by Certbot
}
nginx -t
systemctl reload nginx
// RUN: node wss-listen.mjs
// Shim the websocket. Otherwise, it will complain ReferenceError: WebSocket is not defined.
import w3c from "websocket";
globalThis.WebSocket = w3c.w3cwebsocket;
import { connect } from "nats.ws";
const servers = [
{ servers: ["wss://rollingweddingpictures.com"] },
];
servers.forEach(async (v) => {
try {
const nc = await connect(v);
console.log(`Connected to ${nc.getServer()}`);
// ==================== Listen to a subject ====================
// Example: Subscribe to a subject
const sub = nc.subscribe("my.subject");
(async () => {
for await (const m of sub) {
console.log(`Received message: ${m.string()}`);
}
})();
// =============================================================
// Don't close connection. Listen forever.
} catch (_err) {
console.log(`error connecting to ${JSON.stringify(v)}`);
}
});
node wss-listen.mjs
// RUN: node wss-post.mjs
// Shim the websocket. Otherwise, it will complain ReferenceError: WebSocket is not defined.
import w3c from "websocket";
globalThis.WebSocket = w3c.w3cwebsocket;
import { connect } from "nats.ws";
const servers = [
{ servers: ["wss://rollingweddingpictures.com"]},
];
servers.forEach(async (v) => {
try {
const nc = await connect(v);
console.log(`Connected to ${nc.getServer()}`);
// ================== do something with the connection ==================
// Example: Publish a message
const dateTime = new Date().toLocaleTimeString();
const subject = "my.subject";
const msg = `${dateTime}: ${subject}: Client WSS says: Hello NATS!`;
nc.publish(subject, msg);
console.log("Published", msg);
// ======================================================================
// Don't use nc.close(), use nc.drain() instead.
// If nc.close() is called immediately, any messages that have been published
// but not yet sent over the network or acknowledged by the server might be lost.
// Draining allows the client to send all queued messages and ensures the server has processed them.
await nc.drain();
} catch (_err) {
console.log(`error connecting to ${JSON.stringify(v)}`);
}
});
node wss-post.mjs
I gave up. There are too many problems:
certbot
's certificate and key files: fullchain.pem
and privkey.pem
.
chmod -R 0777 certbot/*.pem
websocket {
port: 443
# TLS configuration is required by default
#
tls {
cert_file: "/mnt/.../certbot/fullchain.pem"
key_file: "/mnt/.../certbot/privkey.pem"
}
}
[FTL] Unable to listen for websocket connections: listen tcp 0.0.0.0:443: bind: permission denied
I found there might not be needed to enable TLS on the Nats server itself. Instead, let Nginx handle that part. It is safe enough.
NATS generally doesn't require binding to port 443 (HTTPS) directly. NATS primarily uses port 4222 for its core messaging protocol and 6222 for cluster communication by default. If you need to use NATS with HTTPS, it's common to use a proxy server like a web server (e.g., Apache or Nginx) configured to handle TLS encryption on port 443 and then forward traffic to NATS on its designated ports. This approach separates the concerns of secure web traffic and NATS messaging.