Headwind MDM
Overview
Headwind MDM is an open-source Mobile Device Management server for Android. It gives you one admin console and a REST API to enrol company devices, push configurations, applications and policies over the air, and monitor the state of your estate.
The SecureAnyCloud image installs the official open-source Headwind MDM 5.40.1 web application on Apache Tomcat 9 with a bundled PostgreSQL 16 database. Both run on a private container network, fronted by an nginx reverse proxy on port 80.
The image removes the two things that make a self-deployed Headwind MDM risky. Upstream seeds a default admin / admin administrator account and a static device shared secret when it first creates its database. On this image, both are replaced with unique random values generated on the first boot of your VM. The reverse proxy refuses the console until that replacement has completed, so the default credential is never reachable over the network.
What you get:
- The official open-source Headwind MDM 5.40.1 web application on Apache Tomcat 9, pinned so that the appliance never silently upgrades
- A bundled PostgreSQL 16 database, pinned by digest and published to no host port at all, that holds your fleet, configurations and application catalogue
- An nginx reverse proxy on port
80serving the admin console, the REST API and an unauthenticated/healthzendpoint for load balancer probes - A unique administrator password, device shared secret and database password generated on the first boot of every VM and stored in a root-only file
- A dedicated, independently resizable Azure data disk mounted at
/var/lib/hmdm-datathat holds the database and your uploaded application files - A public enrolment base URL that resolves to your VM's own address at first boot and that you can change afterwards
- The whole stack managed as one
hmdm.servicesystemd unit alongsidedocker.serviceandnginx.service, all enabled - 24/7 SecureAnyCloud support
Key facts:
- Platform: Ubuntu 24.04 LTS
- Default login user: azureuser
- Admin console: http://<vm-public-ip>/
- Health endpoint: http://<vm-public-ip>/healthz
- Credentials file: /root/headwind-mdm-credentials.txt
- Per-VM configuration: /etc/hmdm/hmdm.env
- Data disk: /var/lib/hmdm-data
Prerequisites
- An active Azure subscription with permission to create virtual machines.
- An SSH key pair for the
azureuseraccount. - A virtual network and subnet in your target region.
- A network security group that allows inbound
22/tcpfrom your management network and80/tcpfrom the clients and Android devices that reach the server.
Recommended VM size: Standard_B2s (2 vCPU / 4 GiB RAM). The Tomcat application, the bundled PostgreSQL database and the nginx proxy run comfortably in that footprint, with roughly 2.8 GiB free.
Choose a larger size such as Standard_D2s_v3 if you plan to manage a large estate or upload a big application catalogue.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, and search the Marketplace for Headwind MDM by SecureAnyCloud. 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), then add 80 after deployment.
Select Review + create, then Create.
Step 2 - Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:headwind-mdm-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> \
--name headwind-mdm \
--image sac1647283583153:headwind-mdm-ubuntu-24-04:default:latest \
--size Standard_B2s \
--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 headwind-mdm --port 80 --priority 1010Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>You can look up the address at any time:
az vm show --resource-group <your-rg> --name headwind-mdm --show-details --query publicIps --output tsvStep 4 - Confirm the stack is running
Headwind MDM runs as a two-container Docker Compose stack managed by a single systemd unit. On the first boot of your VM, the unit generates every secret, re-rolls the database and rotates the administrator password before the console is opened up. Allow a few minutes after the VM is created.
systemctl is-active hmdm.serviceThe unit reports active. List the containers to see the application and its bundled database:
sudo docker compose --env-file /etc/hmdm/hmdm.env -f /etc/hmdm/compose.yaml ps --format 'table {{.Service}}\t{{.Status}}'Expected output:
SERVICE STATUS hmdm Up 12 minutes (healthy) postgresql Up 13 minutes (healthy)You control the whole stack as one unit with sudo systemctl status hmdm and sudo systemctl restart hmdm.
Step 5 - Retrieve your per-VM administrator credentials
The first boot of your VM generates a unique administrator password and writes it to a root-only file. Confirm that the file exists and is 0600:
sudo ls -l /root/headwind-mdm-credentials.txtExpected output:
-rw------- 1 root root 1058 Jul 25 15:18 /root/headwind-mdm-credentials.txtThen read the file to get your credentials:
sudo cat /root/headwind-mdm-credentials.txtThe file contains HMDM_ADMIN_USERNAME (admin), HMDM_ADMIN_PASSWORD (your unique password), HMDM_URL (the console address) and HMDM_ENROLLMENT_BASE_URL (the address your Android devices enrol against). Store them somewhere safe.
Step 6 - Confirm the endpoints are healthy
The nginx proxy serves an unauthenticated health endpoint that suits a load balancer probe:
curl -s -o /dev/null -w 'health -> HTTP %{http_code}\n' http://127.0.0.1/healthzThen check the admin console itself:
curl -s -o /dev/null -w 'console -> HTTP %{http_code}\n' http://127.0.0.1/Both return HTTP 200.
Next, confirm which ports face the network. Only SSH and the nginx proxy are exposed. The Tomcat application is bound to loopback, and the bundled PostgreSQL is on no host port at all:
sudo ss -tln | awk 'NR==1 || /:80 |:8080 /{print $1, $4}'Expected output:
State Local LISTEN 0.0.0.0:80 LISTEN 127.0.0.1:8080 LISTEN [::]:80Your database and uploaded application files live on the dedicated data disk. Confirm it with:
df -h /var/lib/hmdm-dataStep 7 - Confirm the pinned release
The application and database images are pinned and baked into the image. Your VM never downloads anything at first boot and never silently upgrades. List the images with:
sudo docker compose --env-file /etc/hmdm/hmdm.env -f /etc/hmdm/compose.yaml imagesThe hmdm service reports tag 5.40.1. The postgresql service is pinned by image digest rather than a tag, so its tag column shows <none> by design.
Step 8 - Confirm the default credential is gone
This is the security property that makes the image safe to expose. Upstream Headwind MDM seeds an admin / admin account when it creates its database. Your VM rotated that account on first boot.
The console sends the uppercase MD5 of the password. Reproduce that and try the default:
DEFAULT_MD5=$(printf 'admin' | md5sum | awk '{print toupper($1)}')
curl -s -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' \
-d "{\"login\":\"admin\",\"password\":\"$DEFAULT_MD5\"}" \
http://127.0.0.1/rest/public/auth/loginExpected output. The default credential is rejected:
{"status":"ERROR","message":null,"data":null}This endpoint answers HTTP 200 even when authentication fails. Read the status field for the result, not the HTTP code.
A private endpoint reached without a session is refused outright:
curl -s -o /dev/null -w 'private (no session) -> HTTP %{http_code}\n' http://127.0.0.1/rest/private/configurations/searchIt returns HTTP 403.
Step 9 - Sign in to the admin console
Browse to http://<vm-public-ip>/ and sign in with the username admin and the password from Step 5.
The Devices tab is the console's main view. It lists every enrolled Android device with its status, the configuration applied to it, and its permission and installation state. It also shows the per-device actions you can take.
Step 10 - Review configurations and applications
A configuration is the policy and application set that applies to a group of devices. The image ships two ready-made configurations that you can edit, copy or extend.
The Applications tab is your application catalogue. Add your own APK files or web links here. Assign them to a configuration so that every device in that group installs and updates them over the air.
Step 11 - Set your public enrolment URL
Android devices enrol against a public base URL. On first boot, your VM sets that URL to its own address so that the server works immediately:
sudo grep -E '^BASE_DOMAIN|^PROTOCOL' /etc/hmdm/hmdm.envTo enrol devices against your own domain name instead, edit BASE_DOMAIN in /etc/hmdm/hmdm.env. Then restart the stack so that the application picks up the change:
sudo sed -i 's/^BASE_DOMAIN=.*/BASE_DOMAIN=mdm.example.com/' /etc/hmdm/hmdm.env
sudo systemctl restart hmdmThe application regenerates its Tomcat context from that file on every start, so nothing else needs editing.
Point the DNS name at your VM before you enrol devices against it. Enrolled devices keep using the URL they were enrolled with.
Step 12 - Enrol your first Android device
Install the Headwind MDM launcher on the device and point it at your server. Then approve the device in the console:
- In the console, open Devices and select Add to create a device entry. Alternatively, allow automatic creation from Settings.
- Assign the device to a configuration, for example Common - Minimal.
- On the Android device, install the Headwind MDM launcher from the Headwind MDM downloads page and enter your enrolment URL from Step 11.
- The device appears in the Devices list and receives its configuration and applications over the air.
See the Headwind MDM documentation for the full enrolment, kiosk and REST API reference.
Server components
- Headwind MDM 5.40.1: open-source Android Mobile Device Management server, admin console and REST API
- Apache Tomcat 9: servlet container that runs the application, bound to 127.0.0.1:8080
- PostgreSQL 16: bundled database that holds devices, configurations and the application catalogue
- nginx 1.24: reverse proxy on port 80 and the /healthz endpoint
- Docker Engine (Docker CE): container runtime for the two application containers
Filesystem layout
- /: root filesystem on the OS disk
- /boot: operating system kernel files
- /boot/efi: UEFI boot partition (Gen2 Hyper-V)
- /mnt: Azure temporary resource disk
- /var/lib/hmdm-data: dedicated 30 GiB data disk that holds the database and uploaded files
Key directories:
- /etc/hmdm/hmdm.env: per-VM configuration and secrets, 0600, root only
- /etc/hmdm/compose.yaml: the Docker Compose definition of the stack
- /var/lib/hmdm-data/pgdata: PostgreSQL data directory
- /var/lib/hmdm-data/hmdm-work: application working directory and uploaded application files
- /root/headwind-mdm-credentials.txt: your per-VM administrator credentials, 0600, root only
- /etc/nginx/sites-available/hmdm: the reverse proxy configuration
Managing the service
- Status: sudo systemctl status hmdm
- Restart the whole stack: sudo systemctl restart hmdm
- Stop the stack: sudo systemctl stop hmdm
- Container status: sudo docker compose --env-file /etc/hmdm/hmdm.env -f /etc/hmdm/compose.yaml ps
- Application logs: sudo docker compose --env-file /etc/hmdm/hmdm.env -f /etc/hmdm/compose.yaml logs --tail 100 hmdm
- Database logs: sudo docker compose --env-file /etc/hmdm/hmdm.env -f /etc/hmdm/compose.yaml logs --tail 100 postgresql
- Proxy status: sudo systemctl status nginx
Scripts and log files
- /usr/local/sbin/hmdm-firstboot.sh: generates the per-VM secrets and enrolment base URL on first boot
- /usr/local/sbin/hmdm-admininit.sh: rotates the seeded administrator credential on first boot and opens the console
- /usr/local/sbin/hmdm-roundtrip.sh: proves the security model and a full database round trip
- /var/lib/sac/data/hmdm-firstboot.done: marker written once the per-VM secrets exist
- /var/lib/sac/data/hmdm-admininit.done: marker written once the administrator credential has been rotated
- journalctl -u hmdm-firstboot: first-boot secret generation log
- journalctl -u hmdm-admininit: administrator rotation log
On startup
The first boot of your VM runs two one-shot units in order.
hmdm-firstboot.servicegenerates a unique administrator password, device shared secret and database password. It resolves your VM's public address into the enrolment base URL and writes/etc/hmdm/hmdm.envand/root/headwind-mdm-credentials.txt.hmdm.servicethen starts the application, which creates its database schema.hmdm-admininit.servicefinally replaces the seededadmin/admincredential with your unique password. It verifies that the default is rejected and that your password works, then writes its marker.
Until that last marker exists, nginx answers every console route with HTTP 503 and a short explanatory message. This is deliberate. The upstream default credential is never reachable from the network, not even during the two to five minutes that your VM takes to initialise.
Both markers are removed before the image is captured, so every VM you launch repeats this process from a clean database.
Troubleshooting
- The console returns 503 with an initialising message: first boot has not finished. Wait a few minutes, then check journalctl -u hmdm-admininit -n 50.
- The console returns 502: the application container is still starting. Check sudo docker compose --env-file /etc/hmdm/hmdm.env -f /etc/hmdm/compose.yaml logs --tail 100 hmdm.
- You cannot reach the console from your browser: confirm that the network security group allows inbound 80/tcp. Then confirm that curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/ returns 200 on the VM.
- Your password is rejected: confirm that you are using the value from /root/headwind-mdm-credentials.txt. Headwind MDM applies a brief lockout after repeated failures, so wait a minute before retrying.
- Devices enrol against the wrong address: check BASE_DOMAIN in /etc/hmdm/hmdm.env, correct it and restart the stack. Devices keep the URL they were enrolled with.
- The stack will not start: confirm that the data disk is mounted with df -h /var/lib/hmdm-data, then check journalctl -u hmdm -n 80.
Security recommendations
- Change the administrator password from Settings in the console after your first sign in. Remove
/root/headwind-mdm-credentials.txtonce you have stored the value in your password manager. - Terminate TLS in front of the server. The image serves plain HTTP on port 80. Before you enrol production devices, put an Azure Application Gateway, an Azure Front Door or your own reverse proxy with a certificate in front of it. Set
BASE_DOMAINto that public name. - Restrict the network security group. Allow
22/tcponly from your management network. Allow80/tcponly from the networks your devices and administrators use. - Keep the device shared secret private. It is generated per VM and lives in
/etc/hmdm/hmdm.env, which is root only. Anything that can read it can impersonate a device. - Back up the data disk. Your fleet, configurations and uploaded applications live on
/var/lib/hmdm-data. Take Azure snapshots of that disk, or runpg_dumpagainst the database container. - Security patches continue to arrive automatically. Unattended upgrades remains enabled on the image.
Support
SecureAnyCloud provides 24/7 expert support for this image. Contact sales@secureanycloud.com.
Headwind MDM is open-source software licensed under the Apache License 2.0. SecureAnyCloud is not affiliated with or endorsed by Headwind MDM or h-mdm.com. The name is used only to identify the software included in this image.
Deploy on Azure
Launch Headwind MDM 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
Overview
Headwind MDM is an open-source Mobile Device Management (MDM) server for Android device fleets. It is a Java web application with an admin web console and a full REST API. You use it to enrol devices, push device configurations, applications and policies, group devices, and monitor an estate from a single console.
The SecureAnyCloud AMI ships the free and open-source, Apache-2.0-licensed Headwind MDM community server (the os build of hmdm-server). It runs the officially supported way on Apache Tomcat 9 inside a container image that bakes the application WAR. A bundled PostgreSQL 16 database, pinned by digest, keeps your fleet, configurations and history 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. Nothing here ships with a known secret. The administrator password is rotated to a random value on first boot. Each instance gets a unique random device shared secret and a unique random database password. The public enrolment URL is left customer-configurable rather than baked in.
What you get:
- 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-datathat holds the PostgreSQL data directory and the uploaded application and APK storage docker.service,hmdm-firstboot.service,hmdm.service,hmdm-admininit.serviceandnginx.serviceas systemd units, enabled and active on boot- A 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
- A public enrolment base URL that you can customise, with no domain baked in
- No default login, no shipped secret and an empty database on first boot
- An Ubuntu 24.04 LTS base with the latest security patches applied at build time
- 24/7 SecureAnyCloud support with a guaranteed 24-hour response SLA
Headwind MDM is a trademark of its respective owners. This image is produced by SecureAnyCloud 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.
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) and80/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.
- Ubuntu 24.04: the SSH login user is ubuntu. For example: ssh -i your-key.pem ubuntu@<instance-public-ip>
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 described in Prerequisites. Launch the instance and note its public IP address.
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, which is shown on the listing's launch page. Also replace the key name, subnet and security group with your own:
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}]'Connect to your instance
SSH in as the variant's login user, as listed under Connecting to your instance. For Ubuntu, run ssh -i your-key.pem ubuntu@<instance-public-ip>.
Confirm the services are running
The stack runs as two containers under one hmdm.service: the Headwind MDM server and its bundled PostgreSQL database. nginx fronts them.
Confirm that the services are active and list 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 psThe services report active, and the two containers show Up (and healthy). The server (127.0.0.1:8080) is bound only to the loopback interface. 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-dataRead the per-instance credentials
On the first boot of this instance, before the console was reachable, the administrator password was rotated to a random value. A random device shared secret and PostgreSQL password were also generated. All of them were written to a root-only file. Read the file:
sudo cat /root/headwind-mdm-credentials.txtThe file (mode 0600 root:root) holds HMDM_ADMIN_USERNAME (admin) and HMDM_ADMIN_PASSWORD. Together they are the account you use to sign in to the admin web console and the REST API. The file also holds this instance's console URL.
None of these values 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.
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. Run the commands below to confirm four things:
- The health endpoint is up.
- An unauthenticated request to a private API is rejected with
403. - The per-instance administrator credential authenticates a session.
- 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/listThe health endpoint returns 200 with no secret. The unauthenticated private call is rejected with 403. 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)). This proves that the server is live and that the credential reads real data from PostgreSQL.
This is the security model. The per-instance administrator credential (the admin user) gates the console and the REST API, and the built-in admin/admin account no longer exists.
The image also bundles a script at /usr/local/sbin/hmdm-roundtrip.sh that proves the whole model end to end. It confirms that the console is served and that an unauthenticated private request is rejected. It confirms that the per-instance credential authenticates and reads the configurations back from PostgreSQL. It also confirms that a wrong password is rejected. The build and smoke tests run it to prove that the credential works.
The script deliberately makes a rejected login attempt. Headwind MDM briefly delays logins after a failed attempt, so run the script on its own rather than immediately before an authenticated call.
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.
After you sign in, the console opens on the Devices tab. The Applications, Configurations, Files, Settings and Functions tabs run across the top.
The image ships one example device and two ready-made configurations, so you can explore immediately.
Review device configurations
Open the Configurations tab. A configuration is the policy and app set that applies 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. You can edit, copy or extend them. Everything you create here is stored in the durable PostgreSQL database.
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.
Add your own APKs or web links here, then assign them to configurations. Enrolled devices install and update them over the air.
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 URL to http://<instance-public-ip> so that the console works immediately. For a production fleet, 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 hmdmThe server regenerates its Tomcat context from the environment on every start. The enrolment base URL, the device shared secret and the database credentials therefore 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. Alternatively, use the QR-code action on a configuration in the console.
Real-time push to devices uses the MQTT broker on container port 31000. For instant configuration push, publish that port and open it in your security group. Otherwise, devices poll over HTTP.
Server components
The image is laid out as follows:
/etc/hmdm/hmdm.env: the per-instance environment file (0600 root:root) withBASE_DOMAIN,SQL_PASS,SHARED_SECRET,ADMIN_EMAILandHMDM_ADMIN_PASSWORD/etc/hmdm/compose.yaml: the Docker Compose file (thehmdmandpostgresqlservices)/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 the uploaded application and 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
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 100The hmdm.service unit brings the containers up with docker compose up -d --wait. PostgreSQL data persists on the dedicated volume across restarts.
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 and 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.comThen configure nginx (/etc/nginx/sites-available/hmdm) to listen on 443 with the issued certificate and proxy to 127.0.0.1:8080. Set PROTOCOL=https in /etc/hmdm/hmdm.env before you restart the server.
A load balancer that terminates TLS and forwards to port 80 needs no changes on the instance beyond setting BASE_DOMAIN.
Security recommendations
- Restrict
22/tcpto your administration network only - Expose
80/tcp(and31000/tcpfor 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_PASSWORDin/root/headwind-mdm-credentials.txtsecret. 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-datavolume - Keep the instance patched and subscribe to SecureAnyCloud update notifications
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 and does not bundle any paid or premium plugin.
SecureAnyCloud provides 24/7 technical support for this image by email and live chat at sales@secureanycloud.com. Support covers deployment, setting the enrolment URL, TLS termination, database backups, device enrolment, application management and scaling.
Headwind MDM is a trademark of its respective owners. SecureAnyCloud 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.
Deploy on AWS
Launch Headwind MDM Android Device Management Server with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com