Security AWS

Headwind MDM Android Device Management Server on AWS User Guide

| Product: Headwind MDM Android Device Management Server

Overview

This guide covers the deployment and configuration of Headwind MDM on AWS using cloudimg AWS Marketplace AMIs. Headwind MDM is an open source Mobile Device Management (MDM) server for Android device fleets - a Java web application with an admin web console and a full REST API for enrolling devices, pushing device configurations, applications and policies, grouping devices, and monitoring an estate from a single console.

The cloudimg image ships the free and open source, Apache-2.0-licensed Headwind MDM community server (the os build of hmdm-server), run the officially supported way on Apache Tomcat 9 inside a container image that bakes the application WAR, backed by a bundled PostgreSQL 16 database (pinned by digest) so your fleet, configurations and history are durable across restarts. The database and the uploaded application files live on a dedicated, independently-resizable EBS data volume. Headwind MDM normally ships a default admin/admin administrator and a static device shared secret - so nothing here ships with a known secret: the administrator password is rotated to a random value on first boot, a unique random device shared secret and a unique random database password are generated for each instance, and the public enrolment URL is left fully customer-configurable rather than baked in. Backed by 24/7 cloudimg support.

Headwind MDM is a trademark of its respective owners. This image is produced by cloudimg and is not affiliated with, endorsed by, or sponsored by the Headwind MDM project or h-mdm.com. It ships the free and open source Apache-2.0-licensed self-hosted server, unmodified.

What is included:

  • Headwind MDM 5.40.1 (the Apache-2.0-licensed open source server, admin web console and REST API) on Apache Tomcat 9, baked into a container image
  • PostgreSQL 16 (the bundled database for durable fleet state), pinned by image digest
  • Docker Engine with the server published to the loopback interface only and PostgreSQL not exposed to any host port, fronted by nginx on port 80
  • A dedicated EBS data volume at /var/lib/hmdm-data holding the PostgreSQL data directory and uploaded application/APK storage
  • docker.service, hmdm-firstboot.service, hmdm.service, hmdm-admininit.service and nginx.service as systemd units, enabled and active on boot
  • The default administrator password rotated to a random value per instance, plus a random device shared secret and PostgreSQL password, generated on first boot and never baked into the image
  • The public enrolment base URL left fully customer-configurable - no domain baked in
  • No default login, no shipped secret and an empty database on first boot
  • Ubuntu 24.04 LTS base with latest security patches applied at build time
  • 24/7 cloudimg support with a guaranteed 24-hour response SLA

Prerequisites

  • An active AWS account and an EC2 key pair in your target region
  • A subscription to the Headwind MDM listing on AWS Marketplace
  • A VPC and subnet, and a security group allowing inbound 22/tcp (SSH, from your admin network) and 80/tcp (the admin console, REST API and device enrolment, from the networks and devices that use them)

Recommended instance type: m5.large (2 vCPU, 8 GB RAM) is a sensible starting point. The server runs a Tomcat JVM alongside PostgreSQL, so a general-purpose instance with a few gigabytes of RAM is appropriate; use a larger instance for large device fleets.

Connecting to your instance

This listing offers one or more operating-system variants. Connect over SSH as that variant's default login user, using your EC2 key pair:

OS variant SSH login user Example
Ubuntu 24.04 ubuntu ssh -i your-key.pem ubuntu@<instance-public-ip>

Step 1: Subscribe and launch from AWS Marketplace

Subscribe to the Headwind MDM listing on AWS Marketplace, then choose Continue to Launch. Select your instance type (m5.large is recommended), your VPC and subnet, your key pair, and a security group that allows 22/tcp and 80/tcp as above. Launch the instance and note its public IP address.

Step 2: Launch from the AWS CLI

Alternatively, launch from the CLI. Replace the AMI id with the one for the Headwind MDM listing in your region (shown on the listing's launch page), your key name, subnet and security group:

aws ec2 run-instances \
  --image-id ami-0123456789abcdef0 \
  --instance-type m5.large \
  --key-name your-key \
  --subnet-id subnet-0123456789abcdef0 \
  --security-group-ids sg-0123456789abcdef0 \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=headwind-mdm}]'

Step 3: Connect to your instance

SSH in as the variant's login user (see the table above) - for Ubuntu, ssh -i your-key.pem ubuntu@<instance-public-ip>.

Step 4: Confirm the services are running

The stack runs as two containers - the Headwind MDM server and its bundled PostgreSQL database - under one hmdm.service, fronted by nginx. Confirm the services are active and see the containers. nginx listens on :80; the server is published to 127.0.0.1:8080 only, and PostgreSQL is not exposed to any host port:

sudo systemctl is-active docker hmdm nginx
sudo docker compose -f /etc/hmdm/compose.yaml ps

You will see the services report active and the two containers Up (and healthy). The server (127.0.0.1:8080) is bound only to the loopback interface and PostgreSQL runs inside the private Compose network with no host port; nginx on :80 is the single public front door. The PostgreSQL data directory and the uploaded application files live on the dedicated data volume:

findmnt /var/lib/hmdm-data

Step 5: Read the per-instance credentials

The administrator password was rotated to a random value, and a random device shared secret and PostgreSQL password were generated, for this instance on the first boot - before the console was reachable - and written to a root-only file. Read them:

sudo cat /root/headwind-mdm-credentials.txt

The file (mode 0600 root:root) holds HMDM_ADMIN_USERNAME (admin) and HMDM_ADMIN_PASSWORD - the account you sign in to the admin web console and the REST API with - plus this instance's console URL. None of these ship in the image; every value is unique to this instance, so no two instances share a credential and there is no default admin/admin login to change.

Step 6: Verify the security model

The health endpoint is public, but the REST API and the admin console reject every request without the per-instance administrator credential. Confirm that the health endpoint is up, that an unauthenticated request to a private API is rejected with 403, that the per-instance administrator credential authenticates a session, and that an authenticated call reads the seeded configurations back from PostgreSQL:

ADMIN_PW=$(sudo grep '^HMDM_ADMIN_PASSWORD=' /etc/hmdm/hmdm.env | cut -d= -f2-)
MD5=$(printf '%s' "$ADMIN_PW" | md5sum | awk '{print toupper($1)}')
curl -s -o /dev/null -w 'health        (public):    %{http_code}\n' http://localhost/healthz
curl -s -o /dev/null -w 'private API   (no auth):   %{http_code}\n' http://localhost/rest/private/configurations/search
curl -s -c /tmp/hmdm.cookie -o /dev/null -w 'admin login   (session):   %{http_code}\n' \
  -H 'Content-Type: application/json' -H 'Accept: application/json' -X POST \
  -d "{\"login\":\"admin\",\"password\":\"$MD5\"}" http://localhost/rest/public/auth/login
curl -s -b /tmp/hmdm.cookie -H 'Accept: application/json' http://localhost/rest/private/configurations/list

The health endpoint returns 200 with no secret, the unauthenticated private call is rejected with 403, and the login returns 200 and sets a session cookie. The final call returns the two seeded configurations as JSON (Common - Minimal and MIUI (Xiaomi Redmi)), proving the server is live and the credential reads real data from PostgreSQL. This is the security model: the console and the REST API are gated by the per-instance administrator credential, and the built-in admin/admin account no longer exists.

The image also bundles a single script at /usr/local/sbin/hmdm-roundtrip.sh that proves the whole model end to end - it confirms the console is served, that an unauthenticated private request is rejected, that the per-instance credential authenticates and reads the configurations back from PostgreSQL, and that a wrong password is rejected - which the build and smoke tests run to prove the credential works. (Because it deliberately makes a rejected login attempt, run it on its own rather than immediately before an authenticated call, as Headwind MDM briefly delays logins after a failed attempt.)

Step 7: Open the admin console and sign in

Browse to http://<instance-public-ip>/ to reach the Headwind MDM web panel, then sign in with the username admin and the HMDM_ADMIN_PASSWORD from Step 5.

The Headwind MDM web panel sign-in form, with Username or email and Password fields and a Login button, showing the Headwind MDM logo

Once signed in, the console opens on the Devices tab, with the Applications, Configurations, Files, Settings and Functions tabs across the top. The image ships one example device and two ready-made configurations so you can explore immediately.

The Headwind MDM Devices tab after sign-in, listing devices with their status, configuration and actions, and the Devices, Applications, Configurations, Files, Settings and Functions navigation

Step 8: Review device configurations

Open the Configurations tab. A configuration is the policy and app set applied to a group of devices. The image ships two ready-made configurations - Common - Minimal for generic Android devices and MIUI (Xiaomi Redmi) for MIUI devices - which you can edit, copy or extend. Everything you create here is stored in the durable PostgreSQL database.

The Headwind MDM Configurations tab listing the Common - Minimal and MIUI (Xiaomi Redmi) configurations with their descriptions and edit, delete, copy and QR-code actions

Step 9: Manage the application catalogue

Open the Applications tab to manage the app catalogue. Headwind MDM ships a large catalogue of known Android system and vendor packages; you add your own APKs or web links here and then assign them to configurations, so enrolled devices install and update them over the air.

The Headwind MDM Applications tab showing the application catalogue with package IDs, application names, versions and per-app actions

Step 10: Set your enrolment URL and enrol a device

Headwind MDM enrols Android devices against a public base URL. On first boot the image sets this to http://<instance-public-ip> so the console works immediately, but for a production fleet you set it to your own domain. Edit BASE_DOMAIN in the per-instance environment file and restart the server:

sudo sed -i 's|^BASE_DOMAIN=.*|BASE_DOMAIN=mdm.example.com|' /etc/hmdm/hmdm.env
sudo systemctl restart hmdm

The server regenerates its Tomcat context from the environment on every start, so the enrolment base URL, the device shared secret and the database credentials always come from /etc/hmdm/hmdm.env. To enrol a device, install the Headwind MDM Android launcher on it and point it at your base URL, or use the QR-code action on a configuration in the console. Real-time push to devices uses the MQTT broker on container port 31000; publish that port and open it in your security group if you want instant configuration push (devices otherwise poll over HTTP).

Step 11: Server components

The image is laid out as follows:

  • /etc/hmdm/hmdm.env - the per-instance environment file (0600 root:root): BASE_DOMAIN, SQL_PASS, SHARED_SECRET, ADMIN_EMAIL and HMDM_ADMIN_PASSWORD
  • /etc/hmdm/compose.yaml - the Docker Compose file (the hmdm and postgresql services)
  • /var/lib/hmdm-data/pgdata - the PostgreSQL data directory (on the dedicated data volume)
  • /var/lib/hmdm-data/hmdm-work - Tomcat's work directory and uploaded application/APK storage (on the dedicated data volume)
  • /root/headwind-mdm-credentials.txt - the per-instance credentials (0600 root:root)
  • /usr/local/sbin/hmdm-roundtrip.sh - the end-to-end verification script

Step 12: Managing the service

The server runs under systemd via Docker Compose. Manage it with:

sudo systemctl restart hmdm
sudo systemctl stop hmdm
sudo systemctl start hmdm
sudo docker compose -f /etc/hmdm/compose.yaml logs --tail 100

The hmdm.service unit brings the containers up with docker compose up -d --wait; PostgreSQL data persists on the dedicated volume across restarts.

Step 13: Use your own domain and HTTPS (production)

For production, put a TLS certificate in front of the server. Point a DNS record at the instance's public IP, set BASE_DOMAIN to that hostname (Step 10), then terminate TLS either at an AWS Application Load Balancer (forwarding to the instance on port 80) or with a certificate on the instance. To obtain a certificate directly on the instance with certbot:

sudo apt-get update && sudo apt-get install -y certbot
sudo certbot certonly --standalone -d mdm.example.com

Then configure nginx (/etc/nginx/sites-available/hmdm) to listen on 443 with the issued certificate and proxy to 127.0.0.1:8080, and set PROTOCOL=https in /etc/hmdm/hmdm.env before restarting the server. A load balancer that terminates TLS and forwards to port 80 needs no changes on the instance beyond setting BASE_DOMAIN.

Step 14: Security recommendations

  • Restrict 22/tcp to your administration network only
  • Expose 80/tcp (and 31000/tcp for MQTT push, if used) only to the networks and devices that enrol
  • Put HTTPS in front of the console before enrolling real devices (Step 13)
  • Keep the HMDM_ADMIN_PASSWORD in /root/headwind-mdm-credentials.txt secret; rotate it from the console (Settings) and keep the file in sync if you rely on it
  • Take regular backups of the PostgreSQL database and the /var/lib/hmdm-data volume
  • Keep the instance patched, and subscribe to cloudimg update notifications

Step 15: Support and Licensing

Headwind MDM is free and open source under the Apache License 2.0. This image ships the unmodified open source community server; it does not bundle any paid or premium plugin. cloudimg provides 24/7 technical support for this image - deployment, setting the enrolment URL, TLS termination, database backups, device enrolment, application management and scaling - by email and live chat at support@cloudimg.co.uk.

Headwind MDM is a trademark of its respective owners. cloudimg is not affiliated with or endorsed by the Headwind MDM project or h-mdm.com.

Deploy on AWS

Launch Headwind MDM from the AWS Marketplace listing and follow this guide to sign in, set your enrolment URL and enrol your first Android device.