Deployment guide

Signal K Server

Overview

Signal K Server is the open source hub for marine instrument data. It takes the feeds a vessel already produces: NMEA 0183 over the network or a serial link, NMEA 2000 from the bus, and Signal K from other servers. It converts every sentence into the Signal K data model, a single consistent JSON representation of position, heading, depth, wind, engine and tank readings, expressed in SI units with full source attribution.

Applications read that model through a REST API or subscribe to a live WebSocket stream, instead of parsing raw instrument sentences themselves.

The SecureAnyCloud image installs Signal K Server 2.30.0 on Node.js 22 LTS and runs it as a dedicated unprivileged systemd service. It ships a ready-to-use NMEA 0183 listener on UDP port 10110, keeps all configuration and data on a dedicated Azure data disk, and generates a unique administrator password on the first boot of every VM. The image is backed by 24/7 SecureAnyCloud support.

What you get:

  • Signal K Server 2.30.0 on Node.js 22 LTS, installed globally via npm
  • The web admin console at :3000/admin/ with dashboard, data browser, connection manager and plugin appstore
  • A preconfigured NMEA 0183 input listening on UDP 10110, enabled out of the box
  • Signal K's built-in NMEA 0183 output server on TCP 10110, so chartplotters and apps can read the merged stream back out
  • The REST API at /signalk/v1/api/ and the live WebSocket stream at /signalk/v1/stream
  • Security enabled from first boot: a per-VM administrator password, a per-VM token signing key, and no anonymous access to vessel data
  • A dedicated Azure data disk at /var/lib/signalk holding settings, security configuration, installed plugins and data logs, separate from the OS disk and re-provisioned with every VM
  • signalk.service and signalk-firstboot.service as systemd units, enabled and active
  • 24/7 SecureAnyCloud support

A note on security

Upstream Signal K Server ships with no security configuration at all. A stock server is open to anyone who can reach it until an operator manually creates the first account in the web interface. This image never has that window. On first boot, signalk-firstboot.service generates a unique administrator password and token signing key, writes a complete security.json, and only then creates the marker file that permits signalk.service to start. If that bootstrap does not complete, the server simply does not start. There is no default password to change and no unsecured period to race.

Prerequisites

Before you deploy this image, make sure you have:

  • An active Azure subscription and an SSH key pair.
  • A virtual network and subnet in your target region.
  • Standard_B2s (2 vCPU / 4 GiB RAM) is a good starting point. Scale up if you run many plugins or log high-rate NMEA 2000 data.
  • Network security group inbound rules: 22/tcp from your management network, 3000/tcp for the admin console and API, and 10110/udp from your vessel gateway if you are sending NMEA 0183 over the network.

Port 3000 carries the admin console and the data API. Restrict it to known source addresses, or put it behind a TLS-terminating reverse proxy or VPN before you expose it to the internet. See Security recommendations.

Step 1 - Deploy the virtual machine

Option A: Azure Portal. Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Signal K Server by SecureAnyCloud, and select Create.

On Basics, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) and Custom (3000). Review the dedicated data disk on the Disks tab, then select Review + create and Create.

Option B: Azure CLI.

az vm image terms accept --urn sac1647283583153:signalk-server:default:latest
az vm create \
  --resource-group <your-rg> \
  --name signalk \
  --image sac1647283583153:signalk-server:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --vnet-name <your-vnet> --subnet <your-subnet> \
  --public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name signalk --port 3000 --priority 1010

Step 2 - Connect to your VM

ssh azureuser@<vm-public-ip>

If you need the address, ask Azure for it:

az vm show --resource-group <your-rg> --name signalk -d --query publicIps -o tsv

Step 3 - Confirm the service is running

systemctl is-active signalk.service

The command reports active. Signal K takes a few seconds to start while the Node.js runtime loads the server and its plugins.

For more detail, including uptime and memory use:

systemctl status signalk.service --no-pager | head -12
● signalk.service - Signal K Server (open marine data server) Loaded: loaded (/etc/systemd/system/signalk.service; enabled; preset: enabled) Active: active (running) since Thu 2026-08-06 20:54:34 UTC; 9min ago Docs: https://signalk.org/ Main PID: 3654 (node) Tasks: 11 (limit: 4666) Memory: 315.5M (peak: 348.1M) CPU: 31.249s CGroup: /system.slice/signalk.service └─3654 node /usr/bin/signalk-server

Confirm the listeners and the version, and that the configuration directory is the dedicated data disk:

ss -tuln | grep -E ':3000|:10110'
udp UNCONN 0 0 0.0.0.0:10110 0.0.0.0:* tcp LISTEN 0 511 *:3000 *:* tcp LISTEN 0 511 *:10110 *:*

UDP 10110 is the NMEA 0183 input this image ships enabled. TCP 10110 is Signal K's own NMEA 0183 output server, which re-emits the merged data for chartplotters and other NMEA clients.

Step 4 - Retrieve the per-VM administrator password

Every VM generates its own password on first boot and writes it to a root-only file.

sudo cat /root/signalk-credentials.txt
# Signal K Server - generated on first boot by signalk-firstboot.service
# This password is unique to this VM. Store it somewhere safe. SIGNALK_URL=http://<vm-public-ip>:3000/ SIGNALK_ADMIN_USER=admin SIGNALK_ADMIN_PASSWORD=<unique to your VM> SIGNALK_VESSEL_UUID=urn:mrn:signalk:uuid:7c0d367c-7b48-471b-9326-098046162688
# Admin UI: http://<vm-public-ip>:3000/admin/
# NMEA 0183 input: UDP <vm-public-ip>:10110
# Config dir: /var/lib/signalk (settings.json, security.json, plugins, logs)

The file is 0600 root:root, and the security configuration the bootstrap wrote stores only a bcrypt hash of the password, never the password itself:

Copy the password somewhere safe. Store it in a password manager or Azure Key Vault, then treat the file as a recovery copy.

Step 5 - Confirm the server is secured

The vessel data API rejects anonymous callers, refuses a wrong password, and issues a JSON Web Token for the correct one.

curl -s -o /dev/null -w 'anonymous data API -> HTTP %{http_code}\n' \
  http://127.0.0.1:3000/signalk/v1/api/vessels/self/
anonymous data API -> HTTP 401

Now sign in and use the token:

TOKEN=$(curl -s -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"<SIGNALK_ADMIN_PASSWORD>"}' \
  http://127.0.0.1:3000/signalk/v1/auth/login | jq -r .token)
echo "token length: ${#TOKEN}" curl -s -o /dev/null -w 'authenticated data API -> HTTP %{http_code}\n' \
  -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:3000/signalk/v1/api/vessels/self/
token length: 145 authenticated data API -> HTTP 200

The unauthenticated discovery document is deliberately public. It advertises the server version and endpoints and carries no vessel data:

curl -s http://127.0.0.1:3000/signalk | jq .
{ "endpoints": { "v1": { "version": "2.30.0", "signalk-http": "http://127.0.0.1:3000/signalk/v1/api/", "signalk-ws": "ws://127.0.0.1:3000/signalk/v1/stream" } }, "server": { "id": "signalk-server-node", "version": "2.30.0" } }

Step 6 - Sign in to the admin console

Browse to http://<vm-public-ip>:3000/admin/ and sign in as admin with the password from Step 4.

The dashboard reports live throughput in deltas per second, the number of Signal K paths currently in the data model, connected WebSocket clients, and the status of every connection and plugin.

Step 7 - Send vessel data to the server

Point your vessel gateway, OpenPlotter box or NMEA 0183-to-network bridge at UDP port 10110 on this VM. Nothing needs configuring on the server. The connection is already there and enabled.

To prove the path end to end, send a single position sentence from the VM itself and read the value back out of the data model:

printf '$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n' \
  | socat -u - UDP-DATAGRAM:127.0.0.1:10110 sleep 2
TOKEN=$(curl -s -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"<SIGNALK_ADMIN_PASSWORD>"}' \
  http://127.0.0.1:3000/signalk/v1/auth/login | jq -r .token)
curl -s -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:3000/signalk/v1/api/vessels/self/navigation/position \
  | jq '{value, timestamp, source: ."$source"}'
{ "value": { "latitude": 48.1173, "longitude": 11.516666666666667 }, "timestamp": "2026-08-06T21:03:40.000Z", "source": "nmea0183-udp-10110.GP" }

The sentence carried 4807.038,N and 01131.000,E in degrees and decimal minutes. Signal K decoded it to decimal degrees, stamped it with a timestamp, and attributed it to the connection it arrived on.

Step 8 - Browse the live data model

In the admin console choose Data then Browser. Every path currently in the model is listed with its value, timestamp and source. The search box filters by path, source or PGN, and treats spaces as OR.

Values are shown in SI units with a friendlier conversion beside them, so depth arrives in metres, wind speed in metres per second alongside knots, and angles in radians alongside degrees.

To read the whole model for your own vessel over the API:

TOKEN=$(curl -s -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"<SIGNALK_ADMIN_PASSWORD>"}' \
  http://127.0.0.1:3000/signalk/v1/auth/login | jq -r .token)
curl -s -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:3000/signalk/v1/api/vessels/self/ | jq 'keys'

To subscribe to the live stream instead of polling, connect a WebSocket client to ws://<vm-public-ip>:3000/signalk/v1/stream?subscribe=self.

Step 9 - Install plugins and webapps

Choose Apps & Plugins then Store in the admin console to browse the Signal K appstore. Plugins add features such as anchor alarms, data logging, weather routing and cloud forwarding; webapps add chart plotters and instrument dashboards. Installed packages are written to the data disk at /var/lib/signalk/node_modules, so they survive VM resizes and are captured in your own snapshots.

The image already includes the webapps that ship with the server, including Freeboard SK and KIP, reachable from the Webapps menu.

Server components

Component Version Purpose

Signal K Server 2.30.0 Marine data server, REST and WebSocket API, admin console

Node.js 22 LTS JavaScript runtime

tokensecurity built-in JSON Web Token authentication with bcrypt password hashing

NMEA 0183 input built-in UDP listener on port 10110

NMEA 0183 output built-in TCP server on port 10110

Filesystem layout

Path Size Purpose

/ 29 GB Root filesystem

/var/lib/signalk 20 GB Dedicated Azure data disk: Signal K configuration directory

/mnt varies Azure temporary resource disk

Key files and directories:

Path Purpose

/var/lib/signalk/settings.json Server settings: port, vessel identity, connections

/var/lib/signalk/security.json Users, bcrypt hashes and the token signing key, 0600 signalk:signalk

/var/lib/signalk/node_modules Plugins and webapps installed from the appstore

/var/lib/signalk/plugin-config-data Per-plugin configuration

/root/signalk-credentials.txt The per-VM administrator password, 0600 root:root

/etc/systemd/system/signalk.service The server unit

/etc/systemd/system/signalk-firstboot.service The first-boot bootstrap unit

/usr/local/sbin/signalk-firstboot.sh The first-boot bootstrap script

Managing the service

systemctl status signalk.service --no-pager | head -5

To restart, stop or start the server:

sudo systemctl restart signalk.service
sudo systemctl stop signalk.service
sudo systemctl start signalk.service

Follow the server log:

sudo journalctl -u signalk.service -f

The admin console also exposes the log under Server then Server Logs, and a Restart button in the header.

Changing the administrator password

Use the admin console: Security then Users, select admin and set a new password. You can also add further users there, each with an admin, read/write or read-only role. After changing the password, update or delete /root/signalk-credentials.txt so the stale value is not mistaken for the current one.

On startup

signalk-firstboot.service runs once, on the first boot of a new VM. It generates the administrator password and the token signing key, writes /var/lib/signalk/security.json and /root/signalk-credentials.txt, stamps a unique vessel UUID into settings.json, and creates /var/lib/sac/data/signalk-bootstrap-ready. It then marks itself complete with /var/lib/sac/data/signalk-firstboot.done and never runs again.

signalk.service carries ConditionPathExists=/var/lib/sac/data/signalk-bootstrap-ready, so it starts only after that bootstrap has succeeded. This is what guarantees the server is never reachable without authentication.

Troubleshooting

The admin console does not load. Confirm the service is running and the port is open:

systemctl is-active signalk.service

If it reports inactive, check whether the bootstrap marker exists. If it is missing, the first-boot bootstrap did not complete:

test -f /var/lib/sac/data/signalk-bootstrap-ready && echo "bootstrap ok" || echo "bootstrap did NOT complete"
bootstrap ok

Then inspect the bootstrap log:

sudo journalctl -u signalk-firstboot.service --no-pager

If the service is active but the console is unreachable from your workstation, the network security group is the usual cause. Confirm the rule allows 3000/tcp from your address.

I cannot sign in. Re-read the password with sudo cat /root/signalk-credentials.txt. The value is unique to this VM, so a password from another instance or from documentation will not work. The server rate-limits repeated failures, so wait a minute and retry.

No data appears in the data browser. Confirm sentences are arriving. Send a test sentence locally as in Step 7. If the local test works but your gateway's data does not appear, the traffic is not reaching the VM: check that the network security group allows 10110/udp from the gateway's address and that the gateway is sending to the VM's public or private address on that port.

Depth or wind is missing but position works. Signal K only publishes paths it actually receives. Confirm your instruments are emitting the relevant sentences, for example DBT or DPT for depth and MWV for apparent wind.

Security recommendations

  • Restrict port 3000. The admin console and the data API share it. Limit the network security group to known source addresses, or reach it over a VPN or an SSH tunnel: ssh -L 3000:127.0.0.1:3000 azureuser@<vm-public-ip>.
  • Terminate TLS in front of the server. For internet exposure, put a reverse proxy such as nginx or Caddy in front with a certificate, or use Azure Application Gateway. Signal K can also be configured for TLS directly under Server then Settings.
  • Rotate the administrator password after first sign in, and create individual accounts rather than sharing the admin login.
  • Keep anonymous read access off unless you need it. This image ships with it disabled. You can enable read-only public access under Security then Settings if you want to publish data openly.
  • Restrict the NMEA input. UDP 10110 accepts data from anything that can reach it. Limit the network security group to your vessel gateway's address.
  • Keep the VM patched. Unattended security upgrades are enabled by default.
  • Back up the data disk. /var/lib/signalk holds your settings, users and plugins. Use Azure Backup or disk snapshots.

Support

SecureAnyCloud provides 24/7 support for this image. Contact sales@secureanycloud.com.

For Signal K itself, see the Signal K documentation and the Signal K server repository.

Deploy on Azure

Launch Signal K Server on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure