Ss
Applications Azure

Snowstorm SNOMED CT Terminology Server on Ubuntu 24.04 on Azure User Guide

| Product: Snowstorm SNOMED CT Terminology Server on Ubuntu 24.04 LTS on Azure

Overview

Snowstorm is the official open source SNOMED CT terminology server developed by SNOMED International, built on Elasticsearch for performance and enterprise scale. It serves SNOMED CT clinical terminology through a REST API and a standards based HL7 FHIR terminology API, supporting concept lookup, description and full text search, the SNOMED Expression Constraint Language (ECL) for querying subsets of the hierarchy, reference set membership, and the FHIR CodeSystem, ValueSet and ConceptMap operations. It underpins clinical systems, terminology browsers and data pipelines that need an authoritative, queryable SNOMED CT service.

The cloudimg image ships the full server stack wired together and ready on the first boot of every VM: Snowstorm 10.11.2 (Apache 2.0) running on Eclipse Temurin 17, backed by a single node Elasticsearch 8.11.1 datastore (the exact Elasticsearch version Snowstorm is tested against), all as systemd services on a hardened, fully patched Ubuntu 24.04 LTS base. An nginx reverse proxy serves the interactive Swagger interface and the API on port 80. Elasticsearch is bound to the loopback interface only and secured with a per VM password generated on first boot, and the terminology API is served read only so no unauthenticated caller can alter content. The full SNOMED CT content is never bundled, because it requires your own SNOMED CT licence: the server ships healthy and empty, and you import your licensed release when you are ready. Backed by 24/7 cloudimg support.

What is included:

  • Snowstorm 10.11.2 served on port 80 through nginx, managed by systemd, with the Swagger interface at the site root
  • Elasticsearch 8.11.1 as the backing datastore, single node, bound to 127.0.0.1 only
  • Eclipse Temurin 17 as the Java runtime
  • A per VM Elasticsearch superuser password generated on first boot and recorded in a root only file; no shipped default credential
  • Read only API by default, so mutating and administrative operations are protected from unauthenticated callers
  • Java heaps tuned for the recommended 8 GiB VM (Elasticsearch 3 GiB, Snowstorm 2 GiB)
  • elasticsearch.service, snowstorm.service, nginx.service and snowstorm-firstboot.service as systemd units
  • An unauthenticated /healthz endpoint for Azure Load Balancer health probes
  • 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2ms (2 vCPU / 8 GiB RAM) is the recommended size and is what the image is tuned for. Importing and querying the full SNOMED CT International Edition is memory intensive: for production content SNOMED International recommends at least 8 GiB for Elasticsearch alone, so size up (for example to a memory optimised Standard_E4s_v5 with 32 GiB) before importing a full release. NSG inbound: allow 22/tcp from your management network and 80/tcp for the API. Snowstorm serves plain HTTP on port 80; for production, put your own domain and TLS in front of it. Elasticsearch is never exposed: it listens on 127.0.0.1:9200 only, and Snowstorm listens on 127.0.0.1:8080 only, so neither port is reachable from the network.

You will also need your own SNOMED CT licence and RF2 release files to load real content. SNOMED CT is licensed by SNOMED International; in many countries it is available at no charge through a national release centre. See https://www.snomed.org/get-snomed. The cloudimg image deliberately ships with no SNOMED CT content.

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Snowstorm 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 HTTP (80). Then Review + create and Create.

Step 2 - Deploy from the Azure CLI

az vm create \
  --resource-group <your-rg> \
  --name snowstorm \
  --image <marketplace-image-urn> \
  --size Standard_B2ms \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

Then open port 80 to the API:

az vm open-port --resource-group <your-rg> --name snowstorm --port 80

Step 3 - Confirm the services are running

SSH in as azureuser and confirm that Elasticsearch, Snowstorm and nginx are all active. The snowstorm-firstboot unit is a one shot that runs once on the first boot to mint the per VM Elasticsearch password and start the services, then stays satisfied. Elasticsearch and Snowstorm listen only on the loopback interface, while nginx serves the API on port 80.

systemctl is-active elasticsearch snowstorm nginx
active
active
active
ss -tlnp | grep -E ':80 |:8080 |:9200 '

The listing confirms nginx on 0.0.0.0:80 (public) with Elasticsearch on 127.0.0.1:9200 and Snowstorm on 127.0.0.1:8080 bound to the loopback interface only, and the Java runtime is Eclipse Temurin 17.

The elasticsearch, snowstorm and nginx services all active, the Eclipse Temurin 17 runtime, and the listening ports showing nginx public on port 80 while Elasticsearch and Snowstorm stay bound to the 127.0.0.1 loopback interface

Step 4 - Retrieve the per VM credentials

A unique Elasticsearch superuser password is generated on the first boot of every VM and written to a root only file. Read it with sudo:

sudo cat /root/snowstorm-credentials.txt
SNOWSTORM_URL=http://<your-vm-ip>/
SNOWSTORM_FHIR_URL=http://<your-vm-ip>/fhir
ES_ELASTIC_PASSWORD=<generated-on-first-boot>
SNOWSTORM_SPRING_PASSWORD=<generated-on-first-boot>

The passwords are unique to your VM. You do not need them for day to day read only API use, which is open on port 80; they secure the Elasticsearch datastore and are there if you enable Snowstorm's authenticated administrative access.

Step 5 - Explore the Swagger interface

Open http://<your-vm-ip>/ in a browser. Snowstorm serves an interactive Swagger (OpenAPI 3.0) interface that documents and lets you call every endpoint of the terminology server. Because the image ships in read only mode, the content changing and administrative operations are disabled and hidden, so what you see is the safe read and query surface.

The Snowstorm 10.11.2 Swagger interface titled SNOMED CT Terminology Server REST API, showing the OAS 3.0 badge, the Apache 2.0 licence link and the expandable Admin, Admin Permissions, Authoring Stats and Branching endpoint groups

Step 6 - Query the REST API

The REST API answers on port 80. Check the server version:

curl -s http://127.0.0.1/version

The Snowstorm /version endpoint returning JSON with the version 10.11.2 and a build timestamp

List the code systems. On a freshly deployed image the root SNOMEDCT code system exists on the MAIN branch but holds no content yet, which is the expected empty but healthy state before you import a release:

curl -s http://127.0.0.1/codesystems

The Snowstorm /codesystems endpoint returning JSON with a single SNOMEDCT code system on the MAIN branch and no imported modules, the empty but healthy state before a SNOMED CT release is loaded

Once you have imported a SNOMED CT release, the same API answers concept, description and ECL queries, for example http://<your-vm-ip>/MAIN/concepts?term=asthma for a term search or http://<your-vm-ip>/MAIN/concepts?ecl=<404684003 for an Expression Constraint Language query.

Step 7 - Use the HL7 FHIR terminology API

Snowstorm also exposes a standards based HL7 FHIR terminology API under /fhir, providing the CodeSystem, ValueSet and ConceptMap resources and operations such as $lookup, $expand, $validate-code, $subsumes and $translate. After you import a release these back onto your licensed SNOMED CT content, for example:

http://<your-vm-ip>/fhir/CodeSystem/$lookup?system=http://snomed.info/sct&code=427623005
http://<your-vm-ip>/fhir/ValueSet/$expand?url=http://snomed.info/sct?fhir_vs=isa/404684003

Step 8 - Import your licensed SNOMED CT release

The server ships empty and read only. To load real content you provide your own licensed SNOMED CT RF2 release and run a one time import. Snowstorm's recommended flow is to import while writable, then return the server to read only for serving.

First, obtain the SNAPSHOT RF2 archive for your edition from SNOMED International or your national release centre, and copy it to the VM. Then switch Snowstorm to writable and restart:

sudo sed -i 's/^snowstorm.rest-api.readonly=true/snowstorm.rest-api.readonly=false/' \
  /opt/snowstorm/application-local.properties
sudo systemctl restart snowstorm

Create an import job (SNAPSHOT type) and note the returned import id, then upload the RF2 archive to it. You can do this from the Swagger interface under the Import endpoints, or with curl:

# Create a SNAPSHOT import and capture its id
IMPORT_ID=$(curl -s -X POST http://127.0.0.1/imports \
  -H 'Content-Type: application/json' \
  -d '{"type":"SNAPSHOT","branchPath":"MAIN","createCodeSystemVersion":true}' \
  -i | awk -F/ '/Location:/ {print $NF}' | tr -d '\r')

# Upload the licensed RF2 archive to that import job (this can take 30-60+ minutes)
curl -s -X POST "http://127.0.0.1/imports/${IMPORT_ID}/archive" \
  -H 'Content-Type: multipart/form-data' \
  -F file=@SnomedCT_InternationalRF2_PRODUCTION_<release>.zip

Watch progress with curl -s http://127.0.0.1/imports/${IMPORT_ID}. When it reports completed, return the server to read only mode for serving and restart:

sudo sed -i 's/^snowstorm.rest-api.readonly=false/snowstorm.rest-api.readonly=true/' \
  /opt/snowstorm/application-local.properties
sudo systemctl restart snowstorm

Your licensed SNOMED CT content is now served through the REST and FHIR APIs. Full content imports are memory intensive, so make sure the VM is sized appropriately (see Prerequisites) before importing.

Step 9 - The no default credential security model

The image ships with no known credential. Elasticsearch's elastic superuser password is generated uniquely on the first boot of each VM and written to a 0600 root:root file, and Elasticsearch is reachable only on the loopback interface. The terminology API is served read only, so no unauthenticated caller can create or change content. A round trip check proves the per VM Elasticsearch password authenticates while anonymous access and the well known Elasticsearch defaults are rejected, and that a content changing request is refused:

sudo bash /usr/local/sbin/snowstorm-cred-roundtrip.sh
OK per-VM elastic auth works; anon+ES-defaults rejected; /version+/codesystems 200; mutating POST rejected (read-only, HTTP 403)

The Snowstorm security round trip confirming the per VM Elasticsearch password authenticates while anonymous access and the elastic changeme default are refused with HTTP 401, and a content changing POST to the terminology API is refused with HTTP 403 because the server is read only

Step 10 - Architecture and where your data lives

nginx on port 80 reverse proxies to Snowstorm on 127.0.0.1:8080, which stores all terminology data in the single node Elasticsearch 8.11.1 datastore on 127.0.0.1:9200. Elasticsearch holds its indices under /var/lib/elasticsearch on the VM disk; that is where your imported SNOMED CT content lives. The /healthz endpoint returns 200 for load balancer probes.

curl -sI http://127.0.0.1/healthz | head -1
sudo du -sh /var/lib/elasticsearch

The Elasticsearch 8.11.1 datastore reporting cluster status green, the Snowstorm 10.11.2 REST API returning the version and the single empty SNOMEDCT code system, and the Snowstorm terminology indices persisted under /var/lib/elasticsearch on disk

To protect your terminology data, snapshot the VM disk or use Elasticsearch's own snapshot and restore against a repository you control.

Using your own domain and TLS

Snowstorm serves plain HTTP on port 80. For production, put your own domain and a TLS terminating proxy or load balancer in front of the VM. Snowstorm reads X-Forwarded-* headers, which the bundled nginx already sets, so links and the Swagger server URL resolve correctly behind a proxy.

Support

This image is published and supported by cloudimg with 24/7 support. Snowstorm is developed by SNOMED International and released under the Apache 2.0 licence. SNOMED CT content is licensed separately by SNOMED International and is not included in this image. Elasticsearch is included solely as the unmodified backing datastore for Snowstorm. For product documentation see the Snowstorm project. For issues with the cloudimg image, contact cloudimg support.