Applications AWS

hledger Plain Text Accounting on AWS User Guide

| Product: hledger on AWS

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

Three programs are installed to /usr/local/bin from the pinned upstream static release (verified by SHA-256):

  • hledger - the command line reporting tool.
  • hledger-ui - a terminal (curses) interface.
  • hledger-web - a browser interface with journal, register and balance report views and an add-transaction form.

hledger-web is run by an unprivileged hledger system account under a systemd service. It binds the loopback address 127.0.0.1:5000 by design and is never exposed directly. nginx on port 80 is the sole network-facing surface: it enforces HTTP basic authentication on every path except an unauthenticated /healthz probe, and reverse-proxies authenticated requests to hledger-web.

Secure by default

hledger-web ships with no access control of any kind, and 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.
  • On the first boot of every deployed instance, a one-shot service generates a fresh per-instance admin credential and writes it to /root/hledger-credentials.txt (mode 0600, root only). Only the one-way bcrypt hash of the password is stored in the nginx htpasswd file - never the plaintext. No two instances share a credential and no default password ships in the image.
  • The web UI is configured append only (--allow=add): new transactions can be recorded 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 lives at /var/lib/hledger/main.journal on a dedicated, independently resizable EBS data volume kept separate from the operating system disk, and is readable only by the service account and root because it is financial data.

The default security group for this listing opens port 22 (SSH) and port 80 (HTTP) only.

Note - HTTP by default. nginx serves over plain HTTP on port 80, so your basic-auth credentials travel unencrypted. For anything beyond a quick trial, put the instance behind TLS: terminate at an AWS Application Load Balancer or Amazon CloudFront, or enable Let's Encrypt on the instance (see Step 8).

Prerequisites

  • An AWS account subscribed to this product in AWS Marketplace.
  • An EC2 key pair in your target region for SSH access.
  • A security group allowing inbound TCP 22 (SSH) from your IP and TCP 80 (HTTP) from your users.
  • Recommended instance type: m5.large or larger.

Connecting to your instance

SSH in as the default login user for your operating system variant, using the key pair you launched with.

OS variant Login user Example
Ubuntu 24.04 ubuntu ssh -i your-key.pem ubuntu@<instance-public-ip>

Step 1 - Launch from the AWS Marketplace console

  1. Open the product page in AWS Marketplace and choose Continue to Subscribe, then Continue to Configuration.
  2. Select the software version and your AWS Region, then choose Continue to Launch.
  3. Choose an instance type (m5.large or larger), your VPC subnet, the security group described above, and your EC2 key pair.
  4. Launch the instance and wait for it to reach the running state with status checks passed.

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

SSH to the instance and read the credentials file written on first boot. It is mode 0600 and owned by root, so read it with sudo:

sudo cat /root/hledger-credentials.txt

You will see the unique URL, username and password generated for this instance:

HLEDGER_USER=admin
HLEDGER_PASS=682a9a81626d5adc27c2b97da0a3a54ede7b6797
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 both services are active:

systemctl is-active hledger-web.service nginx.service
active
active

Confirm 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",...))

The unauthenticated health endpoint, served by nginx itself, returns HTTP 200:

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthz
200

Every 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/journal
401

Step 5 - Sign in to hledger-web

Browse to http://<instance-public-ip>/ and sign in with the username and password from Step 3 when your browser prompts for HTTP authentication. You land on the journal view, with the live account balances in the sidebar and the general journal on the right.

The hledger-web journal view with account balances

Click any account to open its register - every posting to that account with a running balance and a period-total chart.

The hledger-web account register with a running balance 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 the transaction balances to zero.

Recording a new transaction in hledger-web

Step 6 - Report from the command line

The same journal is reported on directly from the shell. Because the journal is financial data owned by the hledger service account, run these with sudo:

sudo hledger balance
         3126.55 GBP  assets:bank:current
        -2500.00 GBP  equity:opening balances
          123.45 GBP  expenses:cloudimg:verification
          450.00 GBP  expenses:office:rent
        -1200.00 GBP  income:consulting
--------------------
                   0

Show the running register for one account:

sudo -u hledger hledger register assets:bank:current

The shipped self-test proves the appliance end to end - it computes a balance from a fresh transaction, checks the live journal parses, confirms unauthenticated access is refused, and confirms the authenticated web UI serves the same journal:

sudo /usr/local/bin/hledger-selftest
OK hledger self-test passed: ...

Step 7 - Change the web access level (optional)

The web UI ships append only (--allow=add). You can change it:

  • 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 with care: a single bad upload can replace your entire ledger.

Edit the service unit's --allow= flag and restart. Open the drop-in or the unit at /etc/systemd/system/hledger-web.service, change --allow=add to --allow=view (or --allow=edit), then:

  • 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 your basic-auth credentials and data are encrypted. Two common options:

Option A - certbot on the instance. Point a DNS name at the instance, open port 443 in the security group, then obtain a certificate. Run these interactively (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, forwarding to port 80. This keeps certificate management off the instance.

Step 9 - Configuration and data locations

Path Purpose
/var/lib/hledger/main.journal Your journal - the plain text ledger, the 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 - stores only the bcrypt hash of the password.
/etc/nginx/sites-available/cloudimg-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/hledger

Step 10 - Backup and maintenance

  • Back up the journal. The journal is a single plain text file. Copy /var/lib/hledger/main.journal to off-instance storage, keep it in a private git repository, or take regular EBS snapshots of the /var/lib/hledger volume.
  • Rotate the web credential. To set a new password, regenerate the htpasswd entry: sudo htpasswd -B /etc/nginx/hledger.htpasswd admin (it 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. Because the journal lives on a dedicated EBS volume, you can grow it with the AWS console or CLI and then extend the filesystem, with no change to the OS disk.

Support

cloudimg provides 24/7 technical support for this image by email and live chat, covering 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.