Open Integration Engine
Overview
Open Integration Engine (OIE) is a community-governed, MPL-2.0-licensed integration engine for healthcare interoperability. You build channels that receive, filter, transform and route messages between systems. It has first-class support for HL7 v2, FHIR, DICOM, X12, XML, JSON and delimited formats, plus JavaScript transformers and connectors for HTTP, TCP/MLLP, files, databases and JMS.
This image ships version 4.6.0 on Microsoft Azure, with an admin password generated uniquely for your VM. This guide walks you through deploying it, signing in, and pushing your first message through a channel.
What the image gives you
- Engine: Open Integration Engine 4.6.0 on OpenJDK 17
- Base OS: Ubuntu 24.04 LTS, fully patched at build time
- Web edge: nginx on port 443 (TLS) in front of the engine. Port 80 serves a health endpoint and redirects to HTTPS.
- Admin credential: generated per VM on first boot. No shared or default password ships in the image.
- Data: embedded Derby database, keystore and configuration on a dedicated 20 GiB Azure data disk
- Self test: oie-engine-selftest.sh proves that the engine really processes a message
- Recommended size: Standard_B2s (2 vCPU / 4 GiB) for evaluation and light workloads
Compliance note. This image is infrastructure. You remain responsible for every HIPAA, PHI and GDPR control that applies to your deployment, including your own domain with a trusted TLS certificate, network isolation, access control, audit logging and encryption at rest. Nothing here is certified compliant.
1. Launch the VM
From the Azure portal
- Open the offer in the Azure Marketplace and choose Get It Now, then Create.
- Pick your subscription, resource group and region.
- Choose a VM size.
Standard_B2sis enough to evaluate the engine and run light channel traffic. Scale up for production throughput. - Set the administrative username to
azureuserand supply your SSH public key. - On the Networking tab, allow inbound 22 (SSH) and 443 (HTTPS). Port 80 is optional and only serves a health endpoint plus a redirect to HTTPS.
- Create the VM.
From the Azure CLI
Run these commands on your own workstation, not on the VM:
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:open-integration-engine-ubuntu-24-04:default:latest
az vm create \
--resource-group my-resource-group \
--name my-oie-vm \
--image sac1647283583153:open-integration-engine-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
# Then open only the ports you need, restricted to the networks that should reach them.
az vm open-port --resource-group my-resource-group --name my-oie-vm \
--port 443 --priority 1010 --source-address-prefixes <your-mgmt-cidr>
az vm open-port --resource-group my-resource-group --name my-oie-vm \
--port 22 --priority 1020 --source-address-prefixes <your-mgmt-cidr>First boot takes about a minute. The engine creates a fresh database, rotates the admin password to a value unique to your VM, and generates a TLS certificate for this VM. Only then does it start nginx.
2. Retrieve your per-VM admin password
The image ships no usable credential. First boot generates the password on your VM and writes it to a root-only file. Connect over SSH and read the file:
ssh azureuser@<vm-ip>
sudo cat /root/oie-credentials.txt# Open Integration Engine - generated on first boot by oie-firstboot.service
# These credentials are unique to this VM. Store them somewhere safe. OIE_URL=https://<vm-ip>/ OIE_API_BASE=https://<vm-ip>/api OIE_USERNAME=admin OIE_PASSWORD=<OIE_PASSWORD>Copy the password into your password manager, then confirm the services are healthy:
systemctl status oie nginx --no-pager --lines=5You can see exactly what first boot did. The journal shows the credential rotation and the proof that the old default login no longer works:
sudo journalctl -u oie-firstboot.service --no-pager | tail -143. Open the web interface
Browse to https://<vm-public-ip>/.
The appliance uses a self-signed certificate generated for your VM, so your browser warns you the first time. Accept the warning for evaluation. See section 8 before you go to production.
This landing page confirms that the backend is running. It also gives you the URL to point an Administrator Launcher at. Two things are worth understanding here:
- Channel authoring happens in the Administrator, a desktop application that you download separately from the project. Point it at the URL shown on this page and sign in as
adminwith your per-VM password. - Everything the Administrator does is also available over the REST API, which is fully browsable on this VM. This guide uses the API, because it works from any machine with
curland needs nothing installed.
4. Explore the Client API
Click Go to API, or browse to https://<vm-public-ip>/api/.
Expanding Channels shows the operations you use to create, deploy, start, stop and inspect channels.
One API rule to remember. The engine ships with server.api.require-requested-with = true, so every REST call must carry an X-Requested-With header. The API explorer adds it for you. From curl, add it yourself, as shown below.
Without the header, the engine replies 400 All requests must have 'X-Requested-With' header.
Sign in from the command line and keep the session cookie:
PW=$(sudo grep '^OIE_PASSWORD=' /root/oie-credentials.txt | cut -d= -f2-)
curl -sk -c /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
--data-urlencode 'username=admin' \
--data-urlencode "password=${PW}" \
https://127.0.0.1/api/users/_loginA successful sign-in returns a LoginStatus of SUCCESS. Check the engine version the same way:
curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
https://127.0.0.1/api/server/version4.6.05. Prove the engine works, in one command
A web page returning 200 tells you that a web server is up. It does not tell you that the engine can actually move a message. The image ships a self test that answers the real question. It deploys a temporary channel, pushes an HL7 v2 message through it, checks the engine's own statistics, and removes the channel again.
sudo /usr/local/sbin/oie-engine-selftest.shreceived=1 sent=1 error=0 means the message travelled through the source connector, the transformer and the destination connector.
Run the self test any time you want to confirm that the engine is healthy, including after you change configuration.
6. Deploy your first channel
The image ships the self test's channel definition at /usr/share/sac/oie/example-channel.xml so you can run this section verbatim. It is a minimal Channel Reader source feeding a JavaScript Writer destination.
Give the channel an id and a name of your own:
CHANNEL_ID=$(cat /proc/sys/kernel/random/uuid)
echo "${CHANNEL_ID}" | sudo tee /tmp/oie-channel-id >/dev/null
sudo sed -e "s/SAC_CHANNEL_ID/${CHANNEL_ID}/" \
-e "s/SAC_CHANNEL_NAME/my-first-channel/" \
/usr/share/sac/oie/example-channel.xml | sudo tee /tmp/my-channel.xml >/dev/null echo "prepared channel ${CHANNEL_ID}"Create the channel:
curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
-H 'Content-Type: application/xml' \
--data-binary @/tmp/my-channel.xml \
-o /dev/null -w 'create: HTTP %{http_code}\n' \
https://127.0.0.1/api/channelsDeploy the channel:
CHANNEL_ID=$(cat /tmp/oie-channel-id)
curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
-H 'Content-Type: application/xml' \
--data "<set><string>${CHANNEL_ID}</string></set>" \
-o /dev/null -w 'deploy: HTTP %{http_code}\n' \
"https://127.0.0.1/api/channels/_deploy?returnErrors=true"Confirm it reached STARTED:
CHANNEL_ID=$(cat /tmp/oie-channel-id) for i in $(seq 1 30); do
STATE=$(curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
"https://127.0.0.1/api/channels/${CHANNEL_ID}/status" | grep -oE '<state>[A-Z]+</state>')
echo "${STATE}" case "${STATE}" in *STARTED*) break;; esac sleep 2 donePush an HL7 v2 message through the channel:
CHANNEL_ID=$(cat /tmp/oie-channel-id)
curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
-H 'Content-Type: text/plain' \
--data-binary 'MSH|^~\&|SENDING_APP|SENDING_FAC|RECEIVING_APP|RECEIVING_FAC|20260101000000||ADT^A01|MSG00001|P|2.5' \
-w '\nsubmit: HTTP %{http_code}\n' \
"https://127.0.0.1/api/channels/${CHANNEL_ID}/messages?destinationMetaDataId=1"Do not omit destinationMetaDataId. It names the destination connectors that the message should be routed to.
Without the parameter, the engine accepts the message and runs the source connector only. Statistics show received=1 and sent=0 forever, and your destinations never fire. This is the single most common surprise when driving the engine from the REST API.
Check the statistics. received and sent should both be 1:
CHANNEL_ID=$(cat /tmp/oie-channel-id)
curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
"https://127.0.0.1/api/channels/${CHANNEL_ID}/statistics"When you are finished with the example, undeploy and remove it:
CHANNEL_ID=$(cat /tmp/oie-channel-id)
curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
-H 'Content-Type: application/xml' \
--data "<set><string>${CHANNEL_ID}</string></set>" \
-o /dev/null -w 'undeploy: HTTP %{http_code}\n' \
https://127.0.0.1/api/channels/_undeploy
curl -sk -b /tmp/oie-cookies -H 'X-Requested-With: XMLHttpRequest' \
-X DELETE -o /dev/null -w 'delete: HTTP %{http_code}\n' \
"https://127.0.0.1/api/channels/${CHANNEL_ID}"To build real channels, export a definition from the Administrator and create the channel the same way.
7. Opening ports for your own channel listeners
The image opens only what the appliance itself needs: 22, 80 and 443.
Listener ports belong to the channels you create. An MLLP listener on 6661, an HTTP listener on 8081 or a TCP listener on any port binds on the VM as soon as you deploy the channel. Azure does not admit traffic to it until you open that port too:
az vm open-port --resource-group my-resource-group --name my-oie-vm \
--port 6661 --priority 1030 --source-address-prefixes <your-mgmt-cidr>Open only the ports you actually use. Restrict the source address range on the NSG rule to the systems that are meant to talk to the engine.
8. Security posture and hardening
The image is secure by default in three specific ways.
No known credential ships in the image. The engine's database is wiped before the image is captured, so the captured image contains no account at all. Your VM creates the database fresh on first boot. It rotates the admin password to a value unique to your VM before it starts the public web edge.
The engine is not directly exposed. Its admin and REST listeners bind to 127.0.0.1 only. nginx on ports 80 and 443 is the sole public edge. Check the listeners with:
sudo ss -tlnp | grep -E ':8443|:8080|:443|:80 'The old default login is provably dead. Check this yourself at any time:
curl -sk -H 'X-Requested-With: XMLHttpRequest' \
--data-urlencode 'username=admin' --data-urlencode 'password=admin' \
https://127.0.0.1/api/users/_loginThis returns <status>FAIL</status>. If it ever returns SUCCESS, stop and rebuild the VM.
Before production
- Replace the TLS certificate. Point a DNS name at the VM and install a trusted certificate:
bash sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx sudo certbot --nginx -d oie.example.com
- Restrict access. Limit inbound 443 to the networks that need the Administrator or API, using an NSG rule or a private endpoint.
- Change the admin password to one of your own, and create named per-person accounts rather than sharing
admin. - Back up the data disk. Everything stateful lives on it, so an Azure snapshot schedule against that disk captures your channels and message history.
9. Where everything lives
Path Contents
/opt/oie Engine installation, launcher, extensions and logs
/opt/oie/conf/mirth.properties Engine configuration, including the loopback bind and data directory
/opt/oie/conf/custom.vmoptions JVM options, including the heap size
/var/lib/oie/appdata Data disk. Derby database, keystore, configuration map and temporary data
/root/oie-credentials.txt Your per-VM admin credentials, root readable only
/usr/local/sbin/oie-engine-selftest.sh The engine self test
Service management:
sudo systemctl status oie
sudo systemctl restart oie
sudo journalctl -u oie -fTuning the JVM heap
The image sets a 1 GiB maximum heap, which suits Standard_B2s. If you move to a larger VM and drive real message volume, raise the heap:
sudo sed -i 's/^-Xmx1024m/-Xmx3072m/' /opt/oie/conf/custom.vmoptions
sudo systemctl restart oieLeave at least a gigabyte for the operating system, nginx and the database cache. Never add a swap file to compensate for an undersized VM. Move to a larger size instead.
10. Troubleshooting
The web page does not load. Confirm nginx is running and port 443 is open in the NSG:
sudo systemctl status nginx --no-pager curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthz/healthz returns 200 from the VM itself whenever the edge is healthy.
The credentials file still shows <OIE_PASSWORD> instead of a password. First boot has not finished. Watch it:
sudo journalctl -u oie-firstboot.service -fThe engine's first start creates the database and can take up to a couple of minutes.
A REST call returns 400. You omitted the X-Requested-With header. See section 4.
A channel receives messages but never sends them. You omitted destinationMetaDataId when submitting the message. See section 6.
The engine will not start after a configuration change. Check the log:
sudo journalctl -u oie --no-pager | tail -50 sudo tail -50 /opt/oie/logs/mirth.logSupport
This image is maintained by SecureAnyCloud with 24/7 support for deployment, upgrades, TLS setup and channel configuration questions. Contact sales@secureanycloud.com.
Open Integration Engine itself is open source under the Mozilla Public License 2.0. Project documentation, the Administrator Launcher downloads and the community are at openintegrationengine.org.
Deploy on Azure
Launch Open Integration Engine on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com