BHIMA 1.41.1 on AWS User Guide
Overview
This guide covers launching and using the BHIMA 1.41.1 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened EHRbase & GIS, Health and Research Platforms AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
BHIMA (Basic Hospital Information Management Application, by IMA World Health and Third Culture Software) is a free and open source hospital information system for clinics and hospitals: patient registration and visits, invoicing and cash, debtor groups and insurers, inventory and stock, and a full double-entry accounting system (OHADA chart of accounts, fiscal years, journals, reports).
The cloudimg image adds:
- No demo database and no default logins. BHIMA's development database ships test patients and the
superuser/superuserlogin. This image ships an empty MySQL data directory: your instance's first boot creates its own database from BHIMA's release schema, one enterprise and project, and theadminaccount with a random 24-character password hashed with argon2, before any public port opens.admin/adminandsuperuser/superuserare refused, and the web installer (/install) is closed. - Secrets of your instance's own, never on a command line. The admin password, the database password, BHIMA's session secret, the two session-store account passwords and the TLS key are generated on your instance; nothing from the image build is reused. They live in files only root can read (BHIMA's
.envis readable by root and thebhimagroup), never in a process's arguments or environment. - The current, verified release. BHIMA 1.41.1 is checked file for file against the official release archive, pinned by its checksum, with its lockfile pinned too. No source file is patched. Node.js 24.22.2 LTS comes from the official nodejs.org tarball, pinned by checksum.
- HTTPS from the first boot. nginx serves BHIMA on port 443 with a certificate generated on your instance; port 80 redirects to HTTPS and a request for an unknown host name gets no answer. BHIMA itself listens only on a local socket behind nginx.
- Sessions that end when you sign out. The session cookie is
Secure,HttpOnlyandSameSite=Strict. BHIMA also issues a 24-hour API token at sign in that sign-out does not revoke; nginx strips that header, so only the session cookie is honoured and signing out ends access. - A private, password-protected session store. Sessions live in Valkey 7.2 (the open source Redis successor, replacing the
redis-serverBHIMA's documentation suggests) in memory only, with no network listener at all: Valkey listens on the socket/run/valkey/valkey.sockthat only thevalkeyandbhimaaccounts can open. Valkey's default (passwordless) user is off; BHIMA signs in as the accountbhimawith a password generated on your instance, and that account may run only the session-store commands on session keys. - A rate-limited sign in. Sign in is limited to 10 attempts a minute per address (after a burst of 10), in any spelling of the address; nginx sets the client address from the connection, so a forged
X-Forwarded-Forcannot dodge it. - Reports and sign-out not usable from other sites. Report downloads and sign-out require same-site proof from the browser, so a link or image on another site cannot run a report or sign you out.
- Safe uploads. Patient documents and pictures are served only to signed-in users, with
nosniffand a sandboxing content security policy; anything that is not a passive type (image or PDF) is sent as a download. - Report PDFs from a sandboxed Chrome that never calls out. BHIMA renders PDFs with Chrome. The image installs Chrome for Testing 157.0.8103.38 (pinned by checksum) instead of letting the npm package download one, keeps Chrome's own sandbox on (under an AppArmor profile), starts it with a clean environment so it never sees BHIMA's secrets, and stops it resolving any host name but
localhost, so a report never reaches the internet. - Hardened defaults. BHIMA runs as its own
bhimaaccount that can write only/var/lib/bhima(its uploads and Chrome's profile); the code, configuration and Chrome are read only to it. MySQL listens on 127.0.0.1 only, with no X Protocol, no binary or general log andLOCAL INFILEoff; itsrootaccount signs in only through the socket as the operating-system root. BHIMA, nginx, MySQL, Valkey and the first boot service are blocked from the instance metadata service, and the instance requires IMDSv2. - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: BHIMA 1.41.1, Node.js 24.22.2, Chrome for Testing 157.0.8103.38, MySQL 8.0, Valkey 7.2.13, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
- A security group that allows TCP 22 (SSH), TCP 443 (BHIMA) and TCP 80 (redirects to HTTPS) from the networks that should reach them. BHIMA holds patient and financial records: keep 22 to your own networks and open 443 only to the networks your staff work from.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick BHIMA 1.41.1 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above.
With the AWS CLI (replace the values in angle brackets):
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=bhima}]'
First boot takes about a minute after the instance is running (it builds BHIMA's database).
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| BHIMA 1.41.1 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key>.pem ubuntu@<public-ip-or-dns>
Step 3: Check the services
MySQL, Valkey, BHIMA and nginx should all be active:
systemctl is-active mysql valkey-server bhima nginx
active
active
active
active
Only SSH, HTTP and HTTPS listen on the network. MySQL listens on 127.0.0.1, and BHIMA and Valkey have no TCP listener at all (each uses a local socket):
sudo ss -tlnH | awk '{print $4}' | sort -u
0.0.0.0:22
0.0.0.0:443
0.0.0.0:80
127.0.0.1:3306
127.0.0.53%lo:53
127.0.0.54:53
[::]:22
[::]:443
[::]:80
Step 4: Get your sign-in details
The credentials file holds the address BHIMA is served as (your instance's public DNS name by default), the admin user name and its password, and the project to choose at sign in, all generated on this instance:
sudo cat /root/bhima-credentials.txt
# BHIMA on this instance (cloudimg). Root-only (0600). Generated at first boot: 2026-11-07T14:02:11Z
BHIMA_URL=https://ec2-54-172-234-205.compute-1.amazonaws.com/
BHIMA_ADMIN_USER=admin
BHIMA_ADMIN_PASSWORD=<BHIMA_ADMIN_PASSWORD>
BHIMA_PROJECT=Main Project
# The MySQL password of the bhima database user is in /etc/bhima-cloudimg/db-password (root 0600) and in BHIMA's
# /opt/bhima/bin/.env (root:bhima 0640); MySQL root is reachable only as the operating-system root (sudo mysql).
# Change a password: sudo bhima-cloudimg-set-password [user] Change the hostname: sudo bhima-cloudimg-set-hostname <name>
Step 5: Sign in
Open BHIMA_URL in a browser. The certificate is self signed, so the browser warns once; continue to the site (Step 11 replaces it with your own). Visiting the instance by its IP address redirects to the configured name.
BHIMA's interface starts in French. Choose your language from the small menu at the top right of the sign-in box (the choice is kept in your browser).

Sign in as admin with the password from the credentials file and the project Main Project. BHIMA opens on its home page; the module tree is on the left (the arrows at the bottom left open and close it).

A new instance has one enterprise, its project and the admin account only, so set up the basics first:
- Administration > Enterprise: set your organisation's name, address and currency.
- Finance > Account Management: import BHIMA's default OHADA chart of accounts (or your own) from Menu.
- Finance > Fiscal Year: create the current fiscal year.
- Add your services (Administration), at least one debtor group (Finance) and your billable items (Inventory).
- Administration > Users: create an account for each member of staff with only the roles they need, and keep
adminfor administration.
Then register a patient (Hospital > Patient Registration), open a visit and raise an invoice (Finance > Patient Invoice). They appear in the Patients Registry and the Invoice Registry, and in the reports. The screenshots below show a test patient and a consultation invoice.



Step 6: Check HTTPS and the setup state
Port 80 redirects to HTTPS on the configured name:
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://127.0.0.1/
301 https://ec2-54-172-234-205.compute-1.amazonaws.com/
BHIMA's installer reports the instance as installed and refuses to run again, so nobody can take it over:
H=$(sed -n 's/^set \$bhima_host "\(.*\)";$/\1/p' /etc/nginx/bhima-host.conf)
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/install"; echo
curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w 'second install: %{http_code}\n' -H 'Content-Type: application/json' -d '{}' "https://$H/install"
{"isInstalled":true}
second install: 400
Step 7: Your data
BHIMA keeps its records in the MySQL database bhima and its uploaded patient documents and pictures in /var/lib/bhima/data/uploads. Back up both, for example:
sudo mysqldump --single-transaction --routines --triggers bhima | gzip > bhima-$(date +%F).sql.gz
sudo tar -C /var/lib/bhima/data -czf bhima-uploads-$(date +%F).tar.gz uploads
Keep backups private and encrypted: they hold patient and financial records. The session store holds only sessions (in memory, never on disk), so it needs no backup.
Step 8: Sessions and the session store
BHIMA 1.41.1 keeps a session until the user signs out (upstream turns off the session store's expiry). Sign out at the end of each working session, and use Step 13 to sign everyone out at once.
Valkey has no network listener; its socket is open to the valkey and bhima accounts only, and even then a client must sign in:
sudo stat -c '%a %U:%G %n' /run/valkey /run/valkey/valkey.sock /etc/valkey/cloudimg-bhima-users.acl /etc/bhima-cloudimg/valkey-password
sudo -u ubuntu valkey-cli -s /run/valkey/valkey.sock PING 2>&1 | head -1
sudo -u bhima valkey-cli -s /run/valkey/valkey.sock PING 2>&1 | head -1
2750 valkey:bhima /run/valkey
770 valkey:bhima /run/valkey/valkey.sock
640 root:valkey /etc/valkey/cloudimg-bhima-users.acl
600 root:root /etc/bhima-cloudimg/valkey-password
Could not connect to Valkey at /run/valkey/valkey.sock: Permission denied
NOAUTH Authentication required.
Valkey's account file holds only SHA-256 hashes of the two account passwords: bhima (BHIMA's own, session commands on session keys only) and bhima-admin (root's, used by bhima-cloudimg-set-password to find and remove sessions). The passwords are in /etc/bhima-cloudimg/valkey-password and valkey-admin-password (root only). BHIMA receives its password as a systemd credential.
BHIMA 1.41.1 connects to its session store by host and port only and has no setting for a socket or a password. The image adds a small preload, /usr/local/lib/cloudimg/bhima/valkey-client.js (started with node --require), that points BHIMA's client at the socket and signs it in as bhima; it lives outside BHIMA's release folder, which stays identical to the 1.41.1 release. Keep it when you update BHIMA (Step 10).
Step 9: Known limits of BHIMA 1.41.1
These are upstream and have no fix in this release; the image's defaults reduce their reach:
- AngularJS 1.8.3 and Bootstrap 3.4.1. BHIMA's browser interface is built on AngularJS (end of life since January 2022) and Bootstrap 3, with angular-sanitize, which carry moderate npm advisories without fixed versions. The two AngularJS advisories rated high (an
ng-srcsetregular-expression slowdown and an SCE resource-URL check) need features BHIMA does not use. Use a current browser and give staff accounts only the roles they need. - Other moderate and low advisories reported by
npm auditfor BHIMA's dependencies (for exampleuuidanddeepmerge) are in server-side code paths BHIMA does not expose to unauthenticated users. The high-ratedbasic-ftp(only used by the npm package's own Chrome download, which the image skips) andimage-size(only in code paths BHIMA never calls) are not reachable. - No server-side session expiry (Step 8), and the API token BHIMA issues at sign in stays valid for 24 hours; nginx strips it, so it cannot be used through nginx.
Step 10: Updating BHIMA
BHIMA is installed from its release archive in /opt/bhima/bin (owned by root) and runs from there. To move to a newer release, follow BHIMA's upgrade notes for that release after a snapshot and a database backup (Step 7): unpack the new archive in place of the old one, keep /opt/bhima/bin/.env, run npm ci --omit=dev --ignore-scripts with PUPPETEER_SKIP_DOWNLOAD=1, run the release's database migration, then restart BHIMA. Keep the preload (Step 8): BHIMA's systemd unit starts it, so a release that keeps BHIMA's session client keeps working. Check the release notes for a newer Chrome requirement.
Keep the operating system patched with sudo apt-get update && sudo apt-get upgrade (MySQL, Valkey and nginx are Ubuntu packages); unattended security upgrades are already on.
Step 11: Your own DNS name and certificate
Point a DNS record at the instance, then set the name BHIMA is served as. This mints a new self signed certificate for the name, pins it in nginx (the instance's IP addresses redirect to it) and records it in the credentials file:
sudo bhima-cloudimg-set-hostname <your-domain>
To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running bhima-cloudimg-set-hostname again replaces it with a self signed one):
sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/bhima.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/bhima.key
sudo nginx -t && sudo systemctl reload nginx
Once browsers trust the certificate you can also add an HSTS header in nginx. The image does not send one: browsers ignore HSTS from a site whose certificate they do not trust.
Behind a load balancer or CloudFront: terminate TLS on it and forward to port 443 on the instance with the configured host name. nginx always uses the connection's address as the client address, so with a proxy in front every request counts against the proxy's sign-in budget; raise the limit in /etc/nginx/conf.d/cloudimg-bhima-guard.conf if that is too tight.
Step 12: Security checks
The admin password signs in over HTTPS, the session works, sign-out ends it (the old cookie is refused) and a wrong password is refused:
sudo bhima-cloudimg-cred-roundtrip
BHIMA_LOGIN_OK https 200 200 401 401
Without a session, BHIMA's API answers nothing, in any spelling of the address; the upstream default logins are refused:
H=$(sed -n 's/^set \$bhima_host "\(.*\)";$/\1/p' /etc/nginx/bhima-host.conf)
R=(curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null)
for p in /patients /PATIENTS /patients/ /users/1 /reports/finance/invoices; do "${R[@]}" -w "$p %{http_code}\n" "https://$H$p"; done
"${R[@]}" -w 'superuser/superuser %{http_code}\n' -H 'Content-Type: application/json' -d '{"username":"superuser","password":"superuser","project":1}' "https://$H/auth/login"
"${R[@]}" -w 'unknown host %{http_code}\n' -H 'Host: other.example' https://127.0.0.1/ ; echo
/patients 401
/PATIENTS 401
/patients/ 401
/users/1 401
/reports/finance/invoices 403
superuser/superuser 401
unknown host 000
(000 means nginx closed the connection without an answer. 403 on /reports is nginx itself: report and sign-out requests must come from the BHIMA page, with a matching Origin or Referer, before they reach BHIMA.) BHIMA, nginx, MySQL and Valkey are blocked from the instance metadata service (systemd may list the four addresses in a different order):
systemctl show -p IPAddressDeny bhima nginx mysql valkey-server
IPAddressDeny=::ffff:169.254.169.254/128 168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
IPAddressDeny=::ffff:169.254.169.254/128 168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
IPAddressDeny=::ffff:169.254.169.254/128 168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
IPAddressDeny=::ffff:169.254.169.254/128 168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
The credentials and passwords are readable by root only, BHIMA's .env by root and the bhima group, and the code belongs to root:
sudo stat -c '%a %U:%G %n' /root/bhima-credentials.txt /etc/bhima-cloudimg/db-password /etc/bhima-cloudimg/valkey-admin-password /opt/bhima/bin/.env /opt/bhima/bin/server/app.js /var/lib/bhima
600 root:root /root/bhima-credentials.txt
600 root:root /etc/bhima-cloudimg/db-password
600 root:root /etc/bhima-cloudimg/valkey-admin-password
640 root:bhima /opt/bhima/bin/.env
644 root:root /opt/bhima/bin/server/app.js
700 bhima:bhima /var/lib/bhima
Step 13: Passwords and signing everyone out
bhima-cloudimg-set-password gives a BHIMA user (by default admin) a new random password hashed with argon2, signs out every session in the session store and records the password in /root/bhima-credentials.txt. It first checks that the session store answers; if it does not, it changes nothing and says so, and if any session survives it fails with an error. Run it with a password on standard input to choose your own (12 to 96 letters and digits):
sudo bhima-cloudimg-set-password
printf '%s\n' '<new-password>' | sudo bhima-cloudimg-set-password admin --stdin
bhima-cloudimg-set-password: the password of 'admin' was changed and every session signed out (4 revoked, 0 remain)
Step 14: Server components
| Component | Path |
|---|---|
| BHIMA 1.41.1 release (owned by root) | /opt/bhima/bin |
| BHIMA settings (database password, session secret; root:bhima 0640) | /opt/bhima/bin/.env |
Uploads and Chrome's profile (owned by bhima) |
/var/lib/bhima |
| BHIMA service | /etc/systemd/system/bhima.service |
| Session store client preload | /usr/local/lib/cloudimg/bhima/valkey-client.js |
Valkey settings (included last from valkey.conf) and account file |
/etc/valkey/cloudimg-bhima.conf, /etc/valkey/cloudimg-bhima-users.acl |
| Passwords (root only) | /etc/bhima-cloudimg/ |
| Chrome for Testing and its wrapper | /opt/bhima/chrome, /usr/local/lib/cloudimg/bhima-chrome |
| Database (MySQL datadir) and settings | /var/lib/mysql, /etc/mysql/mysql.conf.d/99-cloudimg-bhima.cnf |
| nginx site, request limits and cross-site rules | /etc/nginx/sites-available/cloudimg-bhima, /etc/nginx/conf.d/cloudimg-bhima-guard.conf, /etc/nginx/conf.d/cloudimg-xsite-guard.conf |
| Certificate and key | /etc/nginx/ssl/ |
| First boot and helpers | /usr/local/sbin/bhima-firstboot, bhima-cloudimg-set-hostname, bhima-cloudimg-set-password, bhima-cloudimg-cred-roundtrip |
| Licence and image notes | /usr/share/doc/bhima-cloudimg/ |
| Credentials | /root/bhima-credentials.txt (mode 0600) |
Step 15: Security recommendations
- Allow port 22 in the security group from your own networks only, and 443 only from the networks your staff use.
- Keep the admin password secret (Step 13), give each member of staff their own account with the least roles they need, and sign out at the end of each session.
- Use your own DNS name and a certificate your browsers trust (Step 11).
- Back up the database and the uploads regularly, encrypted (Step 7).
- Keep BHIMA on the newest release (Step 10) and the operating system patched.
Support and licensing
BHIMA is free software under the GNU General Public License version 2 (a copy is at /usr/share/doc/bhima-cloudimg/LICENSE.bhima); the bundled libraries, Chrome for Testing, Node.js, MySQL, Valkey and nginx carry their own open source licences. There is no per user or per CPU licence fee. This image is built and supported by cloudimg; BHIMA is developed by IMA World Health and Third Culture Software, and cloudimg is not affiliated with them.
- Email: support@cloudimg.co.uk