Hf
Application Infrastructure Azure

HAPI FHIR Server on Ubuntu 24.04 on Azure User Guide

| Product: HAPI FHIR Server R4 R5 on Ubuntu 24.04 LTS on Azure

Overview

HAPI FHIR is the de-facto open source FHIR server for healthcare interoperability. It exposes a complete FHIR REST API (FHIR R4/R5) for storing, searching and retrieving clinical resources such as Patient, Observation and Encounter, alongside a built-in web tester UI for browsing the server, reading its CapabilityStatement and running requests interactively. The cloudimg image runs the pinned official HAPI FHIR JPA Server Starter container (hapiproject/hapi:v8.10.0-2, Apache-2.0) bound to loopback, fronts it on port 80 with an nginx reverse proxy that adds a per-VM HTTP Basic Auth gate over both the tester UI and the FHIR API, and persists the bundled H2 database to a dedicated Azure data disk so your data survives restarts. HAPI's server itself ships open by design; the nginx Basic Auth credential is the access gate, and a unique password is generated on the first boot of every VM. Backed by 24/7 cloudimg support.

What is included:

  • HAPI FHIR JPA Server Starter 8.10.0-2 (FHIR R4/R5) running as a Docker container under docker.service
  • The FHIR REST API at /fhir and the interactive FHIR tester UI at /, both on port 80
  • nginx reverse proxy with per-VM HTTP Basic Auth (user admin) protecting the UI and the API, with a unique password generated on first boot
  • The bundled H2 database switched to file-backed persistence on a dedicated Azure data disk at /var/lib/hapi-fhir
  • docker.service + nginx.service as systemd units, enabled and active
  • 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 + subnet in the target region. Standard_B2ms (2 vCPU / 8 GiB RAM) is a comfortable starting point; HAPI FHIR is a Java Spring Boot application and benefits from the extra memory. NSG inbound: allow 22/tcp from your management network, 80/tcp for the UI and API, and 443/tcp if you add TLS. The image serves plain HTTP on port 80; for production and any use with real patient data, terminate TLS in front of it with your own domain and add FHIR-level authorization (see Maintenance and the compliance note below).

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for HAPI FHIR 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). Review the dedicated data disk on the Disks tab, then Review + create -> Create.

Step 2 - Deploy from the Azure CLI

az vm create \
  --resource-group <your-rg> \
  --name hapi-fhir \
  --image <marketplace-image-urn> \
  --size Standard_B2ms \
  --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 hapi-fhir --port 80 --priority 1010

Step 3 - Connect to your VM

ssh azureuser@<vm-public-ip>

Step 4 - Confirm the services are running

systemctl is-active docker.service nginx.service
docker ps

Both services report active, and docker ps shows the hapiproject/hapi:v8.10.0-2 container Up and healthy. HAPI FHIR listens on the loopback connector 127.0.0.1:8080; nginx fronts the tester UI and the FHIR API on port 80, adds the per-VM HTTP Basic Auth gate, and serves an unauthenticated /healthz. The bundled H2 database is persisted on the dedicated Azure data disk mounted at /var/lib/hapi-fhir.

The docker and nginx services active, the HAPI FHIR container running on loopback 127.0.0.1:8080, nginx on port 80, and the dedicated data disk mounted at /var/lib/hapi-fhir

Step 5 - Retrieve your password

nginx protects both the FHIR tester UI and the FHIR API with HTTP Basic Auth. The username is admin and a unique password is generated on the first boot of your VM and written to a root-only file:

sudo cat /root/hapi-fhir-credentials.txt

This file contains HAPI_FHIR_USERNAME, HAPI_FHIR_PASSWORD, the HAPI_FHIR_URL to open in a browser and the HAPI_FHIR_BASE REST endpoint. The password is stored on disk only as a bcrypt hash in /var/lib/hapi-fhir/.htpasswd, so no plaintext password ships in the image. Store the password somewhere safe.

The HAPI FHIR container image and version, the per-VM credentials file with the generated admin password and URLs, and the bcrypt .htpasswd entry that proves no plaintext password ships in the image

Step 6 - Confirm the health endpoint

nginx serves an unauthenticated health endpoint for load balancers and probes:

curl -s http://localhost/healthz

It returns ok. This endpoint never requires authentication, so it is safe for an Azure Load Balancer health probe.

Step 7 - Confirm authentication and run a FHIR round-trip

Because a password is set on first boot, an unauthenticated request to the FHIR API returns HTTP 401, so nobody reaches your FHIR server without the password. The following reads the per-VM password from the credentials file and proves the round-trip - unauthenticated is rejected, a wrong password is rejected, and the correct password returns the FHIR CapabilityStatement - then creates and reads back a Patient resource:

PW=$(sudo grep '^HAPI_FHIR_PASSWORD=' /root/hapi-fhir-credentials.txt | cut -d= -f2-)
echo "unauth   : $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/fhir/metadata)"
echo "wrong pw : $(curl -s -o /dev/null -w '%{http_code}' -u admin:wrong http://127.0.0.1/fhir/metadata)"
echo "authed   : $(curl -s -o /dev/null -w '%{http_code}' -u admin:$PW http://127.0.0.1/fhir/metadata)"
ID=$(curl -s -u admin:$PW -H 'Content-Type: application/fhir+json' -X POST http://127.0.0.1/fhir/Patient \
  -d '{"resourceType":"Patient","name":[{"family":"Smith","given":["Ada"]}]}' | grep -oP '"id"\s*:\s*"\K[^"]+' | head -1)
echo "created  : Patient/$ID"
echo "read back: $(curl -s -o /dev/null -w '%{http_code}' -u admin:$PW http://127.0.0.1/fhir/Patient/$ID)"

It prints unauth : 401, wrong pw : 401, authed : 200, then the created Patient/<id> and read back : 200. The /fhir/metadata endpoint returns the server's FHIR CapabilityStatement, and the Patient you created is now stored in the H2 database on the data disk.

The HTTP Basic Auth round-trip returning 401 unauthenticated, 401 for a wrong password and 200 with the per-VM password for the CapabilityStatement, followed by a FHIR POST Patient and GET read-back both succeeding

Step 8 - Sign in to the FHIR tester UI

Browse to http://<vm-public-ip>/. Your browser prompts for a username and password: enter admin and the password from Step 5. The HAPI FHIR tester UI opens on the server home page, listing every FHIR resource type the server supports on the left, the server software version, the FHIR base URL and a set of server actions on the right.

The HAPI FHIR tester UI home page showing the supported FHIR resource types, the HAPI FHIR R4 server software version, the FHIR base URL and the server actions including the Conformance button

Step 9 - Read the server CapabilityStatement

Click Conformance on the server home page. The tester fetches the server's FHIR CapabilityStatement (also reachable directly at /fhir/metadata) and shows the full request and response, including the HTTP 200 status and the X-Powered-By: HAPI FHIR 8.10.0 REST Server header. The CapabilityStatement is how FHIR clients discover which resources and operations this server supports.

The tester UI showing the CapabilityStatement request against /fhir/metadata returning HTTP 200, with the HAPI FHIR 8.10.0 R4 REST Server response headers

Step 10 - Search a FHIR resource

Select Patient from the resource list, open the Search tab and run the search. The tester executes GET /fhir/Patient against the server and returns a FHIR Bundle of the stored Patient resources, along with the client code snippet you would use to run the same search from your own application.

The tester UI executing a Patient search with GET /fhir/Patient, showing the timing, the generated FHIR client code snippet and the request that returns the stored Patient resources

Step 11 - Read a single Patient resource

From the search results, read an individual Patient. The tester issues GET /fhir/Patient/<id> and returns the single resource with HTTP 200, including the Content-Location and Last-Modified response headers and the resource version history reference. This is the read interaction every FHIR client uses to retrieve a known resource by id.

The tester UI reading a single Patient by id with GET /fhir/Patient/1000 returning HTTP 200 and the FHIR resource response headers including content-location and last-modified

Step 12 - Confirm data lives on the dedicated disk

HAPI FHIR's H2 database and the per-VM password file are kept under /var/lib/hapi-fhir on the dedicated Azure data disk, so your FHIR data survives OS changes and the disk can be resized independently:

findmnt /var/lib/hapi-fhir
ls -lh /var/lib/hapi-fhir/db/hapi.mv.db

The mount is backed by a separate Azure data disk captured into the image and re-provisioned on every VM. The hapi.mv.db file is the persistent H2 database holding your FHIR resources.

The HAPI FHIR data disk mounted at /var/lib/hapi-fhir showing the persistent H2 database file and the count of stored Patient resources on the dedicated Azure volume

Maintenance

  • Password: the password is set on first boot and stored as a bcrypt entry in /var/lib/hapi-fhir/.htpasswd. To change it, run sudo htpasswd -B /var/lib/hapi-fhir/.htpasswd admin and then sudo systemctl reload nginx.
  • FHIR-level authorization: the nginx Basic Auth credential is a single shared gate in front of the whole server; HAPI FHIR itself ships with no authorization. For production, add FHIR-level access control - HAPI supports authorization interceptors and SMART-on-FHIR / OAuth2 - so access is scoped per user and per resource rather than a single shared password.
  • The FHIR API: the REST base is http://<vm-public-ip>/fhir. Point your FHIR client at it with the admin Basic Auth credential; /fhir/metadata returns the CapabilityStatement. The server supports FHIR R4 by default; the starter also supports R5.
  • Persistence and database: FHIR data is stored in the bundled H2 file database under /var/lib/hapi-fhir/db on the data disk. H2 is ideal for evaluation and small single-node workloads; for larger production deployments, repoint HAPI at an external PostgreSQL by editing the container's SPRING_DATASOURCE_* environment variables (see the HAPI FHIR JPA Server Starter documentation).
  • TLS: the server is plain HTTP on port 80. For production, front it with TLS (for example certbot with your own domain) and proxy / over :443.
  • Storage: all FHIR state lives under /var/lib/hapi-fhir on the data disk; back up that volume to protect your data.
  • Security patches: unattended-upgrades remains enabled so the OS continues to receive security updates automatically.

Compliance

This image ships as infrastructure - a self-hosted HAPI FHIR server behind a per-VM password. It is not a certified or HIPAA-compliant healthcare product, and nothing here should be read as implying certified compliance. If you store, process or transmit protected health information (PHI), you are solely responsible for implementing and operating the required controls - encryption in transit and at rest, FHIR-level authentication and authorization, audit logging, access management, backups and any applicable regulatory obligations (HIPAA, GDPR and others). Deploy it inside your own secured, compliant environment and add TLS and FHIR-level authorization before using it with real patient data.

Support

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