Applications Azure

Fava for Beancount on Ubuntu 24.04 on Azure User Guide

| Product: Fava for Beancount on Ubuntu 24.04 LTS on Azure

Overview

Fava is a popular open source web interface for Beancount, the plain text double entry bookkeeping system. It turns a text ledger into an interactive financial dashboard: balance sheets and income statements, a net worth chart, holdings and commodities, an interactive journal, and a powerful Beancount Query Language (BQL) query editor. The cloudimg image installs Fava into a dedicated Python virtual environment running under systemd, then locks it down for a marketplace appliance. Fava has no built in authentication of its own and, by default, can edit the ledger and run queries, so it is bound to loopback 127.0.0.1:5000 and never exposed directly. An nginx reverse proxy on port 80 adds a per VM HTTP Basic Auth gate (user admin, unique password generated on first boot) in front of it, plus an unauthenticated /healthz endpoint for load balancer probes. A populated example ledger ships so every report and chart is non empty out of the box; you replace it with your own Beancount file whenever you are ready. Backed by 24/7 cloudimg support.

What is included:

  • Fava 1.30.14 installed in a dedicated Python virtual environment and running as the fava systemd service
  • The full Fava web UI and JSON API on :80, fronted by nginx with Fava bound to loopback only
  • Per VM HTTP Basic Auth (user admin) protecting the UI, with a unique password generated on first boot
  • Fava bound to 127.0.0.1:5000 - the application is never exposed to the network directly
  • A populated example Beancount ledger at /opt/fava/ledger/example.beancount so reports and charts are non empty on first boot
  • An unauthenticated /healthz endpoint for Azure Load Balancer health probes
  • fava.service + nginx.service as systemd units, enabled and active
  • The bcrypt Basic Auth hash stored on disk only - no plaintext password ships in the image
  • 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a comfortable starting point; Fava is light on resources. NSG inbound: allow 22/tcp from your management network, 80/tcp for the web UI, and 443/tcp if you add TLS. Fava serves plain HTTP on port 80; for production use, terminate TLS in front of it with your own domain and restrict access to trusted IP ranges (see Maintenance).

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Fava by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Then Review + create -> Create.

Step 2 - Deploy from the Azure CLI

az vm create \
  --resource-group <your-rg> \
  --name fava \
  --image <marketplace-image-urn> \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --vnet-name <your-vnet> --subnet <your-subnet> \
  --public-ip-sku Standard

az vm open-port --resource-group <your-rg> --name fava --port 80 --priority 1010

Step 3 - Connect to your VM

ssh azureuser@<vm-public-ip>

Step 4 - Confirm the services are running

systemctl is-active fava.service nginx.service

Both report active. Fava serves its web interface on the loopback address 127.0.0.1:5000; nginx fronts it on port 80 and adds the per VM HTTP Basic Auth gate. The application is never bound to a public interface, so nginx is the only way in.

The fava and nginx services reporting active, Fava listening on loopback 127.0.0.1:5000, and nginx listening on port 80

Step 5 - Retrieve your web UI password

nginx protects the Fava UI with HTTP Basic Auth. The username is admin and a unique password is generated on the first boot of your VM and written to a root only file:

sudo cat /root/fava-credentials.txt

This file contains FAVA_USERNAME, FAVA_PASSWORD and the FAVA_URL to open in a browser. The password is stored on disk only as a bcrypt hash in /etc/nginx/.fava.htpasswd, so no plaintext password ships in the image. Store the password somewhere safe.

The Fava version and the per VM credentials file with the generated admin username, password and URL

Step 6 - Confirm the health endpoint

nginx serves an unauthenticated health endpoint for load balancers and probes:

curl -s http://localhost/healthz

It returns ok. This endpoint never requires authentication, so it is safe for an Azure Load Balancer health probe.

Step 7 - Confirm authentication

Because a password is set on first boot, an unauthenticated request to the UI returns HTTP 401, so nobody reaches Fava without the password. The following reads the per VM password from the credentials file and proves the round trip - unauthenticated is rejected, a wrong password is rejected, and the correct password authenticates and reaches the ledger:

PW=$(sudo grep '^FAVA_PASSWORD=' /root/fava-credentials.txt | cut -d= -f2-)
echo "unauth  : $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/)"
echo "wrongpw : $(curl -s -o /dev/null -w '%{http_code}' -u admin:wrong-pw http://127.0.0.1/)"
echo "authed  : $(curl -s -L -o /dev/null -w '%{http_code}' -u admin:$PW http://127.0.0.1/)"

It prints unauth : 401, wrongpw : 401 and authed : 200. The whole UI is only reachable with the per VM password because Fava itself is bound to loopback and nginx is the only way in.

The HTTP Basic Auth round trip returning 401 unauthenticated, 401 for a wrong password, and 200 with the per VM password

Step 8 - Explore the example ledger from the command line

Beancount ships bean-query, a command line BQL client, in the same virtual environment as Fava. It runs against the bundled example ledger so you can see the data before opening the browser:

sudo -u fava /opt/fava/venv/bin/bean-query /opt/fava/ledger/example.beancount \
  "SELECT account, sum(position) AS balance WHERE account ~ 'Assets' GROUP BY account ORDER BY account"

It prints the balance of every asset account in the example ledger - a checking account, a savings account, a cash wallet and a brokerage account holding shares. The same numbers drive the Fava reports and charts in the browser.

The bean-query command line client listing the asset account balances of the example ledger - checking, savings, cash and brokerage holdings

Step 9 - Sign in and open the Balance Sheet

Browse to http://<vm-public-ip>/. Your browser prompts for a username and password: enter admin and the password from Step 5. Fava opens on the example ledger. Open Balance Sheet from the left navigation: it shows assets, liabilities and equity as an expandable account tree with a treemap chart, as of the latest date in the ledger.

The Fava Balance Sheet report showing the assets, liabilities and equity tree with a treemap chart for the example ledger

Step 10 - Review the Income Statement

Open Income Statement from the left navigation. It summarises income and expenses over the selected period with a bar chart of net income per month and an expandable breakdown of every income and expense account - salary, rent, groceries, utilities, transport and the rest.

The Fava Income Statement report showing a bar chart of net income per month and the income and expense account breakdown

Step 11 - Read the Journal

Open Journal from the left navigation. It is Fava's interactive transaction list: every posting in the ledger, filterable by account, date range, tag or a full text search, with running balances. This is where you audit and drill into individual transactions.

The Fava Journal view listing the ledger transactions with dates, payees, accounts and amounts

Step 12 - Run a query in the BQL editor

Open Query from the left navigation to reach the Beancount Query Language editor. Type a query - for example SELECT account, sum(position) WHERE account ~ 'Expenses' GROUP BY account ORDER BY sum(position) DESC - and run it to get a results table you can sort and export. This is the same query engine as bean-query from Step 8, in the browser.

The Fava BQL query editor showing a query grouping expenses by account with the results table below

Step 13 - Point Fava at your own ledger

The image ships a populated example ledger so the dashboard is not empty. When you are ready, copy your own Beancount file into place and restart Fava:

sudo install -o fava -g fava -m 0644 /path/to/your-ledger.beancount /opt/fava/ledger/example.beancount
sudo systemctl restart fava

Fava reloads the file automatically when it changes on disk, and the restart guarantees it picks up a brand new file. Keep the path /opt/fava/ledger/example.beancount (the fava.service unit points there), or edit ExecStart in /etc/systemd/system/fava.service to reference a different filename and sudo systemctl daemon-reload.

Maintenance

  • Password: the UI password is set on first boot and stored as a bcrypt entry in /etc/nginx/.fava.htpasswd. To change it, run sudo htpasswd -B /etc/nginx/.fava.htpasswd admin and then sudo systemctl reload nginx.
  • Your ledger: replace /opt/fava/ledger/example.beancount with your own Beancount file (owned fava:fava) and sudo systemctl restart fava. Validate any ledger with sudo -u fava /opt/fava/venv/bin/bean-check /opt/fava/ledger/example.beancount before restarting.
  • Read only mode: Fava can edit the ledger and run arbitrary queries. If you only need reporting, add --read-only to ExecStart in /etc/systemd/system/fava.service, then sudo systemctl daemon-reload && sudo systemctl restart fava, so the UI cannot modify your books.
  • Restrict access: Fava serves plain HTTP on port 80. For production, restrict access to trusted IP ranges in your Network Security Group, and front it with TLS (for example certbot with your own domain) terminating on :443.
  • Loopback binding: Fava is bound to 127.0.0.1:5000 in /etc/systemd/system/fava.service, so nginx is the only path in. Keep it that way - do not change the listen address to a public interface.
  • Upgrades: upgrade Fava in its virtual environment with sudo /opt/fava/venv/bin/pip install --upgrade fava and sudo systemctl restart fava.
  • Security patches: unattended-upgrades remains enabled so the OS continues to receive security updates automatically.

Support

cloudimg provides 24/7 expert support for this image. Contact support@cloudimg.co.uk.