Firefly III 6.7.9 on AWS User Guide
Overview
This guide covers launching and using the Firefly III 6.7.9 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg accounting and HR software listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
Firefly III is an open source, self-hosted personal finance manager. It uses double entry bookkeeping to keep an exact record of your money: asset, expense, revenue and liability accounts, budgets, categories and tags, bills and subscriptions, piggy banks for savings goals, rules that file transactions automatically, recurring transactions, multi-currency support, charts and reports, and a REST API for importers and apps. Your data stays on your own server.
The cloudimg image is a hardened, ready-to-use Firefly III server:
- The upstream release. The official Firefly III 6.7.9 release archive, verified against its SHA-256, with its prebuilt web interface. No Firefly III source file is modified. Six bundled Symfony libraries are raised to their current security releases within the same versions;
SOURCE.txtlists exactly what changed. - PHP 8.5 from the ondrej/php PPA. Firefly III 6.7.9 needs PHP 8.5, which Ubuntu 24.04 does not ship. PHP comes from the long-standing ondrej/php PPA, trusted through one pinned signing key and limited by an apt pin to PHP packages only. Unattended upgrades keep both Ubuntu and PHP patched.
- No default login. First boot creates one owner account,
admin@example.com, with a random password unique to your instance, and closes self-registration (single user mode). APP_KEY, the encryption key for Firefly III's stored secrets, and the cron token are also generated on your instance. Until first boot has finished, neither nginx nor PHP starts. - HTTPS from the first boot. nginx serves Firefly III 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.
- Hardened web tier. PHP runs as its own unprivileged user on a Unix socket that only nginx can reach. nginx passes Firefly III the real client address from the connection, never a header a client sends, limits sign-in attempts per address, refuses cross-site requests to state-changing pages, and refuses the web installer, the web cron URL, the configuration file, the data directory and the source tree.
- SQLite. The database is one file in
/var/lib/firefly-iii, so there is no database server, no database port and no database password to manage. - Metadata service blocked. nginx, PHP, the first-boot service and the cron 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: Firefly III 6.7.9 (AGPL-3.0-or-later), PHP 8.5 FPM and CLI (ondrej/php PPA), nginx, SQLite, cloud-init and the AWS Systems Manager agent, all from Ubuntu 24.04 except Firefly III and PHP.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large (2 vCPUs, 8 GiB). A t3.medium is enough for a household or a small business.
Prerequisites
- 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.
- For production, a DNS name you control (for example
finance.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 | SSH user |
|---|---|
| Ubuntu 24.04 | ubuntu |
Step 1: Launch the instance
Launch from the AWS Marketplace page (choose the Firefly III 6.7.9 on Ubuntu 24.04 version), or 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=firefly-iii}]'
First boot takes about a minute once the instance is up: it creates the database, the encryption keys, the owner account and the certificate. The web interface stays offline until that has finished.
Step 2: Connect to your instance
ssh -i my-key.pem ubuntu@<public-ip>
Step 3: Check the services
systemctl is-active nginx.service php8.5-fpm.service firefly-iii-cron.timer
sudo ss -ltnp | awk 'NR==1 || /:(22|80|443) /'
sudo ss -lxp | grep firefly-fpm
active
active
active
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=903,fd=3),("systemd",pid=1,fd=227))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=55399,fd=5),("nginx",pid=55398,fd=5),("nginx",pid=45804,fd=5))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=55399,fd=7),("nginx",pid=55398,fd=7),("nginx",pid=45804,fd=7))
LISTEN 0 4096 [::]:22 [::]:* users:(("sshd",pid=903,fd=4),("systemd",pid=1,fd=228))
LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=55399,fd=6),("nginx",pid=55398,fd=6),("nginx",pid=45804,fd=6))
LISTEN 0 511 [::]:443 [::]:* users:(("nginx",pid=55399,fd=8),("nginx",pid=55398,fd=8),("nginx",pid=45804,fd=8))
u_str LISTEN 0 4096 /run/php/firefly-fpm.sock 106981 * 0 users:(("php-fpm8.5",pid=45809,fd=10),("php-fpm8.5",pid=45808,fd=10),("php-fpm8.5",pid=45799,fd=8))
Only SSH, HTTP and HTTPS listen on the network. PHP listens only on its Unix socket.
Step 4: Get your address and the owner password
First boot writes everything you need to sign in to a file only root can read:
sudo cat /root/firefly-iii-credentials.txt
# Firefly III 6.7.9 - generated on first boot by firefly-iii-firstboot.service
# These credentials are unique to this instance; none was baked into the image. Keep this file secret.
FIREFLY_URL=https://ec2-44-201-132-253.compute-1.amazonaws.com/
FIREFLY_ADMIN_EMAIL=admin@example.com
FIREFLY_ADMIN_PASSWORD=<24 random letters and digits>
FIREFLY_TLS_SHA256=B5:D7:CC:DC:6B:0D:DE:85:54:BC:D1:94:32:B3:0E:ED:01:1F:57:D0:8F:34:EF:03:14:9F:39:64:CD:BF:D1:78
...
- FIREFLY_URL is your Firefly III address.
- FIREFLY_TLS_SHA256 is the fingerprint of the instance's own certificate. Your browser will warn that the certificate is not trusted; compare the fingerprint it shows with this one before you continue. Replace it with a certificate from a certificate authority for your own name when you can (Step 10).
- FIREFLY_ADMIN_EMAIL / FIREFLY_ADMIN_PASSWORD sign in to Firefly III as its owner.
Step 5: Run the self-test
The image includes an end-to-end test. Over HTTPS on the instance it checks that port 80 redirects, that the installer, the front controller path, the cron URL, the configuration file, the data directory and the sources are refused, that registration is closed, that the owner signs in through Firefly III's own login form, and that a wrong password and common default passwords are refused:
sudo firefly-iii-cred-roundtrip
FIREFLY_ROUNDTRIP_OK https 301 + no installer/index.php/cron URL/env/data/sources + health + registration closed + owner sign-in (Firefly III's own login form, CSRF token) + wrong and default passwords refused
Step 6: Sign in to the web interface
Open FIREFLY_URL in your browser, check the certificate fingerprint, and sign in with FIREFLY_ADMIN_EMAIL and FIREFLY_ADMIN_PASSWORD.

On the first sign-in Firefly III asks for the name of your bank, the balance of your main account and its currency, an optional savings balance and your language:

Submit, and the dashboard opens with your accounts, a balance chart and your net worth:

Change the e-mail address on your profile page (/profile, the person icon at the top right). To change the password, run one of these on the instance. The first generates a new random password; the second reads yours (20 to 72 letters and digits) from the terminal, never from the command line. Either way every session is signed out and the new password is written to /root/firefly-iii-credentials.txt:
sudo firefly-iii-set-password
# Type <new-password> at the prompt; nothing is echoed.
read -rs -p 'New Firefly III password: ' P; echo
printf '%s\n' "$P" | sudo firefly-iii-set-password --stdin; unset P
Once you have stored the password somewhere safe, you can delete the FIREFLY_ADMIN_PASSWORD line from /root/firefly-iii-credentials.txt.
Step 7: Record your money
- Accounts. Add your bank, savings and credit card accounts under Accounts > Asset accounts and Liabilities. Expense and revenue accounts (shops, employers) are created as you record transactions.
- Transactions. Use Create new > Withdrawal / Deposit / Transfer. Every transaction moves money between two accounts, so balances always add up.
- Budgets, categories and tags organise spending; Bills track recurring payments and warn when one is missed; Piggy banks set money aside for a goal.
- Rules (under Automation) file new transactions automatically, for example by description.
- Reports show income, spending and net worth over any period.

The owner account also has System settings (/settings): Firefly III's configuration, notification settings, update checks (off on this image until you turn them on) and All users, where you invite more users. Self-registration stays closed.

Step 8: The daily cron
Recurring transactions, bill reminders, auto-budgets and exchange rates are processed by Firefly III's cron. On this image it is a systemd timer that runs it once a day at 03:00 UTC as the unprivileged firefly user; the web cron URL is not exposed. To see the schedule or run it now:
systemctl list-timers firefly-iii-cron.timer --no-pager
sudo systemctl start firefly-iii-cron.service
systemctl show -p Result -p ExecMainStatus firefly-iii-cron.service
NEXT LEFT LAST PASSED UNIT ACTIVATES
Sun 2026-11-08 03:03:14 UTC 5h 15min Sat 2026-11-07 21:18:53 UTC - firefly-iii-cron.timer firefly-iii-cron.service
1 timers listed.
Pass --all to see loaded but inactive timers, too.
Result=success
ExecMainStatus=0
Step 9: Firefly III's command line
firefly-iii-artisan runs Firefly III's own artisan commands as the firefly user with the same metadata block as the web tier:
sudo firefly-iii-artisan firefly-iii:output-version
6.7.9
Step 10: Use your own domain and certificate
Point a DNS name at the instance (an Elastic IP is a good idea), then:
sudo firefly-iii-set-hostname <your-domain>
This creates a new self-signed certificate for the name, pins nginx to it (other names are dropped), sets Firefly III's APP_URL (used in links in its e-mails) and updates the URL and fingerprint in /root/firefly-iii-credentials.txt. To use a certificate from a certificate authority, replace /etc/nginx/ssl/firefly-iii.crt and firefly-iii.key (keep the key 0600 root) and run sudo systemctl reload nginx.
To send e-mail (password resets, bill reminders), set the MAIL_* values in /etc/firefly-iii/env (see Firefly III's documentation) and run sudo systemctl restart php8.5-fpm.
Step 11: Restrict access to your networks
Limit who can reach the web interface in the instance's security group. For example, to allow HTTPS only from your office network:
aws ec2 authorize-security-group-ingress --group-id sg-xxxxxxxx --protocol tcp --port 443 --cidr <your-mgmt-cidr>
aws ec2 revoke-security-group-ingress --group-id sg-xxxxxxxx --protocol tcp --port 443 --cidr 0.0.0.0/0
Firefly III locks a client address out of sign-in for a minute after five attempts, and nginx separately rate limits sign-in and registration requests per client address.
Step 12: Check the security posture
U=$(sudo sed -n 's/^FIREFLY_URL=//p' /root/firefly-iii-credentials.txt)
C=/etc/nginx/ssl/firefly-iii.crt
for p in login register health; do
curl -s --cacert "$C" -o /dev/null -w "GET /$p: %{http_code}\n" "${U}$p"
done
for p in install index.php/login api/v1/cron/x .env storage/logs/laravel.log composer.json; do
curl -s --cacert "$C" -o /dev/null -w "GET /$p: %{http_code}\n" "${U}$p"
done
curl -s --cacert "$C" -o /dev/null -w 'GET /flush from another site: %{http_code}\n' -H 'Sec-Fetch-Site: cross-site' "${U}flush"
curl -s -o /dev/null -w 'GET http://: %{http_code}\n' "${U/https:/http:}"
curl -sk -o /dev/null -w 'GET with an unknown Host: %{http_code}\n' -H 'Host: not-this-server.example' https://127.0.0.1/ || true
systemctl show -p IPAddressDeny nginx.service php8.5-fpm.service
curl -s http://127.0.0.1/healthz
GET /login: 200
GET /register: 200
GET /health: 200
GET /install: 404
GET /index.php/login: 404
GET /api/v1/cron/x: 404
GET /.env: 404
GET /storage/logs/laravel.log: 404
GET /composer.json: 404
GET /flush from another site: 403
GET http://: 301
GET with an unknown Host: 000
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
ok
/registeranswers with "Registration is currently not available": the owner is the only account until you invite others.- The installer, the front controller path, the web cron URL, the configuration file, logs and sources are refused. Requests for any name other than the configured one are dropped (
000: nginx closes the connection). - IPAddressDeny is the metadata-service block on nginx and PHP; the first-boot service and the cron have the same one.
http://127.0.0.1/healthzreturnsokfor a monitoring agent on the instance.
Step 13: Back up your data
Your data is the SQLite database plus any attachments in /var/lib/firefly-iii/storage/upload. Keep /etc/firefly-iii/env too: its APP_KEY decrypts the attachments and stored secrets, and a backup restored without it cannot read them. Copy them to files only root can read, and keep copies off the instance (for example in an encrypted S3 bucket):
sudo -u firefly sqlite3 -readonly /var/lib/firefly-iii/storage/database/database.sqlite .dump \
| sudo sh -c 'umask 077; gzip > /var/backups/firefly-iii-$(date +%F).sql.gz'
sudo sh -c 'umask 077; tar -C /var/lib/firefly-iii/storage -czf /var/backups/firefly-iii-uploads-$(date +%F).tar.gz upload'
sudo sh -c 'umask 077; cp /etc/firefly-iii/env /var/backups/firefly-iii-env-$(date +%F)'
sudo ls /var/backups/ | grep firefly-iii
firefly-iii-2026-11-07.sql.gz
firefly-iii-env-2026-11-07
firefly-iii-uploads-2026-11-07.tar.gz
Firefly III also exports transactions as CSV under Options > Export data. EBS snapshots of the instance are a simple way to keep point-in-time copies of the whole server.
Step 14: Updates
Ubuntu and PHP security updates install automatically (unattended upgrades). New Firefly III releases are published as new versions of this listing: launch the new version, stop its services, copy your database, uploads and /etc/firefly-iii/env across (keeping the new instance's APP_URL), then run sudo firefly-iii-artisan migrate --force and start the services again.
Step 15: Licences and source
ls /usr/share/doc/firefly-iii/
sed -n '1,4p' /usr/share/doc/firefly-iii/SOURCE.txt
LICENSE
SOURCE.txt
Firefly III 6.7.9 on Ubuntu 24.04 by cloudimg - source and changes
Upstream: the official Firefly III 6.7.9 release archive, https://github.com/firefly-iii/firefly-iii/releases/download/v6.7.9/FireflyIII-v6.7.9.zip
(sha256 f806ac9c8d34f570d954172a6ea4718ee387350d6c9b9b8ef7ba02902dc63752; GitHub tag v6.7.9).
Firefly III is licensed under the GNU Affero General Public License version 3 or later. SOURCE.txt names the exact upstream archive, the files that are not shipped (tests, sample configuration, Apache and IIS files) and the six Symfony libraries raised for security, so the corresponding source of the running version is the upstream release plus those six releases.
Security notes
- Secrets. The owner password is in
/root/firefly-iii-credentials.txtuntil you remove it; Firefly III stores only its bcrypt hash. APP_KEY and the cron token are in/etc/firefly-iii/env, readable by root and thefireflygroup only (not by the web server). The TLS key is in/etc/nginx/ssl/firefly-iii.key(root only). No secret is on any command line, in a systemd unit or in a log. - Client addresses. nginx gives Firefly III the address of the connection; forwarding headers sent by clients are replaced or removed. Behind a load balancer every request therefore comes from the load balancer's address.
- PHP. The PHP pool runs as
firefly, cannot start other programs, and is confined to Firefly III's code, data and configuration directories.
Support
cloudimg provides support for this image by email at support@cloudimg.co.uk. Firefly III documentation is at docs.firefly-iii.org.