Signal K Server on Ubuntu 24.04 on Azure User Guide
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/signalkholding settings, security configuration, installed plugins and data logs, separate from the OS disk and re provisioned with every VM signalk.serviceandsignalk-firstboot.serviceas 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
- 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/tcpfrom your management network,3000/tcpfor the admin console and API, and10110/udpfrom 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:

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/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
- 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/signalkholds 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.