Deployment guide

FROST-Server

Overview

FROST-Server is the Fraunhofer IOSB reference implementation of the OGC SensorThings API, the open standard for managing and sharing Internet of Things sensor data. It models the world as Things that carry Sensors, each feeding a Datastream of Observations of an ObservedProperty at a Location. It exposes that model over a REST interface that clients query, filter, page and expand with ordinary HTTP requests.

This guide covers deploying and using FROST-Server 2.8 on Ubuntu 24.04 on Azure from the SecureAnyCloud Azure Marketplace image. The image persists every observation in PostgreSQL with the PostGIS spatial extension, so geospatial filtering and long term historical storage come from a database engine built for the job. Because FROST-Server implements a published OGC standard rather than a proprietary API, data recorded through it is portable across SensorThings clients and tools.

Security by design. The upstream default for FROST-Server is an open, unauthenticated read and write API. On a public address, that lets anyone inject or delete your sensor data. This image ships with FROST's Basic authentication plugin enabled, so reads stay public but every write requires an administrator credential.

That credential and the PostgreSQL password are generated uniquely on each VM at first boot and written to a root only file. The captured image contains no database password, no service credential and no TLS private key. PostgreSQL listens only on the loopback interface. The servlet container also listens only on loopback, behind an nginx reverse proxy that forwards an explicit allowlist of paths. The base is patched at build time, with unattended security upgrades enabled.

What you get:

  • FROST-Server 2.8.0 HTTP service (the SensorThings REST/data service WAR from Maven Central, checksum pinned), running in Apache Tomcat 10 on OpenJDK 21.
  • PostgreSQL 16 with the PostGIS 3 extension, hosting the SensorThings data on the OS disk. It listens on loopback only, with scram-sha-256 passwords and no trust rule anywhere.
  • FROST's BasicAuthProvider, enabled with anonymous reads allowed and per-VM administrator credentials. The three default read/write/admin users that FROST would otherwise create are removed.
  • nginx as an allowlisting reverse proxy on port 80 (public API) and port 443 (per-VM TLS, so administrator writes can be sent encrypted).
  • A small sample dataset, ready to query from first boot: a weather station Thing with a Location, a Sensor, an ObservedProperty, a Datastream and several temperature Observations.
  • A root only credentials file at /root/frost-server-credentials.txt. The first boot service frost-server-firstboot.service gates the whole stack, so it never serves before the per-VM secrets exist.

Prerequisites

  • An Azure subscription and the Azure CLI (az) signed in, or the Azure Portal.
  • Permission to create a resource group, a virtual machine and a public IP.
  • An SSH key pair for administrative access to the VM.
  • The recommended size is Standard_B2s (2 vCPU / 4 GiB). The JVM heap and PostgreSQL buffers are sized automatically from the memory of whatever size you launch.

Deploy the virtual machine

Create the VM from the image, then open SSH and HTTP, and optionally HTTPS, to your own network:

# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:frost-server:default:latest
az vm create \
  --resource-group my-rg \
  --name frost-1 \
  --image sac1647283583153:frost-server:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard
az vm open-port --resource-group my-rg --name frost-1 --port 80 --priority 900
az vm open-port --resource-group my-rg --name frost-1 --port 443 --priority 910

First boot takes a little longer than a plain VM. Before the API is allowed to serve, the VM mints its PostgreSQL and administrator credentials, sets the SensorThings service root URL to its own address and writes its TLS certificate. The stack is gated, so it never answers on an uninitialised database.

Retrieve your per VM credentials

On first boot, the VM generates its own PostgreSQL password and FROST administrator password. Connect over SSH and read the root only credentials file:

sudo cat /root/frost-server-credentials.txt

The file is mode 0600, owned by root, and lists the public SensorThings URL, the administrator user and password used for writes, the PostgreSQL role and password, and the TLS certificate fingerprint. Nothing is baked into the image, so every deployed VM has its own credentials.

Confirm the service is healthy

Connect to the VM over SSH, check the services and query the SensorThings service root over the loopback interface. Reads are public, so no credential is needed:

systemctl is-active postgresql@16-main tomcat10 nginx curl -s http://localhost/FROST-Server/v1.1/ | head -c 400; echo

The service root returns a JSON document. Its value array lists every entity set the API exposes: Things, Locations, Datastreams, Sensors, ObservedProperties, Observations, FeaturesOfInterest and HistoricalLocations.

From a client off the VM, use your VM's own address in place of localhost, for example http://<vm-ip>/FROST-Server/v1.1/ once port 80 is open.

Explore the sample dataset

The image ships with one weather station already recorded. Read the Thing and expand its related entities in a single request:

curl -s "http://localhost/FROST-Server/v1.1/Things?\$expand=Locations,Datastreams(\$expand=Sensor,ObservedProperty)" | head -c 700; echo

Query observations

Retrieve the observations of the sample datastream, and select just the timestamp and the result:

curl -s "http://localhost/FROST-Server/v1.1/Datastreams(1)/Observations?\$select=phenomenonTime,result&\$orderby=phenomenonTime%20asc"; echo

The response is a SensorThings collection. Its value array carries the recorded results, which are a series of air temperature readings. You can filter on the server with $filter, for example ...Observations?$filter=result gt 22 to return only the warmer readings.

Record your own observations (writes require authentication)

Reads are anonymous, but creating or changing data requires the administrator credential from your credentials file. The API refuses an unauthenticated write:

curl -s -o /dev/null -w "anonymous write: HTTP %{http_code}\n" \
  -X POST -H 'Content-Type: application/json' \
  -d '{"name":"unauthorised","description":"should be refused"}' \
  http://localhost/FROST-Server/v1.1/Things

That returns HTTP 401. Supply the administrator credential to record a new observation against the sample datastream, and substitute the password from your credentials file:

curl -s -o /dev/null -w "authenticated write: HTTP %{http_code}\n" \
  -u "frostadmin:<FROST_ADMIN_PASSWORD>" \
  -X POST -H 'Content-Type: application/json' \
  -d '{"phenomenonTime":"2026-02-01T09:00:00Z","result":19.7,"Datastream":{"@iot.id":1}}' \
  http://localhost/FROST-Server/v1.1/Observations

That returns HTTP 201 Created. From a client off the VM, use your VM's address in place of localhost.

To send the credential encrypted rather than in the clear, use the TLS endpoint on port 443: https://<vm-ip>/FROST-Server/v1.1/.... The certificate is self signed per VM, so pass -k or install your own certificate.

Security posture

  • Writes require authentication. FROST's BasicAuthProvider is enabled with auth.allowAnonymousRead=true, so GET requests are public while POST, PATCH, PUT and DELETE demand a credential with the appropriate role. The upstream default read/write/admin accounts are deleted. Each VM has exactly one administrator, minted at first boot with a bcrypt hashed password.
  • No known credential ships. The captured image contains no database password, no FROST administrator and no TLS private key. frost-server-firstboot.service mints them per VM, and only then writes the marker that un-gates PostgreSQL, Tomcat and nginx. An uninitialised instance can never serve.
  • PostgreSQL is loopback only. It listens on localhost, uses scram-sha-256, and its pg_hba.conf has no trust rule on any path. FROST reaches it over the loopback interface, and it is never exposed off the VM.
  • The proxy is an allowlist. nginx forwards only /FROST-Server/ to Tomcat, which itself listens on 127.0.0.1 only. Every other path returns 404, and no Tomcat or nginx version banner is disclosed.
  • Send writes over TLS where possible, using the port 443 endpoint, so administrator credentials are encrypted in transit.

Operations

  • Change the administrator password by updating the row in the USERS table (bcrypt hashed via pgcrypto), for example on the VM: sudo -u postgres psql -d sensorthings -c "UPDATE \"USERS\" SET \"USER_PASS\" = crypt('newpassword', gen_salt('bf',12)) WHERE \"USER_NAME\"='frostadmin';"
  • Access the database directly on the VM, with no password, over the local socket: sudo -u postgres psql -d sensorthings
  • Point self links at your own hostname by editing serviceRootUrl in /etc/tomcat10/Catalina/localhost/FROST-Server.xml and restarting tomcat10. This is useful behind your own DNS or load balancer.
  • Enable MQTT (SensorThings subscribe/publish) by deploying FROST's all-in-one MQTTP package instead of the HTTP-only WAR. This image ships HTTP-only to keep the memory footprint within Standard_B2s.
  • Move the database to a dedicated disk for large observation volumes. Attach an Azure data disk and relocate the PostgreSQL cluster, rather than growing the OS disk.

Support

Every SecureAnyCloud image is paired with this deployment guide and backed by 24/7 SecureAnyCloud support. If you hit an issue deploying or operating FROST-Server on Azure, contact SecureAnyCloud support. Include your VM details and the output of the health checks above.

Deploy on Azure

Launch FROST-Server 2.8 on Ubuntu 24.04 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