SSD Nodes Learn 8GB RAM — $66/yr
How to do am Matt ConnorBy Matt Connor · Updated 2026-08-02

How to Host Actual Budget for Your Own VPS

Run Actual Budget with Docker Compose on your VPS. Learn the data volume, why browsers need HTTPS, how to create your first budget, import banks, and back up.

Wetin You Dey Build

Actual Budget na self-hosted envelope budgeting app. E na the usual answer when people dey find YNAB alternative wey dem fit host by demself. The server na one container, one data volume, and one HTTPS name. Everything wey normal budget need go run well for the smallest VPS wey you fit rent, because the server mainly dey store files and sync dem.

You need understand the architecture before you type anything. The budget itself na SQLite database wey dey inside your browser and inside each mobile app. The server wey you wan install na sync endpoint: e dey hold the account list, the budget files, and the change log wey make phone and laptop agree. Na why the app still dey work when the server dey down, and na why losing the server no go make you lose your budget as long as one client still get one copy.

Why server need HTTPS

Actual dey ask for HTTPS, and e no be just formality. Browsers only expose Web Crypto API, wey Actual dey use for end-to-end encryption, inside wetin the specification dey call secure context. Secure context na https:// or http://localhost. If you load the app from http://203.0.113.10:5006 for browser wey dey another machine, those features no go dey there, because the browser no ever hand dem over to the page. The official mobile builds too dey reject plain http:// server URL.

So, two setups fit work. Put real certificate for real name in front of the container, na this guide dey do. Or give the server self-signed certificate with ACTUAL_HTTPS_KEY and ACTUAL_HTTPS_CERT, as the project documentation explain, and accept browser warning for every device. Free certificate from Let's Encrypt dey take five minutes, so use the first option.

Install Actual Budget with Docker Compose

First install Docker if the server still dey fresh. If Compose file syntax still new to you, the Docker Compose basics for a VPS guide explain the fields wey dey below.

sudo install -d -m 755 /opt/actual
sudo install -d -m 700 /opt/actual/data

Write /opt/actual/docker-compose.yml:

services:
  actual:
    image: actualbudget/actual-server:latest
    container_name: actual
    restart: unless-stopped
    ports:
      - '127.0.0.1:5006:5006'
    volumes:
      - ./data:/data

Three details for that file matter.

The image na actualbudget/actual-server:latest, wey the project publish for Docker Hub and mirror for ghcr.io/actualbudget/actual. E get latest-alpine tag for machines wey no get plenty power.

The container dey write everything under /data. Inside am, you go get server-files, wey hold account.sqlite with your login and session tokens, and user-files, wey hold the budget files themselves. Mount that path, otherwise the next docker compose pull go throw away your budget. ACTUAL_DATA_DIR fit move am, but the default dey okay.

The port dey publish for 127.0.0.1 only. Plain 5006:5006 go publish for every interface, and Docker dey write im own rules before ufw, so the app go open to the internet even when firewall get deny-all rule. Why Docker published ports bypass ufw explain this surprise. When you bind to loopback, na only the reverse proxy for the same server fit reach am.

Start am:

cd /opt/actual
docker compose up --detach
docker compose logs -f actual

The log go settle once the server report say e dey listen on port 5006. Check am locally before you touch DNS:

curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5006/

A 200 mean say the app dey serve requests. curl: (7) Failed to connect mean say the container no dey run, and docker compose ps go show say e don exit. The usual cause na permission problem for the mounted volume, wey go show as an EACCES line for the log.

Put certificate and real name for front

Point an A record to the VPS, budget.example.com, and wait make e resolve. Then install nginx and issue the certificate. The guide Certbot for Ubuntu 24.04 with nginx explain issuance and renewal timer finish.

The proxy block:

server {
    listen 443 ssl;
    http2 on;
    server_name budget.example.com;

    ssl_certificate     /etc/letsencrypt/live/budget.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/budget.example.com/privkey.pem;

    client_max_body_size 100m;

    location / {
        proxy_pass http://127.0.0.1:5006;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

client_max_body_size na the line wey people dey forget. The budget file dey upload complete during full sync. Nginx default request body limit na 1 MB. So when the file pass that size, sync go fail with 413 Request Entity Too Large for nginx access log, while the app go show only general sync error. The server get separate limits: ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB default na 20, and ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB default na 50. So set the nginx limit above the one wey apply to you.

Reload and test:

sudo nginx -t && sudo systemctl reload nginx
curl -fsS -o /dev/null -w '%{http_code}\n' https://budget.example.com/

First run: di password and your first budget file

Open https://budget.example.com for browser. Di first screen go ask you to set server password. Na that one password dey protect di whole server, so generate long random one and keep am for place wey you go fit find am again, like a password manager wey you host yourself. You no need create user accounts. Actual server na single-password by design, so to share budget mean say you dey share that password.

Then create budget file. Actual go ask whether to enable end-to-end encryption. Answer yes, and di server go store only ciphertext, wey correct for financial data on rented machine. But e get real cost: di encryption password no dey reach di server, so if you lose am, di file don go and no reset dey available. Write am down before you click pass that screen.

Set your starting balances from your bank current figures instead of importing years of history. Envelope budgeting dey work forward from di money wey you get now, so empty history no cost you anything.

Transactions wey dey enter

Na here honesty pass enthusiasm matter, because import na the main reason people dey comot from self-hosted budgeting.

Manual entry na the baseline, and e dey always work. For envelope method, na arguably the main point, because when you type purchase, e make you notice am.

File import dey handle plenty transactions. Actual dey read CSV, QIF, OFX and QFX, and every bank dey export at least one of dem. Import per account from the account screen, map the columns once, and Actual go remember that layout for the account.

Automatic bank sync dey available, and e need third-party service because the server no fit talk to banks by itself. Actual dey support SimpleFIN Bridge for North American banks, Enable Banking for Europe, Akahu for New Zealand, and Pluggy.ai for Brazil. GoCardless still dey supported, but e no dey accept new accounts. You go sign up with the provider yourself, generate credentials, and add dem to the server. SimpleFIN Bridge dey charge 15 US dollars per year for up to 25 institutions as of July 2026, while the others get different pricing.

Accept these two limits before you rely on am. The API credentials dey on the server and end-to-end encryption no cover dem, because the server need use dem. And Actual no dey poll: syncing na button wey you press, no be background job.

Backups, because na na files

Everything wey you care about dey under /opt/actual/data. No export step dey, and no database dump wey you need script.

The only trap na SQLite. If you copy account.sqlite while the server dey write to am, you fit capture transaction wey never finish. You no go know until you try restore. Stop the container for the few seconds wey the copy dey take:

cd /opt/actual
docker compose stop
restic -r sftp:backup@backup.example.com:/srv/restic backup /opt/actual/data
docker compose start

Put am for schedule with the approach for restic backups for VPS, wey covers repository setup, retention and the restore drill. Run the restore drill. Any backup wey you never restore na guess.

Actual own client-side backups na separate matter, and e good make you know about am. The browser dey keep recent copies of the budget file. You fit reach dem from the file menu. This one handles "I delete category by mistake" without touching the server at all.

Update di server

cd /opt/actual
docker compose pull
docker compose up --detach

Compose go recreate the container from the new image and attach the same volume again, so the data no go loss. Update the clients too. Server and app versions suppose stay close. If client old well-well pass the server, e fit refuse to sync and show version mismatch message. Make backup before major version jump, because migrations go run for first start and downgrade path no dey.

Wetin fit spoil, and wetin you go see

The app go load but sync no go ever finish. Check nginx access log for 413. That one mean say client_max_body_size set too low. If na 502, e mean say nginx dey up but the container no dey up.

Encryption options no dey, or the mobile app no accept the URL. The page no dey for secure context. Address bar go show http:// with IP address or hostname wey no be localhost. Fix the certificate instead of finding workaround.

Message say budget file no compatible with this version. Client and server versions don drift apart. Update both to the same release, then reload.

The container dey restart inside loop. Read docker compose logs actual. Permission error on /data mean say mounted directory no writable by the container user. Address-in-use error mean say another thing don already hold 5006 on loopback.

First load dey feel slow. The whole budget file dey download go browser when you open am. Na one large transfer first, then local reads. E no be server sizing problem, and adding RAM no go change am.

FAQ

Actual Budget need HTTPS to work?

Yes, for practical use. Actual end-to-end encryption dey use browser Web Crypto API, and browsers only dey expose am for secure context, wey mean https:// or http://localhost. If you use plain HTTP from another machine, those features no go dey available, and the official mobile apps no go accept plain HTTP server URL. Use Let's Encrypt certificate for real hostname, or self-signed certificate with ACTUAL_HTTPS_KEY and ACTUAL_HTTPS_CERT if na desktop browser you go ever use.

Actual fit import my bank transactions automatically?

Only through third-party service wey you register for by yourself: SimpleFIN Bridge for North America, Enable Banking for Europe, Akahu for New Zealand, or Pluggy.ai for Brazil. GoCardless dey supported, but e no dey accept new accounts. Those API credentials dey on your server, and end-to-end encryption no cover dem. Sync sef na manual, so na you go press button; nothing dey poll for background. CSV, QIF, OFX and QFX import no need any third party at all.

Wetin exactly I need back up?

Na the mounted data directory, wey be /opt/actual/data for this guide. E contain server-files/account.sqlite with logins and sessions, and user-files with the budget files. Stop the container before you copy am, because copying live SQLite database fit capture partial write. Nothing else for the server dey hold state.

Wetin go happen if I lose the encryption password?

The file no fit recover. The password never reach the server, and na the main reason for end-to-end encryption, so no reset dey and no support path dey. Store am inside password manager immediately after you create the file, and keep one copy for somewhere wey no depend on this same server.

How much server resource Actual Budget need?

E need very little. The container dey serve static assets and files, while budget calculations dey happen inside browser. One shared vCPU with 1 GB of RAM fit run am without problem, and the data directory for household budget with several years of history dey remain around tens of megabytes. Disk pressure dey come from your backups and other containers, not from Actual.