Applications Azure

Signal K Server on Ubuntu 24.04 on Azure User Guide

| Product: Signal K Server on Ubuntu 24.04 LTS on Azure

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, and 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 then read that model through a REST API or subscribe to a live WebSocket stream instead of parsing raw instrument sentences themselves.

The cloudimg image installs Signal K Server 2.30.0 on Node.js 22 LTS, runs it as a dedicated unprivileged systemd service, 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. Backed by 24/7 cloudimg support.

What is included:

  • 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 cloudimg 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

  1. An active Azure subscription and an SSH key pair.
  2. A virtual network and subnet in your target region.
  3. 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.
  4. 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 exposing 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 cloudimg, 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 Review + create and Create.

Option B: Azure CLI.

az vm create \
  --resource-group <your-rg> \
  --name signalk \
  --image cloudimg: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:

Signal K Server running as a systemd service, listening on port 3000 for the admin console and API and on port 10110 for NMEA 0183, with the configuration directory mounted on a dedicated Azure 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:

The first boot bootstrap wrote a root only credentials file and a security configuration containing a bcrypt password hash, with anonymous read access and self registration both disabled

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 vessel data API returns 401 to anonymous callers, refuses a wrong password, issues a JSON Web Token for the per VM password, and accepts that token

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 Signal K admin console sign in page, with security enabled out of the box and the server version shown in the footer

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.

The Signal K dashboard showing live throughput, the number of active Signal K paths, connected WebSocket clients and the status of the preconfigured NMEA 0183 connection

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.

The preconfigured NMEA 0183 connection, enabled, taking UDP input on port 10110

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.

A single NMEA 0183 sentence sent to the UDP listener appears in the Signal K data model as decimal degrees, alongside other live instrument paths normalised to SI units

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.

The Signal K data browser showing live vessel data decoded from NMEA 0183: position in decimal degrees, depth, apparent wind and course, each converted to SI units and attributed to the connection it arrived on

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/cloudimg/signalk-bootstrap-ready. It then marks itself complete with /var/lib/cloudimg/signalk-firstboot.done and never runs again.

signalk.service carries ConditionPathExists=/var/lib/cloudimg/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/cloudimg/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. Repeated failures are rate limited by the server; 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

  1. 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>.
  2. 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.
  3. Rotate the administrator password after first sign in, and create individual accounts rather than sharing the admin login.
  4. 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.
  5. 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.
  6. Keep the VM patched. Unattended security upgrades are enabled by default.
  7. Back up the data disk. /var/lib/signalk holds your settings, users and plugins. Use Azure Backup or disk snapshots.

Support

cloudimg provides 24/7 support for this image. Contact support@cloudimg.co.uk.

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