SSD Nodes Learn Hosting plans →
How to do am Matt ConnorBy Matt Connor · Updated 2026-10-28

How to Host Actual Budget on Your VPS

Run Actual Budget with Docker Compose on your VPS, then set HTTPS, create your first budget, import bank data, and back up the data volume safely.

Wetin you dey build

Actual Budget na self-hosted envelope budgeting app, and 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 comfortably for the smallest VPS wey you fit rent, because the server mostly dey store files and sync dem.

E good make you 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 account list, budget files, and change log wey allow phone and laptop agree with each other. Na why the app still dey work when server dey down, and na why losing the server no go lose your budget as long as one client still get copy.

Why server need HTTPS

Actual dey ask for HTTPS, and no be mere formality. Actual need one browser feature wey dem call SharedArrayBuffer to run at all, and browsers only enable am when page dey cross-origin isolated for wetin specification call secure context. Secure context na https:// or http://localhost. Load app from http://203.0.113.10:5006 for browser on another machine, and browser no go ever give that feature to the page, so the budget no go load. Phones no get exception. Actual FAQ talk say official iOS app dey beta testing and e dey connect to your own server, and say official Android app never dey yet. So for most phones, you go use the same web app, install am for home screen as Progressive Web App, and the same secure-context rule still apply.

So, two setups fit work. Put real certificate for real name in front of the container, na this guide dey do. Or give server self-signed certificate with ACTUAL_HTTPS_KEY and ACTUAL_HTTPS_CERT, as the project document am, 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

Install Docker first if na fresh box. If Compose file syntax 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 low-power machines.

The container write everything under /data. Inside am, you 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 wipe your budget. ACTUAL_DATA_DIR fit move am, but the default dey okay.

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

Start am:

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

The log go settle when 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. curl: (7) Failed to connect mean say nothing accept the connection on 127.0.0.1:5006. Run docker compose ps --all, because plain docker compose ps dey list only containers wey dey run. If the container show as exited, the usual cause na permission problem for the mounted volume. You go see am as an EACCES line for the log.

Put certificate and real name in front

Point A record go the VPS, budget.example.com, then wait make e resolve. After that, install nginx and issue the certificate. The Certbot for Ubuntu 24.04 with nginx guide explain the issuance and renewal timer complete.

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. Normal sync dey send only the changes you make, but the complete budget file go upload when you first register budget with the server and again anytime you reset sync. Nginx default request body na 1 MB, so once the file pass that size, those full uploads 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. Set the nginx limit above whichever limit 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 this one password dey protect the whole server, so generate long random one and keep am for place wey you fit find am again, like a self-hosted Vaultwarden password manager. Out of the box, no user accounts dey to create: di server get one password, so to share budget mean say you share that password. Separate logins only dey possible if you connect OpenID provider, wey Actual support for multi-user setups.

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

Set your starting balances based on 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 import

Na here honesty matter pass enthusiasm, because import na the main reason wey make people give up on self-hosted budgeting.

Manual entry na the basic option and e always work. For envelope method, na arguably the main point, because when you type purchase, you go notice am.

File import fit handle plenty transactions. Actual dey read CSV, QIF, OFX, QFX and CAMT, and every bank dey export at least one of dem. Import each account from the account screen, then map the columns when the file na CSV.

Automatic bank sync dey available, and e need third-party service because server no fit talk to banks by itself. Actual 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, then add dem to the server. SimpleFIN Bridge dey charge 15 US dollars per year plus tax for up to 25 institutions as of October 2026, while the others get different prices.

Accept these two limits before you depend on this. API credentials dey on the server and end-to-end encryption no cover dem, because server need use dem. And nothing dey sync while Actual close: automatic syncing dey off by default, and after you turn am on, e only run while the app dey open, so server no dey poll your banks by itself.

Backups, bikos na files nomo

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

The only trap na SQLite. If you copy account.sqlite while server still dey write to am, e fit capture transaction wey never finish. You no go know until you try restore am. 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 cover repository setup, retention, and restore drill. Run the restore drill. Backup wey you never restore na just guess.

Actual own automatic backups na separate thing, and e good make you know about am, but dem dey only for the desktop app. E dey keep up to 10 recent copies of the budget file. You fit reach dem from File > Load Backup. This one cover situation where you delete category by mistake, without touching the server at all. The web app for browser no dey keep these copies. So if everybody for house dey use browser, the server backup above na the only one wey you get.

Server update kina

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

Compose go recreate the container from the new image and reconnect the same volume, so the data go remain safe. Update the clients too. Client wey old pass the data wey e receive go stop applying changes and show Update Required message. Make backup before you jump to major version, because migrations go run for first start and downgrade no dey possible. Actual dey tolerate floating latest tag because e state na directory of files, but app wey dey carry real database no be so. self-hosting Chatwoot explain the pinned tags and pre-upgrade dump wey this process require.

Wetin fit spoil, and wetin you go see

The app loads but sync never finishes. Check the nginx access log for 413. That one mean say client_max_body_size set too low. 502 instead mean say nginx no get usable response from the upstream. Most times na because the container don stop or e no dey listen on 5006, so check docker compose ps --all and the container log before anything else.

The app no go load for another device, or e report say SharedArrayBuffer dey miss. The page no dey for secure context. The address bar go show http:// with an IP address or hostname wey no be localhost. Fix the certificate instead of finding workaround.

A message say the budget file no compatible with this version. The file don open for newer version of Actual pass the client wey you dey use now. Update the client to at least that version, or restore backup wey you take before the newer version touch the file, then reload.

The container dey restart for loop. Read docker compose logs actual. Permission error for /data mean say the mounted directory no writable by the container's user. Address-in-use error mean say something else don already hold 5006 for loopback.

First load dey feel slow. The whole budget file dey download go browser when you open am. Na one large transfer, 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 need the browser `SharedArrayBuffer feature to run, and browsers only enable am for secure context, meaning https:// or http://localhost. If na plain HTTP from another machine, the app no go load. Official iOS app dey beta testing, and no official Android app dey yet, so most phones dey use the same web app and face the same rule. (FAQ) Use Let's Encrypt certificate for real hostname, or self-signed certificate with ACTUAL_HTTPS_KEY and ACTUAL_HTTPS_CERT` if certificate warning for every device no be problem for you.

Actual fit import my bank transactions automatically?

Only through third-party service wey you sign up for 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 your server and end-to-end encryption no cover dem. Automatic syncing dey off by default, and after you enable am, e only dey run while Actual dey open, so nothing dey poll for background on the server. CSV, QIF, OFX, QFX and CAMT import no need any third party at all.

Wetin exactly I need back up?

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?

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

How much server Actual Budget need?

Very little. The container dey serve static assets and files, while budget calculations dey happen for the browser. One shared vCPU with 1 GB of RAM dey run am without problem, and the data directory for household budget with several years of history dey stay within tens of megabytes. Your backups and other containers dey cause disk pressure, not Actual. If you dey size box wey must run something hungrier alongside am, photo server usually dey set the minimum, so check how much RAM PhotoPrism and Immich really need before you pick plan. The same logic apply to media stack: transcoding dey decide the plan, while browser frontend like Halcyon, wey dey turn Jellyfin library into 90s video store wey you fit walk through cost about as little as Actual.

Dis article help you?