Deployment guide

hledger 1.52.4

Overview

This guide covers launching and using the hledger 1.52.4 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud accounting and HR software listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.

hledger is open source plain-text accounting. Your whole ledger is one human-readable text file, the journal. Every transaction in it is a few lines of dated, balanced double-entry postings. hledger reads that file and produces balance sheets, income statements, registers, budgets and multi-currency reports.

There is no database to administer. The file is the single source of truth, it diffs cleanly, and it can live in version control. The image ships the three upstream programs: the hledger command line, the hledger-ui terminal interface and the hledger-web browser interface. hledger-web shows the journal, account registers with balance charts, and a form to add transactions.

This image is a hardened, ready-to-use hledger server.

What you get:

  • The upstream release. The official hledger 1.52.4 static Linux binaries, verified against the release's SHA-256, with their manual pages, info pages and bash completion. Nothing is recompiled. The GPL license text and the source offer are installed beside them.
  • hledger-web is never exposed directly. hledger-web has no access control of its own. On this image it listens on a private Unix socket (/run/hledger-web/hledger-web.sock), not on any TCP port, and only nginx can connect to it.
  • No default login. nginx requires a username and password for every page and API call. First boot creates one account, admin, with a random password unique to your instance, and stores only its bcrypt hash for nginx. Until first boot has finished, neither hledger-web nor nginx starts, so the instance never serves an open journal.
  • HTTPS from the first boot. nginx serves hledger-web on 443 with a certificate generated on your instance (an end-entity certificate, not a certificate authority). Port 80 redirects to HTTPS. The server's name is set from the instance's EC2 public DNS name at first boot and requests for any other name are dropped. You can change it to your own domain.
  • Append-only web access. hledger-web runs with --allow=add: signed-in users can view reports and add transactions, but cannot edit, upload or download the journal file through the browser. Writes from other web sites are refused (protection against cross-site request forgery), and nginx passes hledger-web the real client address from the connection, never a header a client sends.
  • Metadata service blocked. hledger-web, nginx and the first-boot service cannot reach the EC2 instance metadata service. The AMI requires IMDSv2.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: hledger, hledger-ui and hledger-web 1.52.4 (GPL-3.0-or-later), nginx, apache2-utils (for htpasswd), cloud-init and the AWS Systems Manager agent, all from Ubuntu 24.04 except hledger.

Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large (2 vCPUs, 8 GiB).

hledger is light. A t3.small is enough for a personal or small business ledger.

Prerequisites

Before you start, have the following ready:

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet.
  • A security group that allows TCP 22 (SSH) from your own networks only, TCP 443 from wherever your users are, and TCP 80 (redirects to HTTPS). No other port is needed, because hledger-web has no network port.
  • A DNS name you control (for example ledger.example.com) and, ideally, a certificate from a certificate authority. Until then, the instance uses its EC2 public DNS name with its own self-signed certificate.

Connecting to your instance

Operating system: Ubuntu 24.04. SSH user: ubuntu.

1

Launch the instance

Launch from the AWS Marketplace page and choose the hledger 1.52.4 on Ubuntu 24.04 version. Alternatively, launch with the AWS CLI, using the AMI ID shown on the listing's launch page:

aws ec2 run-instances --image-id ami-xxxxxxxxxxxxxxxxx --instance-type m5.large \
  --key-name my-key --security-group-ids sg-xxxxxxxx --subnet-id subnet-xxxxxxxx \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=hledger}]'

First boot takes a few seconds once the instance is up. It creates the certificate and the administrator password. The web interface stays offline until that has finished.

2

Connect to your instance

ssh -i my-key.pem ubuntu@<public-ip>
3

Check the services

systemctl is-active hledger-web.service nginx.service
sudo ss -ltnp | awk 'NR==1 || /:(22|80|443) /'
sudo ss -lxp | grep hledger-web
active active State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=3464,fd=5),("nginx",pid=3463,fd=5),("nginx",pid=3462,fd=5)) LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=892,fd=3),("systemd",pid=1,fd=112)) LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=3464,fd=7),("nginx",pid=3463,fd=7),("nginx",pid=3462,fd=7)) LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=3464,fd=6),("nginx",pid=3463,fd=6),("nginx",pid=3462,fd=6)) LISTEN 0 4096 [::]:22 [::]:* users:(("sshd",pid=892,fd=4),("systemd",pid=1,fd=113)) LISTEN 0 511 [::]:443 [::]:* users:(("nginx",pid=3464,fd=8),("nginx",pid=3463,fd=8),("nginx",pid=3462,fd=8)) u_str LISTEN 0 128 /run/hledger-web/hledger-web.sock 18935 * 0 users:(("hledger-web",pid=3449,fd=13))

Only SSH, HTTP and HTTPS listen on the network. hledger-web listens only on its Unix socket.

4

Get your address and the administrator password

First boot writes everything that is unique to your instance to a file only root can read:

sudo cat /root/hledger-credentials.txt
# hledger 1.52.4 - written on first boot by hledger-firstboot.service. Unique to this instance. Keep it secret. HLEDGER_URL=https://ec2-52-87-182-134.compute-1.amazonaws.com/ HLEDGER_TLS_SHA256=F7:11:BA:F6:EE:79:29:16:65:D2:DD:B5:76:38:24:C5:10:48:4B:CD:25:71:DC:29:AD:5B:53:93:7F:FF:90:38 HLEDGER_USER=admin HLEDGER_PASS=<40 random hexadecimal characters> ...
  • HLEDGER_URL is your hledger address.
  • HLEDGER_TLS_SHA256 is the fingerprint of the instance's own certificate. Your browser warns that the certificate is not trusted. Compare the fingerprint it shows with this one before you continue. Replace the certificate with a certificate from a certificate authority for your own name when you can (Step 12).
  • HLEDGER_USER / HLEDGER_PASS sign in to hledger-web. This is the only account.
5

Run the self-test

The image includes an end-to-end test. It runs these checks:

  • hledger's arithmetic on a scratch journal
  • The live journal parses, and its balance and register agree
  • Every page refuses requests without the password or with a wrong one
  • A sign-in over HTTPS reads the journal
  • A write from another web site is refused

Run the test:

sudo hledger-selftest
hledger self-test passed: computed 743.25 from a fresh transaction; assets:bank:current balance = register (3126.55); health 1.52.4 via the socket; every path 401 without the credential and with a wrong one; hledger-web serves the same journal over pinned HTTPS; cross-origin POST 403; edit capability not granted HLEDGER_SELFTEST_OK
6

Sign in to the web interface

Open HLEDGER_URL in your browser, check the certificate fingerprint, and sign in as admin with the password from Step 4. The journal view lists every transaction, and the sidebar shows every account with its balance:

To change the password, run the command below on the instance and type the new password (at least 12 characters) at the prompt. The password is read from the terminal, never from the command line, and nginx stores only its bcrypt hash:

# Type <new-password> at the prompt; nothing is echoed. read -rs -p 'New hledger password: ' P; echo printf '%s\n' "$P" | sudo hledger-set-password admin; unset P

After you store the password somewhere safe, you can delete the HLEDGER_PASS line from /root/hledger-credentials.txt.

7

Understand the journal file

Everything hledger reports comes from one plain text file, /var/lib/hledger/main.journal:

sudo -u hledger cat /var/lib/hledger/main.journal

The journal is owned by the hledger service account with mode 0640 inside a 0750 directory, so only that account and root can read it.

A transaction looks like this. Every transaction must balance to zero, and leaving the last amount blank lets hledger infer it:

2026-01-08 * Client invoice 001 assets:bank:current 1200.00 GBP income:consulting

Accounts are created simply by naming them, and colons make a hierarchy, so assets:bank:current sits under assets:bank, which sits under assets. The sample entries give the instance data to report on. Delete them when you start recording your own transactions.

You do not need to pass -f to hledger. LEDGER_FILE is set for login shells and hledger's default path (~/.hledger.journal) is linked to the journal for root and the hledger account.

8

Record a transaction from the command line

Append a transaction 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
sudo -u hledger hledger check && echo "journal OK"
journal OK

hledger check prints nothing else when the journal is valid. Because the file is plain text, a typo is a parse error you can see and fix, not a corrupted database.

hledger also has an interactive entry mode that suggests accounts as you type. Run sudo -u hledger hledger add in your SSH session.

9

Run balance and register reports

The balance report totals every account. The register report shows the postings to an account with a running total:

sudo -u hledger hledger balance
sudo -u hledger hledger register income:consulting
3976.55 GBP assets:bank:current -2500.00 GBP equity:opening balances 123.45 GBP expenses:sac:verification 450.00 GBP expenses:office:rent -2050.00 GBP income:consulting -------------------- 0 2026-01-08 Client invoice 001 income:consulting -1200.00 GBP -1200.00 GBP 2026-01-20 Client invoice 002 income:consulting -850.00 GBP -2050.00 GBP

The new invoice is reflected immediately. Income and equity accounts show as negative under the standard double-entry sign convention. Other useful reports:

sudo -u hledger hledger balancesheet
sudo -u hledger hledger incomestatement
sudo -u hledger hledger accounts
sudo -u hledger hledger stats

hledger-web re-reads the journal when the file changes, so the browser shows the new transaction on its next page load.

10

Explore accounts in the browser

Select any account in the sidebar to open its register, with a running balance chart above the postings:

The search box takes hledger's query language: inacct:expenses shows transactions touching expense accounts, date:2026-01 limits to January 2026 and amt:>500 filters by size.

11

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. Leave the last amount blank to let hledger infer it. Select Save to append the transaction to the same /var/lib/hledger/main.journal, so hledger balance shows it at once.

The web interface is append-only on this image. It can add transactions but cannot edit or replace the journal. Make corrections in the file itself (Step 8), where every change is visible.

hledger-web also has a JSON API behind the same password, for example /accounts, /transactions and /accountnames. From your own machine, curl -u admin https://<your-domain>/accounts prompts for the password and returns the account tree.

12

Use your own domain and certificate

Point a DNS name at the instance. An Elastic IP is a good idea. Then run:

sudo hledger-set-hostname <your-domain>

This command renders nginx for the new name (the instance's other names redirect to it) and sets hledger-web's base URL to it. It also creates a new self-signed certificate for the name and updates the URL and fingerprint in /root/hledger-credentials.txt.

To use a certificate from a certificate authority, replace /etc/ssl/hledger/hledger.crt and hledger.key (keep the key 0600 root) and run sudo systemctl reload nginx.

13

Restrict hledger to your networks

Every page uses the access list in /etc/nginx/sac/hledger-access.conf. By default it is allow all;, and every page still needs the password. To allow only your own networks, replace its contents with your addresses and a final deny all;:

sudo tee /etc/nginx/sac/hledger-access.conf >/dev/null <<'EOF' allow <your-mgmt-cidr>; deny all; EOF
sudo nginx -t && sudo systemctl reload nginx

Everyone else then gets 403 Forbidden. hledger-set-hostname never overwrites this file. You can also restrict TCP 443 in the security group.

14

Check the security posture

U=$(sudo sed -n 's/^HLEDGER_URL=//p' /root/hledger-credentials.txt) for p in "" journal register accounts add; do curl -s --cacert /etc/ssl/hledger/hledger.crt -o /dev/null -w "GET /$p without a password: %{http_code}\n" "${U}$p" done WRONG="admin:$(openssl rand -hex 20)"
# a random password that is not yours curl -s --cacert /etc/ssl/hledger/hledger.crt -o /dev/null -w 'GET /journal with a wrong password: %{http_code}\n' --user "${WRONG}" "${U}journal"
sudo ls -l /run/hledger-web/
sudo -u nobody curl -s -m 5 --unix-socket /run/hledger-web/hledger-web.sock -o /dev/null -w 'hledger-web socket as another local user: %{http_code}\n' http://localhost/version || true systemctl show -p IPAddressDeny -p RestrictAddressFamilies -p User hledger-web.service curl -s http://127.0.0.1/healthz; echo
GET / without a password: 401 GET /journal without a password: 401 GET /register without a password: 401 GET /accounts without a password: 401 GET /add without a password: 401 GET /journal with a wrong password: 401 total 0 srwxrwx--- 1 hledger www-data 0 Nov 7 14:44 hledger-web.sock hledger-web socket as another local user: 000 IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 ::ffff:169.254.169.254/128 169.254.169.254/32 User=hledger RestrictAddressFamilies=AF_UNIX "1.52.4"
  • Every page and API call needs the password. If the password file is missing, nginx refuses every request rather than serving the journal.
  • The socket belongs to hledger with the www-data group (nginx), so no other local account can talk to hledger-web.
  • IPAddressDeny is the metadata-service block on hledger-web. nginx and the first-boot service have the same block. hledger-web can only use Unix sockets (RestrictAddressFamilies=AF_UNIX), so it cannot open a network connection at all.
  • http://127.0.0.1/healthz returns hledger-web's version for a monitoring agent on the instance. It is answered only on 127.0.0.1, never on 443.
15

Back up your journal

Your data is the journal file. Copy it to a file that only root can read, and keep copies off the instance, for example in an encrypted S3 bucket:

sudo sh -c 'umask 077; cp /var/lib/hledger/main.journal /var/backups/main.journal.$(date +%F)'
sudo ls -l /var/backups/ | grep main.journal

Plain text also works well with git, which gives you a complete, diffable history of every change to your books. EBS snapshots of the instance are a simple way to keep point-in-time copies of the whole server.

16

Updates

Ubuntu security updates install automatically (unattended upgrades). New hledger releases are published as new versions of this listing. Launch the new version and copy your journal across.

17

Licenses and source

ls /opt/hledger/ sed -n '1,3p' /opt/hledger/VERSION
LICENSE SOURCE-OFFER.txt VERSION main.journal.sample hledger_version=1.52.4 hledger_tarball_sha256=f76a2e4088e162b7658e43077502569d5a6fdacd219b19bfb9aab6588b69643a hledger_license=GPL-3.0-or-later

hledger is licensed under the GNU General Public License version 3 or later. The image ships the unmodified upstream binaries. SOURCE-OFFER.txt says where to get the corresponding source (the upstream project at tag 1.52.4) and includes a written offer to provide it.

Security notes

  • Advisories. hledger 1.52.4 includes the fixes for the two published hledger-web advisories rated high: a cross-site scripting issue in the add form's account autocompletion (GHSA-538p-cvc4-4qjm, fixed in 1.52.2) and a cross-site scripting and journal include injection issue in the add form's error handling (GHSA-vq7r-8w52-jv84, fixed in 1.52.3).
  • Web server library. The upstream 1.52.4 binary is built with warp 3.4.15, which has a published request smuggling advisory (HSEC-2026-0013, fixed in warp 3.4.17). It is not reachable here: hledger-web listens only on its Unix socket, and its only client is nginx, which requires the password before it forwards anything, refuses the ambiguous request framings that smuggling relies on, and sends each request on its own connection.
  • Web access level. --allow=add is set in /etc/systemd/system/hledger-web.service. Use view for read-only reports. Raising it to edit would let anyone with the password replace the whole journal from the browser, so SecureAnyCloud does not recommend it.
  • Secrets. The administrator password is in /root/hledger-credentials.txt until you remove it. nginx stores only a bcrypt hash in /etc/nginx/sac/hledger.htpasswd. The TLS key is in /etc/ssl/hledger/hledger.key (root-only). No secret is on any command line, in a systemd unit or in a log.

Support

SecureAnyCloud provides support for this image by email at sales@secureanycloud.com. The hledger documentation is at hledger.org.

Deploy on AWS

Launch hledger 1.52.4 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS