What you get, and what lives on the server
Actual keeps your budget on each device you use and syncs the changes through your server, so it keeps working when you are offline. According to the install guide, you need a server to sync between devices, to use Actual in a web browser or on your phone, to connect a bank, and to use the API. Everything else, from budgeting to reports, works on a single device without one.
- The account database (
server-files/account.sqlitein the data folder): the server password (stored hashed), your budget list and the login session, as the configuration page describes. The sync-server source code (secrets-service.js) keeps bank connection secrets there too. - The budget files (
user-files/): one blob per budget, unreadable to the server if you turn on encryption. - Your devices: each holds a full copy, and the docs point out it is not encrypted there; they suggest full disk encryption on your laptop.
How much server does a budget need?
Very little, as far as anyone can say: the docs never state how many cores or how much RAM it wants, and it ships an Alpine-based image tag meant for low-powered devices. Our own view is that a budget server is idle almost all the time between syncs, so what you should care about is that the data is safe and that you can get back in when something goes wrong.
- Our dedicated servers give you the whole machine: no one else running anything on that hardware, full root access and an unmetered port, so nothing about your finances shares a box with strangers.
- The compact cartridge servers are made for one job, which is exactly this. The out-of-band console (the screen and the power button when SSH will not answer) comes with rack servers, blades and two cartridge models, the m510 and m710x; any other cartridge offers power control alone.
- Avoid the X-Gene ARM cartridge, whose only system choices are Ubuntu 16.04 and 18.04.
- A ready server reaches you within 30 minutes of payment approval; the ready servers page shows what is racked. One IPv4 address and IPv6 at no charge come with it.
Step 1: lock the door, then start the container
The project's own compose file publishes port 5006 on every address. Docker then listens on both 0.0.0.0 and [::], and traffic arriving there bypasses ufw for IPv4. For IPv6 it bypasses ufw too if you have switched on IPv6 support inside Docker; if you have not, IPv6 goes through docker-proxy, and ufw sees that. The safe answer is to publish 5006 on 127.0.0.1 only and switch ufw on, allowing just SSH, before anything starts. Make sure Docker Engine is version 28 or later: from 28 on, a 127.0.0.1 port really is reachable from the server alone.
Before you start, the server needs Ubuntu and Docker, including docker compose, installed the way Docker's own guide describes. Then paste this as root. It writes a compose file based on the official one with the port pinned to loopback, plus the Caddy service from the docs' proxy examples, which waits for step 3.
# as root; /srv/actual-budget is our choice of folder
sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw
ufw allow 22/tcp
ufw --force enable
docker version --format 'Engine {{.Server.Version}}'
install -d -m 700 /srv/actual-budget
cd /srv/actual-budget && mkdir -p actual-data
cat > docker-compose.yml <<'EOF'
services:
actual:
image: docker.io/actualbudget/actual-server:latest
restart: unless-stopped
ports:
- '127.0.0.1:5006:5006' # this machine only
volumes:
- ./actual-data:/data
caddy:
image: caddy:alpine
restart: unless-stopped
ports:
- '80:80'
- '443:443'
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy/data:/data
- ./caddy/config:/config
EOF
docker compose up -d actual
Step 2: pick your password before anyone else can
A brand-new Actual server has no password, and its sync-server code (app-account.js and account-db.js) lets the very first visitor set one; after that it says no. That is why the port stays private until you have done this.
- On your own computer, open a tunnel with your normal SSH login:
ssh -N -L 8006:127.0.0.1:5006 you@your-server. - Visit
http://localhost:8006in your browser. To the browser this is localhost, which the HTTPS guide accepts without a certificate. - Choose a strong server password, then create your first budget or import one.
- Stop the tunnel with Ctrl+C.
Forgot the password later? Your budgets are safe. From the /srv/actual-budget folder, run docker compose exec actual node src/scripts/reset-password.js (the docs' Docker reset script, which sits under /app, the image's working folder) and type a new one twice.
Step 3: put it on HTTPS so your phone and laptop can use it
Actual runs its database engine inside your browser, and the docs explain that this needs a "secure context" plus two special headers. Outside localhost, that means HTTPS. The docs recommend a reverse proxy with SSL for anything on the internet, and their simplest example is Caddy, which fetches and renews the certificate on its own.
First create a DNS record for your chosen name pointing at the server (an A record, plus AAAA if you use IPv6). Replace money.example.com with that name and run:
cd /srv/actual-budget
cat > Caddyfile <<'EOF'
money.example.com {
encode gzip zstd
reverse_proxy actual:5006
}
EOF
ufw allow 80,443/tcp
docker compose up -d caddy
docker compose logs --tail 50 caddy
Why the ufw allow line, when Docker publishes 80 and 443 anyway? On IPv6, unless Docker runs its own IPv6 networking, ufw sits in front of docker-proxy, so without the rule IPv6 visitors and the certificate check over AAAA would be turned away. If you ever move the proxy out of Docker onto the server itself, the same rule is what lets anyone in at all. Prefer clicking to config files? Our Nginx Proxy Manager guide covers that route; with plain nginx, follow the docs' note on hiding duplicate COOP/COEP headers.
Linking your bank, and a second lock on your budget
Bank sync is optional and only works with a server. You sign up with a provider yourself and paste their credentials into Actual. The docs currently describe Akahu for New Zealand, Enable Banking for Europe, SimpleFIN Bridge for North America and Pluggy.ai for Brazil, and note that GoCardless Bank Account Data has accepted no new accounts since July 2025. Automatic syncing starts switched off, and it only runs while Actual is open on one of your devices.
For extra privacy you can turn on end-to-end encryption in a budget's settings. You pick a second password (the docs ask for one unlike your server password), and from then on the server only stores scrambled data. Two warnings from the docs: lose that password and your last local copy, and nobody can recover the budget; and there is no off switch, only exporting and importing a fresh copy.
Encryption covers the budget, not the bank link. Bank credentials stay on the server in plain reach of anyone with its database, so treat the data folder and every backup of it as private.
Copies you can restore, and updates without surprises
You manage the server; we keep the hardware and the network running. So the backups are yours to set up, and the script below is one way to do it: save it as /srv/actual-budget/backup.sh and make it private with chmod 700. It pauses the container while the copy runs, so the database files are captured in a consistent state, and starts it again even if the copy fails.
# /srv/actual-budget/backup.sh -- our script, run as root
set -euo pipefail
umask 077
dest=/var/backups/actual-budget
mkdir -p "$dest" && chmod 700 "$dest"
cd /srv/actual-budget
docker compose stop actual
trap 'docker compose start actual' EXIT
stamp=$(date +%F)
tar -czf "$dest/budget-$stamp.tar.gz" actual-data docker-compose.yml Caddyfile
find "$dest" -name 'budget-*.tar.gz' -mtime +14 -delete
- Make it run nightly with a file named
/etc/cron.d/actual-budget-backupcontaining this single line:15 4 * * * root umask 077; mkdir -p /var/backups/actual-budget; chmod 700 /var/backups/actual-budget; bash /srv/actual-budget/backup.sh > /var/backups/actual-budget/backup-$(date +\%F).log 2>&1. - Copy the archives to somewhere other than this server, regularly.
- To roll back, name the archive and let this check it before anything is moved; your current folder is renamed, not deleted: `cd /srv/actual-budget; a=/var/backups/actual-budget/budget-2026-10-22.tar.gz; if [ -s "$a" ] && tar -tzf "$a" actual-data > /dev/null; then docker compose stop actual && mv actual-data "actual-data.old-$(date +%s)" && tar -xzf "$a" actual-data && docker compose up -d actual; else echo "no usable archive at $a"; fi`.
- Keep the app's own export too: in Settings, the Data section has Export data, which saves one budget as a zip. Bring it back from the file screen with Import file, then Actual.
- To update, take a backup, then in
/srv/actual-budgetrundocker compose pullto fetch the new image anddocker compose up -dto restart on it, the pair the Docker guide gives. Stick with thelatesttag: the docs link one restore error to switching anightlyimage for a stable release.
The licence, and when to choose something else
Actual Budget is released under the MIT licence (the LICENSE.txt file in its GitHub repository). In general terms (not legal advice): you can use it, change it, and pass it on or sell copies, provided you include the copyright and permission notice with the copies you hand out. Running it on your own server for yourself asks nothing of you, and it comes without any warranty.
It may not be the right fit if you want several separate logins (that needs an OpenID provider), if two people often edit the same budget at once (the docs suggest avoiding it), or if you want native phone apps (the official ones are deprecated; the web app can be added to your home screen). Curious about another finance app you can host yourself? Look at Firefly III on your own server. Want your paperwork next to your budget? See Paperless-ngx.
Questions people ask
- Can I just open the server's IP address on port 5006?
- Not for everyday use. Actual's docs say its database engine needs a secure context, reached through HTTPS or localhost, and a public address over plain HTTP is neither, so the app will not run properly. Use the SSH tunnel while setting up, and your HTTPS name after that.
- Where is my budget actually kept?
- In two places at once: a full copy on every device you use, and the synced copy in the server's data folder. That is why Actual keeps working offline, and also why your server backups and the in-app export both matter.
- Is the Docker image on Docker Hub or GitHub?
- Both. The project pushes it to Docker Hub as
actualbudget/actual-serverand to GitHub's registry asghcr.io/actualbudget/actual, and says either works the same. Thelatesttag is the current official release. - Do I need encryption if the server is mine?
- It is your call. Encryption means even someone who copies your server's files cannot read your budget, at the cost of a password you must never lose. It never covers bank sync credentials, so protecting the server itself matters either way.
- How do I know an update went well?
- After
docker compose up -d, sign in and open a budget. If a device says syncing was reset or needs an update, follow the prompt; the docs say Actual guides you through it. Keep the backup you took before the update until you are happy.