hledger
Overview
This guide covers the deployment and configuration of hledger on Ubuntu 24.04 on Azure using SecureAnyCloud Azure Marketplace images. hledger is a mature open source plain text accounting system. Instead of a database, your entire ledger lives in a single human readable text file that you own, can read without any special software, can diff and can keep in version control. hledger reads that journal and produces real double entry accounting reports: balance sheets, income statements, account registers, budgets and forecasts.
The image installs the pinned hledger 1.52.1 release (recorded in /opt/hledger/VERSION) as fully static binaries, providing three interfaces over the same journal: the hledger command line tool, the hledger-ui terminal interface, and hledger-web, a browser interface run under systemd as the unprivileged hledger system user and bound to loopback only (127.0.0.1:5000). nginx is the sole network facing surface: it terminates TLS on port 443, enforces HTTP basic authentication on every request and reverse proxies to hledger-web. Port 80 redirects to HTTPS.
Secure by default, and it matters more here than for most applications. hledger-web ships with no authentication of any kind — the upstream manual states plainly that there is no access control and that it should be placed behind a suitable web proxy. Worse, its default access level permits anyone who can reach it to add transactions to your journal. This image never exposes it that way. hledger-web is bound to loopback and fronted by nginx basic authentication, and a unique administrator credential is generated on each virtual machine's first boot. Only a one way bcrypt hash of the password is written to the server; the plain password is placed in a root only file for the administrator to read. There are no shared or default credentials, and nothing usable is baked into the image.
The web interface is configured deliberately in append only mode: new transactions can be recorded from the browser, but existing history cannot be altered, deleted, uploaded over or downloaded. Step 11 explains how to change this.
What is included:
hledger 1.52.1, hledger-ui 1.52.1 and hledger-web 1.52.1 installed as fully static binaries in /usr/local/bin, with man pages and bash completion
hledger-web run under systemd as the unprivileged hledger system user, bound to loopback only (127.0.0.1:5000); it is never network exposed directly
nginx terminating TLS on :443, enforcing HTTP basic authentication and reverse proxying to hledger-web, with HSTS and sensible security headers
Port :80 returns a 301 redirect to HTTPS for every path except an unauthenticated /healthz endpoint (HTTP 200) for load balancer probes
A unique admin credential generated per VM on first boot; only the one way bcrypt hash is stored, the plain password lands in a root only file
A self signed TLS certificate regenerated per VM on first boot (its Subject Alternative Names include the VM public IP, hostname and 127.0.0.1); replace it with your own CA signed certificate for production
A documented starter journal at /var/lib/hledger/main.journal, readable only by the service account and root because it is financial data
The web interface set to append only (--allow=add) so recorded history cannot be rewritten from the browser
A built in self test at /usr/local/bin/hledger-selftest that proves the accounting engine, the authentication gate and the web interface end to end
The GNU GPL v3 licence text and a pointer to the corresponding source in /opt/hledger
Ubuntu 24.04 LTS base with the latest security patches applied at build time
Azure Linux Agent for seamless cloud integration and SSH key injection
24/7 SecureAnyCloud support with a guaranteed 24 hour response SLA
Prerequisites
You need:
- An Azure subscription with permission to deploy virtual machines
- An SSH key pair for administrative access to the VM as the
azureuseraccount - A Network Security Group that allows inbound TCP
443(HTTPS) and80(redirect) from the networks that should reach the service, and22(SSH) from your management network only - A VM size of Standard_B2s or larger (recommended)
Deploy from the Azure Portal
Find the hledger on Ubuntu 24.04 LTS image in the Azure Marketplace and select Create.
Choose your subscription, resource group and region.
Select a VM size (Standard_B2s or larger) and provide your SSH public key for the azureuser account.
On the Networking tab, allow inbound 443 and 80 from your users. Restrict 22 to your management network.
Click Review + create. When the VM is running, browse to https://<your-public-ip>/.
Deploy from the Azure CLI
Deploy the image from the command line, and restrict SSH to your management network. Replace the example values with your own before you run the commands:
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:hledger-ubuntu-24-04:default:latest
az vm create \
--resource-group my-resource-group \
--name hledger \
--image sac1647283583153:hledger-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_rsa.pub \
--public-ip-sku Standard
# Allow the web interface from your users and SSH from your management network only
az vm open-port --resource-group my-resource-group --name hledger --port 443 --priority 1001
az network nsg rule create --resource-group my-resource-group --nsg-name hledgerNSG \
--name allow-ssh --priority 1002 --destination-port-ranges 22 \
--source-address-prefixes <your-mgmt-cidr> --access Allow --protocol TcpWhen the VM is running, browse to https://<public-ip>/.
Retrieve your per-instance admin credential
HTTP basic authentication protects access. Each VM generates its own admin username and password on first boot and writes them to a root-only file. Read the file over SSH:
ssh azureuser@<vm-ip> 'sudo cat /root/hledger-credentials.txt'The file contains the HLEDGER_USER, HLEDGER_PASS and HLEDGER_URL for this specific VM. Keep it secret. The server stores only a one-way bcrypt hash of the password, never the plain text.
Sign in to the web interface
Browse to https://<vm-ip>/. The certificate is self-signed per VM, so your browser shows a certificate warning the first time. Accept it to proceed, or install the per-VM certificate or a CA-signed certificate as described in Step 12.
Your browser then prompts for the basic authentication username and password from Step 3.
You land on the journal view. The sidebar lists your accounts with their current balances. The main panel lists every transaction in the journal, newest first.
Understand the journal file
Everything hledger reports comes from one plain text file:
ssh azureuser@<vm-ip>
sudo -u hledger cat /var/lib/hledger/main.journalThe hledger service account owns the journal, with mode 0640 inside a 0750 directory. Only that account and root can read it. It is financial data, so it is deliberately not world-readable.
A transaction looks like this. Every transaction must balance to zero. If you leave the last amount blank, hledger infers it:
2026-01-08 * Client invoice 001 assets:bank:current 1200.00 GBP income:consultingYou create accounts simply by naming them. Colons make a hierarchy, so assets:bank:current sits under assets:bank, which sits under assets.
You do not need to pass -f to point hledger at this file. The image wires journal discovery in two ways. LEDGER_FILE is preset for interactive shells. hledger's own default path ($HOME/.hledger.journal) is symlinked to the journal for both the hledger service account and root.
As a result, sudo -u hledger hledger balance and sudo hledger balance both work from scripts, cron jobs and non-interactive sessions, not only from a login shell.
Run reports as the hledger account, which owns the ledger:
sudo -u hledger hledger balanceRecord a transaction
Append a transaction to the journal with any editor or from the shell. This example records a second client invoice:
sudo -u hledger tee -a /var/lib/hledger/main.journal >/dev/null <<'TXN' 2026-01-20 * Client invoice 002 assets:bank:current 850.00 GBP income:consulting TXNhledger also has a prompt-driven entry mode, which suggests accounts as you type:
sudo -u hledger hledger addAfter any edit, confirm the journal still parses:
sudo -u hledger hledger checkOn success, the command prints nothing. Because the file is plain text, a typo is a parse error that you can see and fix, not a corrupted database.
Run balance and register reports
The balance report totals every account. The register report shows the individual postings to an account with a running total. Both read the journal you just edited:
sudo -u hledger hledger balance
sudo -u hledger hledger register income:consultingThe new invoice shows up immediately. assets:bank:current has risen to 3976.55 GBP and income:consulting to -2050.00 GBP. Income and equity accounts show as negative under standard double-entry sign conventions.
Other reports worth knowing:
sudo -u hledger hledger balancesheet
# assets and liabilities, with net worth
sudo -u hledger hledger incomestatement
# revenues and expenses over the period
sudo -u hledger hledger accounts
# every account name in the journal
sudo -u hledger hledger stats
# journal size, date span, transaction countExplore an account in the browser
Click any account in the sidebar to open its register. The browser view adds a running-balance chart over the period alongside the postings table. This is the quickest way to see how an account moved over time.
The search box accepts hledger's query language, so you can narrow any view. For example, inacct:expenses shows only expense postings, date:2026-01 limits the view to January 2026, and amt:>500 filters by size.
Add a transaction from the browser
Select Add a transaction on the journal view. The form takes a date, a description and up to four account and amount pairs. If you leave the final amount blank, hledger infers the balancing posting, exactly as in the file.
Select Save. hledger appends the transaction to the same /var/lib/hledger/main.journal file, so a CLI hledger balance reflects it immediately.
There is no separate database to keep in step. The browser, the terminal and the file are three views of one journal.
Verify the deployment
Confirm that the services are running and that hledger-web is reachable only through nginx:
systemctl is-active hledger-web nginx hledger-firstboot ss -tlnp | grep -E ':5000 |:443 |:80 'hledger-web must appear on 127.0.0.1:5000 and nowhere else. nginx holds :80 and :443.
The authentication gate and the access level
Every path except the health probe requires the per-VM credential. Confirm it:
curl -sk -o /dev/null -w '%{http_code}\n' https://127.0.0.1/
# 401
curl -sk -o /dev/null -w '%{http_code}\n' https://127.0.0.1/journal
# 401
curl -sk -o /dev/null -w '%{http_code}\n' -X POST https://127.0.0.1/add
# 401
curl -sk -o /dev/null -w '%{http_code}\n' https://127.0.0.1/healthz
# 200The image runs hledger-web with --allow=add. This level permits recording new transactions. It never permits altering or deleting existing history, or uploading or downloading the journal file.
To change the level, add a systemd override instead of editing the shipped unit, so your choice survives any future package update. This example switches the browser interface to strictly read-only:
sudo install -d /etc/systemd/system/hledger-web.service.d
sudo tee /etc/systemd/system/hledger-web.service.d/10-access-level.conf >/dev/null <<'EOF' [Service] ExecStart= ExecStart=/usr/local/bin/hledger-web --serve --host=127.0.0.1 --port=5000 --allow=view --file=/var/lib/hledger/main.journal EOF
sudo systemctl daemon-reload
sudo systemctl restart hledger-webThe empty ExecStart= line is required. It clears the shipped command before the replacement is set.
The three useful values for --allow are:
--allow=view: strictly read-only. The browser becomes a report viewer. You can add transactions only by editing the file or using the CLI.--allow=add: the default on this image. It is append-only. You can record new transactions from the browser, but you cannot change existing history.--allow=edit: additionally permits editing, uploading and downloading the journal file. This is convenient, but a single bad upload replaces your entire ledger. Choose it only if you understand that risk.
To check which level is active at any time, run systemctl show -p ExecStart hledger-web | grep -o -- '--allow=[a-z]*'.
Replace the certificate for production
The per-VM certificate is self-signed. For production, point a real domain at the VM and install a CA-signed certificate:
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.example.comcertbot edits the nginx server block in place and sets up renewal. The basic authentication gate and the loopback binding are unaffected.
Back up your journal
The ledger is one plain-text file, so a backup is a file copy, and version control works properly:
# Point in time copy
sudo -u hledger cp /var/lib/hledger/main.journal \
/var/lib/hledger/main.journal.$(date +%F)
# Or track the journal in git, which gives you a full, diffable audit history cd /var/lib/hledger && sudo -u hledger git init && sudo -u hledger git add main.journal
sudo -u hledger git commit -m "January 2026 accounts"Copy the file off the VM regularly, for example to Azure Blob Storage, and take VM-level snapshots for disaster recovery. A journal committed to git gives you a reviewable change history.
Baked version and the built-in self-test
The image records the exact version it shipped. It also includes a self-test that proves the accounting engine, rather than merely checking that a port is open. The self-test:
- Computes a balance from a freshly written transaction.
- Confirms that the shipped journal reports correctly through the CLI.
- Checks that unauthenticated requests are refused.
- Confirms that the authenticated web interface serves the same data.
Check the version and run the self-test:
cat /opt/hledger/VERSION
sudo /usr/local/bin/hledger-selftestSecurity notes
- No default login. nginx HTTP basic authentication protects access, and each VM generates a unique admin credential on first boot. Only a one-way bcrypt hash is stored. The plain password lives in the root-only
/root/hledger-credentials.txt. - hledger-web has no authentication of its own. Upstream is explicit that it has no access control and must sit behind a proxy, and its default access level lets anyone who can reach it add transactions. This image binds it to
127.0.0.1:5000and puts nginx in front. Never change the--hostsetting to expose it directly. - Append-only by default. The web interface runs with
--allow=add, so you cannot rewrite or delete recorded history from the browser, and you cannot upload over the journal file. Review Step 11 before you widen this to--allow=edit. - Your journal is financial data. It has mode
0640and is owned by thehledgerservice account inside a0750directory, so it is not world-readable. Keep it that way. Anyone with the basic authentication credential can read the whole ledger. - Restrict access. Allow
443only from the networks that need it. Keep22restricted to your management network. Replace the self-signed certificate with a CA-signed one for production. Basic authentication credentials over plain HTTP would be exposed, which is why:80only redirects. - Rotate the credential by regenerating the nginx htpasswd entry (
sudo htpasswd -B /etc/nginx/hledger.htpasswd admin) and reloading nginx. Alternatively, place your own identity-aware proxy in front.
Support
This image is backed by 24/7 SecureAnyCloud support with a guaranteed 24 hour response SLA. Contact sales@secureanycloud.com for assistance. hledger is open source software distributed under the GNU General Public License version 3 or later and is free; the licence text and a pointer to the corresponding source are included on the image in /opt/hledger. The SecureAnyCloud charge covers packaging, hardening, security patching, image maintenance and support.
Deploy on Azure
Launch hledger 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
This image runs hledger 1.52.1, the open-source plain-text accounting toolset, on Ubuntu 24.04 LTS. Instead of a database, your entire ledger lives in a single human-readable text file, the journal. You own it, can read it without special software, can diff it and can keep it in version control.
hledger reads that journal and produces real double-entry accounting reports: balance sheets, income statements, account registers, budgets and forecasts.
What you get:
- Three programs installed to
/usr/local/binfrom the pinned upstream static release, verified by SHA-256:hledger(the command-line reporting tool),hledger-ui(a terminal curses interface) andhledger-web(a browser interface with journal, register and balance report views and an add-transaction form) - hledger-web run by an unprivileged
hledgersystem account under a systemd service. It binds the loopback address127.0.0.1:5000by design and is never exposed directly. - nginx on port 80 as the only network-facing surface. It enforces HTTP basic authentication on every path except an unauthenticated
/healthzprobe, and reverse-proxies authenticated requests to hledger-web. - A fresh per-instance admin credential, generated on the first boot of every deployed instance by a one-shot service and written to
/root/hledger-credentials.txt(mode0600, root only) - Only the one-way bcrypt hash of the password stored in the nginx
htpasswdfile, never the plaintext. No two instances share a credential, and no default password ships in the image. - A web UI configured append-only (
--allow=add). You can record new transactions from the browser, but existing history can never be altered, deleted, uploaded over or downloaded. This preserves the audit trail that plain-text accounting exists to provide. - Your journal at
/var/lib/hledger/main.journalon a dedicated, independently resizable EBS data volume that is separate from the operating system disk. Only the service account and root can read it, because it is financial data. - A default security group that opens port 22 (SSH) and port 80 (HTTP) only
- 24/7 SecureAnyCloud support
Secure by default
hledger-web ships with no access control of any kind. Its default access level lets anyone who can reach it append transactions to your journal. This image never exposes it that way. hledger-web is loopback-only, behind nginx basic authentication on every request.
HTTP by default. nginx serves plain HTTP on port 80, so your basic-auth credentials travel unencrypted. For anything beyond a quick trial, put the instance behind TLS. Terminate TLS at an AWS Application Load Balancer or Amazon CloudFront, or enable Let's Encrypt on the instance (see Step 8).
Prerequisites
You need:
- An AWS account subscribed to this product in AWS Marketplace
- An EC2 key pair in your target region for SSH access
- A security group that allows inbound TCP 22 (SSH) from your IP and TCP 80 (HTTP) from your users
- An instance type of
m5.largeor larger (recommended)
Connecting to your instance
Connect over SSH as the default login user for your operating system variant. Use the key pair that you launched with.
- OS variant: Ubuntu 24.04
- Login user: ubuntu
- Example: ssh -i your-key.pem ubuntu@<instance-public-ip>
Step 1 - Launch from the AWS Marketplace console
- Open the product page in AWS Marketplace. Choose Continue to Subscribe, then Continue to Configuration.
- Select the software version and your AWS Region, then choose Continue to Launch.
- Choose an instance type (
m5.largeor larger), your VPC subnet, the security group described above and your EC2 key pair. - Launch the instance. Wait until it reaches the running state and its status checks pass.
Step 2 - Launch from the AWS CLI (alternative)
Replace the AMI id, key name, security group and subnet with your own values:
aws ec2 run-instances \
--image-id ami-xxxxxxxxxxxxxxxxx \
--instance-type m5.large \
--key-name your-key \
--security-group-ids sg-xxxxxxxx \
--subnet-id subnet-xxxxxxxx \
--metadata-options "HttpTokens=required" \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=hledger}]'Step 3 - Retrieve the per-instance web credential
Connect to the instance over SSH and read the credentials file that first boot wrote. The file has mode 0600 and is owned by root, so read it with sudo:
sudo cat /root/hledger-credentials.txtThe file shows the unique URL, username and password generated for this instance:
HLEDGER_USER=admin HLEDGER_PASS=<set-at-first-boot> HLEDGER_URL=http://203.0.113.10/Step 4 - Confirm the services are healthy
hledger-web runs on loopback 127.0.0.1:5000, and nginx fronts it on port 80. Confirm that both services are active:
systemctl is-active hledger-web.service nginx.serviceactive activeConfirm the listeners. hledger-web is loopback-only on 5000, and nginx is on 80:
sudo ss -tlnp | grep -E ':80 |:5000 'LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",...)) LISTEN 0 2048 127.0.0.1:5000 0.0.0.0:* users:(("hledger-web",...))nginx itself serves the unauthenticated health endpoint, which returns HTTP 200:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthz200Every other path requires the credential. Unauthenticated access to the journal is refused with 401:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/journal401Step 5 - Sign in to hledger-web
Browse to http://<instance-public-ip>/. When your browser prompts for HTTP authentication, sign in with the username and password from Step 3.
You land on the journal view. The sidebar shows live account balances, and the general journal is on the right.
Click any account to open its register. The register shows every posting to that account, with a running balance and a period-total chart.
Choose Add a transaction to record a new entry. Enter a date and description, then the accounts and amounts. hledger infers a blank amount so that the transaction balances to zero.
Step 6 - Report from the command line
You can report on the same journal directly from the shell. The journal is financial data owned by the hledger service account, so run these commands with sudo:
sudo hledger balance3126.55 GBP assets:bank:current -2500.00 GBP equity:opening balances 123.45 GBP expenses:sac:verification 450.00 GBP expenses:office:rent -1200.00 GBP income:consulting -------------------- 0Show the running register for one account:
sudo -u hledger hledger register assets:bank:currentThe shipped self-test proves the appliance end to end. It computes a balance from a fresh transaction, checks that the live journal parses, confirms that unauthenticated access is refused, and confirms that the authenticated web UI serves the same journal:
sudo /usr/local/bin/hledger-selftestOK hledger self-test passed: ...Step 7 - Change the web access level (optional)
The web UI ships append-only (--allow=add). You can change the access level:
view: a strictly read-only report viewer. The browser can no longer record transactions.edit: additionally allows uploading, downloading and overwriting the whole journal file. Use it with care, because a single bad upload can replace your entire ledger.
To change the level, edit the service unit's --allow= flag and restart the service. Open the drop-in or the unit at /etc/systemd/system/hledger-web.service. Change --allow=add to --allow=view (or --allow=edit), then run:
- Reload systemd:
sudo systemctl daemon-reload - Restart the service:
sudo systemctl restart hledger-web.service
Step 8 - Enable HTTPS (recommended)
For anything beyond a quick trial, terminate TLS so that your basic-auth credentials and data are encrypted. There are two common options.
Option A: certbot on the instance. Point a DNS name at the instance and open port 443 in the security group. Then obtain a certificate. Run these commands interactively, because they prompt for your email and domain:
- Install the packages:
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx - Obtain and install the certificate:
sudo certbot --nginx -d your-domain.example.com
certbot edits the nginx site to listen on 443 with your certificate and sets up automatic renewal.
Option B: terminate TLS upstream. Place an AWS Application Load Balancer or Amazon CloudFront in front of the instance with an ACM certificate, and forward to port 80. This keeps certificate management off the instance.
Step 9 - Configuration and data locations
The key paths are:
- /var/lib/hledger/main.journal: your journal, the plain-text ledger and single source of truth, on a dedicated EBS data volume
- /root/hledger-credentials.txt: the per-instance web username and password generated on first boot (mode 0600)
- /etc/nginx/hledger.htpasswd: the nginx basic-auth file, which stores only the bcrypt hash of the password
- /etc/nginx/sites-available/sac-hledger: the nginx reverse-proxy site configuration
- /etc/systemd/system/hledger-web.service: the hledger-web service unit (host, port and --allow= access level)
- /opt/hledger/LICENSE, /opt/hledger/SOURCE-OFFER.txt: the GPL-3.0 licence text and the corresponding-source offer
Confirm the journal is on its own volume:
df -h /var/lib/hledger | tail -1/dev/nvme1n1 9.8G 40K 9.3G 1% /var/lib/hledgerStep 10 - Backup and maintenance
- Back up the journal. The journal is a single plain-text file. Copy
/var/lib/hledger/main.journalto off-instance storage, keep it in a private git repository, or take regular EBS snapshots of the/var/lib/hledgervolume. - Rotate the web credential. To set a new password, regenerate the htpasswd entry with
sudo htpasswd -B /etc/nginx/hledger.htpasswd admin. The command prompts for the new password. Then update your own records. The stored value is always a bcrypt hash. - Keep the OS patched. Apply Ubuntu security updates regularly with
sudo apt-get update && sudo apt-get upgrade. - Resize storage. The journal lives on a dedicated EBS volume. Grow it with the AWS console or CLI, then extend the filesystem. The OS disk is unchanged.
Support
SecureAnyCloud provides 24/7 technical support for this image by email and live chat. Support covers hledger deployment, recording transactions, running balance and register reports, importing data, changing the web access level, TLS termination, backups and scaling.
hledger is licensed under the GNU General Public License version 3 or later. The licence text and a pointer to the corresponding source are included on the image at /opt/hledger. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.
Deploy on AWS
Launch hledger on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com