Deployment guide

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 azureuser account
  • A Network Security Group that allows inbound TCP 443 (HTTPS) and 80 (redirect) from the networks that should reach the service, and 22 (SSH) from your management network only
  • A VM size of Standard_B2s or larger (recommended)
1

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>/.

2

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 Tcp

When the VM is running, browse to https://<public-ip>/.

3

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.

4

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.

5

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.journal

The 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:consulting

You 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 balance
6

Record 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 TXN

hledger also has a prompt-driven entry mode, which suggests accounts as you type:

sudo -u hledger hledger add

After any edit, confirm the journal still parses:

sudo -u hledger hledger check

On 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.

7

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:consulting

The 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 count
8

Explore 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.

9

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.

10

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.

11

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
# 200

The 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-web

The 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]*'.

12

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.com

certbot edits the nginx server block in place and sets up renewal. The basic authentication gate and the loopback binding are unaffected.

13

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.

14

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-selftest

Security 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:5000 and puts nginx in front. Never change the --host setting 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 0640 and is owned by the hledger service account inside a 0750 directory, so it is not world-readable. Keep it that way. Anyone with the basic authentication credential can read the whole ledger.
  • Restrict access. Allow 443 only from the networks that need it. Keep 22 restricted 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 :80 only 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

Deploy on Azure