FROST-Server 2.8 on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and use of FROST-Server 2.8 on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. 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, and exposes that model over a clean REST interface that clients query, filter, page and expand with ordinary HTTP requests.
Every observation is persisted 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 it implements a published OGC standard rather than a proprietary API, data recorded through FROST-Server is portable across the growing ecosystem of SensorThings clients and tools.
Security by design. FROST-Server's upstream default 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 that 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 listens only on loopback behind an nginx reverse proxy that forwards an explicit allowlist of paths, and the base is fully patched with unattended security upgrades enabled.
What is included:
- 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, listening on loopback only with
scram-sha-256passwords and notrustrule anywhere - FROST's BasicAuthProvider enabled with anonymous reads allowed and per-VM administrator credentials; the three default
read/write/adminusers 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 — a weather station Thing with a Location, a Sensor, an ObservedProperty, a Datastream and several temperature Observations — ready to query from first boot
- A root only credentials file at
/root/frost-server-credentials.txt, first boot servicefrost-server-firstboot.servicegating 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, opening SSH and HTTP (and optionally HTTPS) to your own network:
az vm create \
--resource-group my-rg \
--name frost-1 \
--image <this-marketplace-image> \
--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 because the VM mints its PostgreSQL and administrator credentials, sets the SensorThings service root URL to its own address and writes its TLS certificate before the API is allowed to serve. 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. SSH in 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
SSH into the VM, 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 whose 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
The whole point of the API is retrieving observation values. Pull the observations of the sample datastream, selecting 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 whose value array carries the real recorded results — a series of air temperature readings. You can filter server side 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. An unauthenticated write is refused:
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 (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 (the certificate is self signed per VM, so pass -k or install your own certificate): https://<vm-ip>/FROST-Server/v1.1/....

Security posture
- Writes require authentication. FROST's BasicAuthProvider is enabled with
auth.allowAnonymousRead=true, soGETrequests are public whilePOST,PATCH,PUTandDELETEdemand a credential with the appropriate role. The upstream defaultread/write/adminaccounts 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.servicemints them per VM and only then writes the marker that un-gates PostgreSQL, Tomcat and nginx, so an uninitialised instance can never serve. - PostgreSQL is loopback only. It listens on
localhost, usesscram-sha-256, and itspg_hba.confhas notrustrule on any path. FROST reaches it over the loopback interface; it is never exposed off the VM. - The proxy is an allowlist. nginx forwards only
/FROST-Server/to Tomcat, which itself listens on127.0.0.1only; every other path returns404, 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
USERStable (bcrypt hashed viapgcrypto), 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
serviceRootUrlin/etc/tomcat10/Catalina/localhost/FROST-Server.xmland restartingtomcat10— 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 by attaching an Azure data disk and relocating the PostgreSQL cluster, rather than growing the OS disk.
Support
Every cloudimg image is paired with this deployment guide and backed by 24/7 cloudimg support. If you hit an issue deploying or operating FROST-Server on Azure, contact cloudimg support with your VM details and the output of the health checks above.