ezBookkeeping 2.1.1 on AWS User Guide
Overview
This guide covers launching and using the ezBookkeeping 2.1.1 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg accounting, HR and finance listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
ezBookkeeping is a lightweight, self hosted personal finance and bookkeeping application. You keep your cash, bank, credit card, savings and investment accounts in one place, record income, expenses and transfers in any currency, organise them with two level categories and tags, and read the results as an overview, a transaction calendar, and pie, bar and trend charts. It imports CSV, OFX, QIF and other bank and app exports, exports your data again at any time, supports scheduled transactions, transaction templates, receipt pictures and two factor sign-in, and has a desktop and a mobile web interface that can be installed as an app on a phone.
The cloudimg image makes it safe to run on EC2:
- No default account. The image ships with no account and no database. On first boot your instance creates one administrator account with a 24 character random password, written to a root only file. Self registration is switched off, so nobody else can create an account on your instance.
- A session signing key for each instance. ezBookkeeping signs every login session with a secret key, and its sample configuration leaves that key empty, which falls back to a built in default anyone can look up. Your instance generates its own 48 character key on first boot, and the service refuses to start if the key is missing, still a placeholder or the default.
- HTTPS from the first boot. nginx serves ezBookkeeping on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS. Nothing answers until first boot has finished.
- ezBookkeeping listens on localhost only (
127.0.0.1:8080), so nginx is its only client, and it trusts the client address nginx forwards only from nginx itself. - Sign-in is rate limited twice. nginx slows down repeated sign-in and two factor attempts from one address, and ezBookkeeping locks out an address or user for the rest of the minute after five failed sign-ins.
- Other web sites cannot act with your login. nginx refuses API requests that another web site's page sends in your browser, and any change whose browser
Originis not this instance. - No access to the instance metadata service. nginx and ezBookkeeping are blocked at the kernel level from the EC2 instance metadata service (IPv4 and IPv6). ezBookkeeping runs as its own unprivileged user with no Linux capabilities.
- No third party services by default. The AI receipt recognition, the map and the MCP server are off and have no API keys; avatars are served from the instance; e-mail is off. The only outbound request ezBookkeeping makes is for currency exchange rates (Step 9).
- Built from source, with current fixes. ezBookkeeping 2.1.1 is built from its tagged release source with Go 1.27.3. Four of the Go libraries it uses received security fixes after 2.1.1 was released (
github.com/xuri/excelize/v2,golang.org/x/net,github.com/klauspost/compressandgithub.com/go-jose/go-jose/v4); the image uses the fixed releases, with no known reachable vulnerabilities at build time. There is no compiler on the image. - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: ezBookkeeping 2.1.1 (/opt/ezbookkeeping, configuration in /etc/ezbookkeeping/ezbookkeeping.ini, database, pictures and log in /var/lib/ezbookkeeping), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair, and a VPC subnet with internet access.
- A security group that allows TCP 22 (SSH), TCP 443 (ezBookkeeping) and TCP 80 (redirects to HTTPS) from the networks you and your users work from.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick ezBookkeeping 2.1.1 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (ezBookkeeping itself needs very little, so a smaller instance such as t3.small also works), your key pair, a subnet and a security group as above.
With the AWS CLI (replace the values in angle brackets):
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=ezbookkeeping}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| ezBookkeeping 2.1.1 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot sets the host name, generates the certificate, the session signing key and the administrator account; then ezBookkeeping and nginx start:
systemctl is-active ezbookkeeping-firstboot ezbookkeeping nginx
sudo test -f /var/lib/cloudimg/ezbookkeeping-firstboot.done && echo FIRSTBOOT_DONE
All three units are active and the first boot marker is present:
active
active
active
FIRSTBOOT_DONE
Only SSH and nginx listen on the network. ezBookkeeping listens on localhost only:
sudo ss -lntpH | awk '{print $4, $6}' | sed -E 's/users:\(\("([^"]+)".*/\1/' | grep -v '127.0.0.5' | sort -u
0.0.0.0:22 sshd
0.0.0.0:443 nginx
0.0.0.0:80 nginx
127.0.0.1:8080 ezbookkeeping
[::]:22 sshd
[::]:443 nginx
[::]:80 nginx
Step 4: Get your URL, user name and password
sudo cat /var/lib/cloudimg/ezbookkeeping-credentials.txt
The file is readable by root only and holds the URL and the user name and password of your instance's administrator account:
# ezBookkeeping 2.1.1 - generated on first boot by ezbookkeeping-firstboot.service
# This account is unique to this instance; none was baked into the image. Keep this file secret.
EZBOOKKEEPING_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
EZBOOKKEEPING_USERNAME=admin
EZBOOKKEEPING_PASSWORD=<EZBOOKKEEPING_PASSWORD>
#
# Open EZBOOKKEEPING_URL in a browser (self-signed certificate minted on this instance's first boot; port 80 redirects to
# https) and sign in with EZBOOKKEEPING_USERNAME / EZBOOKKEEPING_PASSWORD. Change the password under User Settings.
# Self-registration is off: this account owns the instance. The session signing key was generated for this instance too.
# Self-test: sudo ezbookkeeping-selftest Change the served name: sudo ezbookkeeping-set-hostname <your-domain>
# User guide: https://www.cloudimg.co.uk/guides/ezbookkeeping-2-1-1-on-ubuntu-24-04-aws/
The URL uses the instance's public DNS name from EC2 instance metadata. Opening https://<public-ip>/ sends you on to that name.
The image includes a self test. Through nginx it signs in with the account, reads the profile, signs out and checks that the session is revoked, and checks registration, the request guards, the service's privileges and the metadata block:
sudo ezbookkeeping-selftest
SELFTEST-OK https 301 + healthz + healthz.json 2.1.1 + UI + profile API refused without a token + registration 404 + wrong password refused + per-instance account signed in, profile read, signed out, token revoked + 403 cross-site sign-in + 403 cross-origin POST + 444 unknown host + ezbookkeeping uid ezbookkeeping, no capabilities, NoNewPrivs, listening 127.0.0.1:8080 only + metadata 000 inside ezbookkeeping and nginx (control 200)
Step 5: Sign in
Open the EZBOOKKEEPING_URL in your browser. The certificate is self signed, so your browser asks you to confirm it the first time (or install your own certificate, Step 11). Sign in with EZBOOKKEEPING_USERNAME and EZBOOKKEEPING_PASSWORD. Create an account on the sign-in page does nothing, because self registration is off.

After you sign in for the first time, open the user menu (top right), choose User Settings and change the password to one of your own; under Two-Factor Authentication you can add an authenticator app. Under Basic Settings set your default currency, language, time zone and the first day of the week.
The Overview shows this month's expense and income, your total assets, liabilities and net assets, a heatmap and calendar of your transactions, the expense category ranking and trend, and today's, this week's, this month's and this year's totals:

Step 6: Accounts, categories and transactions
Start under Accounts (the card icon): add your cash, bank, credit card, savings, investment and debt accounts, each with its currency and opening balance. A credit card or debt account counts as a liability. An account can hold sub-accounts, for example one bank account with several currencies.

Then set up Transaction Categories (user menu > Transaction Categories): ezBookkeeping offers a ready made set of expense, income and transfer categories in two levels, or you can create your own. A transaction always uses a second level category.
Record transactions with the + button: an expense, income or transfer, with its time, category, amount, account, tags, description and, if you like, a picture of the receipt. Transaction List shows them by day with filters for month, type, category, account and tag, and a search; the calendar and gallery views show the same transactions by date and by picture:

Import on the transaction list reads exports from banks and other apps (CSV, TSV and Excel files with a column mapping, OFX, QFX, QIF, IIF, Camt.052 and Camt.053, MT940, GnuCash, Firefly III, Beancount and others), and user menu > User Settings > Data Management exports all your transactions to CSV or TSV.
Step 7: Statistics and analysis
Statistics & Analysis (the pie chart icon) breaks income and expense down by account or by category for any period, as a pie, bar or radar chart, and Trend Analysis and Asset Trends show them over time. Insights Explorer (the compass icon) lets you build your own queries over your transactions.

Step 8: Use the API from a script
The web interface uses ezBookkeeping's JSON API, and so can your scripts: sign in to /api/authorize.json for a session token, then send it as a bearer token. nginx's cross-site guard needs every API request to say where it comes from, so a script sends Origin with the instance's own URL. This example signs in with the account from the credentials file, reads the profile and the number of accounts, and signs out again:
F=/var/lib/cloudimg/ezbookkeeping-credentials.txt
H=$(sudo sed -n 's#^EZBOOKKEEPING_URL=https://\([^/]*\)/$#\1#p' "$F")
API=(-sk --resolve "$H:443:127.0.0.1" -H "Origin: https://$H" -H 'Content-Type: application/json')
TOKEN=$(sudo python3 -c 'import json,sys
kv=dict(l.rstrip("\n").split("=",1) for l in open(sys.argv[1]) if "=" in l and not l.startswith("#"))
print(json.dumps({"loginName": kv["EZBOOKKEEPING_USERNAME"], "password": kv["EZBOOKKEEPING_PASSWORD"]}))' "$F" \
| curl "${API[@]}" --data-binary @- "https://$H/api/authorize.json" | python3 -c 'import json,sys; print(json.load(sys.stdin)["result"]["token"])')
AUTH=(-H @/dev/stdin)
curl "${API[@]}" "${AUTH[@]}" "https://$H/api/v1/users/profile/get.json" <<<"Authorization: Bearer $TOKEN" | python3 -c 'import json,sys; r=json.load(sys.stdin)["result"]; print("signed in as", r["username"], "-", r["defaultCurrency"])'
curl "${API[@]}" "${AUTH[@]}" "https://$H/api/v1/accounts/list.json" <<<"Authorization: Bearer $TOKEN" | python3 -c 'import json,sys; print(len(json.load(sys.stdin)["result"]), "accounts")'
curl "${API[@]}" "${AUTH[@]}" "https://$H/api/logout.json" <<<"Authorization: Bearer $TOKEN" | python3 -c 'import json,sys; print("signed out" if json.load(sys.stdin)["success"] else "sign-out failed")'
signed in as admin - USD
4 accounts
signed out
The password and the token travel on standard input, so they do not appear in the process list. A session token is valid for 30 days unless you sign out; user menu > User Settings > Device & Sessions lists and revokes sessions. (The 4 accounts line counts the accounts you have created; on a new instance it reads 0 accounts.)
Step 9: Exchange rates and other outbound features
ezBookkeeping converts between currencies with daily exchange rates. When you open the exchange rates page or a total needs converting, the instance fetches the rates from the European Central Bank ([exchange_rates] data_source = euro_central_bank in /etc/ezbookkeeping/ezbookkeeping.ini), so the instance needs outbound HTTPS for that. Other sources (several central banks) can be set there; restart ezBookkeeping after editing the file (sudo systemctl restart ezbookkeeping).
These features are off on the image, and each needs your own account with the provider before you switch it on in the same file:
- AI recognition of receipt pictures and text, and the AI coding assistant (
[llm_image_recognition],[llm_text_recognition],[llm_coding_assistant]):llm_provideris empty and no API key is set. - Maps for transaction locations (
[map] map_provider): empty. - The MCP server for AI assistants (
[mcp] enable_mcp = false) and API tokens ([security] enable_api_token = false). - E-mail (
[mail] enable_smtp = false), which password reset and e-mail verification need.
Step 10: More users
Self registration is off, so the administrator account owns the instance. To let household members create their own accounts, switch it on for a short time and off again once they have registered:
sudo sed -i 's/^enable_register = false/enable_register = true/' /etc/ezbookkeeping/ezbookkeeping.ini
sudo systemctl restart ezbookkeeping
# ... once everyone has registered:
sudo sed -i 's/^enable_register = true/enable_register = false/' /etc/ezbookkeeping/ezbookkeeping.ini
sudo systemctl restart ezbookkeeping
Every account keeps its own books; accounts do not see each other's data.
Step 11: Use your own domain and certificate
- In your DNS, point
<your-domain>(an A record) at the instance's public IP address. An Elastic IP keeps that address stable. - Tell the instance to use it. This sets the name nginx accepts, the request guards and ezBookkeeping's
[server] domain, and generates a new self signed certificate for it:
sudo ezbookkeeping-set-hostname <your-domain>
- For a certificate your browser trusts, put the certificate chain for
<your-domain>in/etc/ssl/ezbookkeeping/ezbookkeeping.crtand its private key in/etc/ssl/ezbookkeeping/ezbookkeeping.key(root only, mode 600), and runsudo systemctl reload nginx. Alternatively put the instance behind an Application Load Balancer with an AWS Certificate Manager certificate that forwards to port 443 on the instance.
Step 12: Data and backups
Everything ezBookkeeping keeps is in /var/lib/ezbookkeeping: data/ezbookkeeping.db (the SQLite database with your accounts and transactions), storage/ (avatars and receipt pictures), log/ and secret_key (the session signing key). To back it up, stop ezBookkeeping for a moment:
sudo systemctl stop ezbookkeeping
sudo tar -C /var/lib -czf /root/ezbookkeeping-backup.tgz ezbookkeeping
sudo systemctl start ezbookkeeping
To restore on a new instance, stop ezBookkeeping, extract the archive over /var/lib/ezbookkeeping (owner ezbookkeeping, mode 700; secret_key mode 600) and start it again. Restoring secret_key keeps existing sessions valid; without it everyone simply signs in again. An EBS snapshot of the instance's volume is also a complete backup.
Step 13: Security checks
nginx and ezBookkeeping are blocked from the instance metadata service:
for u in nginx ezbookkeeping; do printf '%s ' "$u"; systemctl show -p IPAddressDeny --value "$u" | tr ' ' '\n' | sort | tr '\n' ' '; echo; done
nginx 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
ezbookkeeping 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
ezBookkeeping runs as ezbookkeeping with no Linux capabilities and cannot gain any:
P=$(systemctl show -p MainPID --value ezbookkeeping)
ps -o user= -p "$P"
grep -E '^(CapEff|CapBnd|CapAmb|NoNewPrivs)' /proc/"$P"/status
ezbookkeeping
CapEff: 0000000000000000
CapBnd: 0000000000000000
CapAmb: 0000000000000000
NoNewPrivs: 1
Registration is closed, nginx refuses an API request with no same-origin signal, a sign-in that another web site's page sends and a change from a foreign Origin, and a request for a host name the instance does not serve gets no answer at all:
H=$(sudo sed -n 's#^EZBOOKKEEPING_URL=https://\([^/]*\)/$#\1#p' /var/lib/cloudimg/ezbookkeeping-credentials.txt)
curl -sk -o /dev/null -w 'registration: %{http_code}\n' --resolve "$H:443:127.0.0.1" -X POST -H 'Content-Type: application/json' -H "Origin: https://$H" --data '{}' "https://$H/api/register.json"
curl -sk -o /dev/null -w 'no same-origin signal: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/api/v1/users/profile/get.json"
curl -sk -o /dev/null -w 'cross-site sign-in: %{http_code}\n' --resolve "$H:443:127.0.0.1" -X POST -H 'Content-Type: application/json' -H 'Sec-Fetch-Site: cross-site' --data '{}' "https://$H/api/authorize.json"
curl -sk -o /dev/null -w 'cross-origin POST: %{http_code}\n' --resolve "$H:443:127.0.0.1" -X POST -H 'Content-Type: application/json' -H 'Origin: https://evil.example' --data '{}' "https://$H/api/v1/accounts/add.json"
curl -sk -o /dev/null -w 'unknown host name: %{http_code}\n' --resolve evil.example:443:127.0.0.1 https://evil.example/ || true
registration: 404
no same-origin signal: 403
cross-site sign-in: 403
cross-origin POST: 403
unknown host name: 000
The credentials, the session signing key, the TLS key and ezBookkeeping's data are readable by root (and the key and data by the ezbookkeeping service user) only:
sudo stat -c '%a %U:%G %n' /var/lib/cloudimg/ezbookkeeping-credentials.txt /var/lib/ezbookkeeping/secret_key /etc/ssl/ezbookkeeping/ezbookkeeping.key /etc/ezbookkeeping/ezbookkeeping.ini /var/lib/ezbookkeeping /var/lib/ezbookkeeping/data
600 root:root /var/lib/cloudimg/ezbookkeeping-credentials.txt
600 ezbookkeeping:ezbookkeeping /var/lib/ezbookkeeping/secret_key
600 root:root /etc/ssl/ezbookkeeping/ezbookkeeping.key
640 root:ezbookkeeping /etc/ezbookkeeping/ezbookkeeping.ini
700 ezbookkeeping:ezbookkeeping /var/lib/ezbookkeeping
700 ezbookkeeping:ezbookkeeping /var/lib/ezbookkeeping/data
Other notes:
- The password is stored only as ezBookkeeping's salted PBKDF2-SHA256 hash in the database. Change it in the browser (user menu > User Settings > Security Settings); the credentials file keeps the first one, so update or delete it afterwards.
- Failed sign-ins. After five failed sign-ins in a minute from one address, or for one user, ezBookkeeping refuses further attempts, even with the right password, until the minute has passed. nginx also answers
429to a burst of sign-in requests. - Who can reach it. Open ports 80 and 443 in the security group only to the networks your users work from.
- The build.
/usr/share/doc/cloudimg-ezbookkeeping/BUILD-INFO.txtrecords the release, the Go toolchain and the four dependency updates;go.mod,go.sumandgo-version-m.txtnext to it list every library in the program, andthird-party-dependencies.jsonthe web interface's libraries. - Logs. nginx logs are in
/var/log/nginx(without query strings); ezBookkeeping logs to the journal (journalctl -u ezbookkeeping) and to/var/lib/ezbookkeeping/log/.
Step 14: Keep it up to date
Ubuntu security updates install automatically. To apply all updates now:
sudo apt-get update && sudo apt-get upgrade -y
New ezBookkeeping releases arrive as new versions of this listing, built and checked the same way. To move to one, back up /var/lib/ezbookkeeping (Step 12), launch the new version and restore it; ezBookkeeping updates its database on start. Read the ezBookkeeping release notes before you move.
Support and licensing
ezBookkeeping is open source under the MIT licence (MaysWind and contributors); the licence text is in /usr/share/doc/cloudimg-ezbookkeeping/, and the source code is at github.com/mayswind/ezbookkeeping. The Go and web libraries it is built with are open source, mainly under the MIT, Apache 2.0 and BSD licences; go-version-m.txt and third-party-dependencies.json in the same directory list them. There is no per user licence fee. The AMI price covers cloudimg's build, hardening, first boot setup and support.
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
- Support: 24/7, with a 24 hour response SLA
The other versions on this listing are on the accounting, HR and finance guide.