Actual Budget: Privacy-Focused Budgeting
Local-first budgeting app with sync. Zero-based budgeting and bank import support.
Introduction
Actual Budget is a powerful, privacy-focused personal finance application that brings zero-based budgeting to your homelab. Originally a paid service, Actual was open-sourced in 2022 and has since become one of the most popular self-hosted budgeting solutions with over 13,000 GitHub stars.
What is Zero-Based Budgeting?
Zero-based budgeting is a method where every dollar you earn is assigned a specific job before the month begins. Unlike traditional budgeting that tracks past spending, zero-based budgeting is proactive - you decide where your money goes before you spend it. This approach helps you:
- Eliminate wasteful spending by being intentional with every dollar
- Build emergency funds and savings systematically
- Break the paycheck-to-paycheck cycle
- Achieve financial goals faster through focused allocation
Why Choose Actual Budget?
Complete Financial Privacy
Your bank account data, spending habits, and financial information never leave your server. Commercial apps like Mint, YNAB, or Personal Capital aggregate your data for advertising and data broker sales. With Actual Budget, you maintain complete control.
Modern Features Without Subscriptions
YNAB (You Need A Budget) charges $99/year for similar features. Actual Budget is completely free with:
- Real-time bank synchronization via SimpleFIN or GoCardless
- Multi-device sync with end-to-end encryption
- Mobile-optimized web interface
- Powerful reporting and analytics
- Budget templates and recurring transactions
- Goal tracking and debt paydown planning
Active Development and Community
With regular updates, an active Discord community of 3,000+ users, and comprehensive documentation, Actual Budget continues to improve while respecting your privacy.
Prerequisites
Before starting, ensure you have:
Required
- Docker and Docker Compose v2.0+ installed
- 512MB RAM minimum (1GB recommended)
- 2GB free disk space
- Basic understanding of Docker concepts
Optional but Recommended
- SimpleFIN Bridge account for automatic bank sync (USD 1.50/month)
- GoCardless account for European bank sync (free tier available)
- Reverse proxy for HTTPS access (Traefik, Caddy, or Nginx Proxy Manager)
- Mobile device for on-the-go budget access
Financial Data Preparation
Have ready:
- Current bank account balances
- Recent transactions for import (CSV or OFX format)
- List of monthly bills and due dates
- Financial goals and savings targets
Understanding Actual Budget’s Architecture
Actual Budget consists of two main components:
Web Frontend
- React-based single-page application
- Runs entirely in browser after initial load
- Stores data locally with IndexedDB
- Syncs changes to server for multi-device access
Sync Server
- Node.js backend for data synchronization
- Handles encryption/decryption of budget data
- Manages user authentication
- Stores encrypted budget files
Data Flow:
User Device → Browser (Frontend) → Sync Server → Encrypted Storage
↓ ↓ ↓
Local DB UI Updates Multi-device Sync All budget data is encrypted before leaving your device, ensuring privacy even if the server is compromised.
Installation with Docker Compose
Step 1: Create Project Directory
# Create directory structure
mkdir -p ~/actual-budget
cd ~/actual-budget
# Create data directory
mkdir -p ./data Step 2: Create Docker Compose Configuration
Create docker-compose.yml:
version: '3.8'
services:
actual-server:
image: actualbudget/actual-server:latest
container_name: actual-budget
restart: unless-stopped
ports:
- '5006:5006'
volumes:
- ./data:/data
environment:
# Optional: Set a password for accessing the server
# If not set, no authentication is required
# ACTUAL_PASSWORD: 'your-secure-password-here'
# Optional: Configure upload limits
ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB: 20
ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB: 50
ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB: 20
labels:
# Traefik labels (if using Traefik)
- "traefik.enable=true"
- "traefik.http.routers.actual.rule=Host(`budget.yourdomain.com`)"
- "traefik.http.routers.actual.entrypoints=websecure"
- "traefik.http.routers.actual.tls=true"
- "traefik.http.routers.actual.tls.certresolver=letsencrypt"
- "traefik.http.services.actual.loadbalancer.server.port=5006"
networks:
- actual-network
networks:
actual-network:
driver: bridge Step 3: Configure Environment Variables (Optional)
Create .env file for sensitive configuration:
# Server password (optional but recommended for production)
ACTUAL_PASSWORD=your-secure-password-here
# Upload limits (adjust based on budget file size)
ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB=20
ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB=50
ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB=20
# Your domain (for reverse proxy)
DOMAIN=budget.yourdomain.com Update docker-compose.yml to use environment file:
services:
actual-server:
env_file: .env
# ... rest of configuration Step 4: Deploy the Stack
# Start Actual Budget
docker compose up -d
# Verify container is running
docker compose ps
# Check logs for any errors
docker compose logs -f actual-server
# You should see: "Listening on 0.0.0.0:5006..." Step 5: Initial Access and Setup
Navigate to http://YOUR-SERVER-IP:5006 in your browser.
First-Time Setup:
- Click “Create New Budget File”
- Enter budget name (e.g., “Family Budget 2025”)
- Set optional password for budget file encryption
- Choose starting month (typically current month)
- Click “Create”
Important: If you set ACTUAL_PASSWORD in your environment, you’ll be prompted to enter it before accessing the application.
Configuring Your First Budget
Understanding Budget Categories
Actual Budget uses category groups to organize spending:
Default Category Groups:
- Food (Groceries, Dining Out)
- Bills (Rent/Mortgage, Utilities, Phone)
- Transportation (Car Payment, Gas, Insurance)
- Personal (Clothing, Health, Entertainment)
- Savings (Emergency Fund, Vacation, Goals)
Creating a Zero-Based Budget
Step 1: Enter Your Income
1. Click "Add Income" in current month
2. Enter paycheck amount: $3,500
3. Name: "Salary - Main Job"
4. Date: Expected payment date
5. Set as recurring if regular paycheck Step 2: Assign Every Dollar
Category Budgeting Example (Monthly):
Income: $3,500
├─ Rent/Mortgage: $1,200
├─ Utilities: $150
├─ Groceries: $450
├─ Transportation: $300
├─ Insurance: $200
├─ Debt Payment: $400
├─ Emergency Fund: $300
├─ Entertainment: $150
├─ Dining Out: $100
├─ Clothing: $75
├─ Miscellaneous: $75
└─ Savings Goal: $100
Total: $3,500 (Zero Remaining) The Key Rule: “To Be Budgeted” should equal $0 when done. Every dollar has a job.
Adding Accounts
Step 1: Create Bank Accounts
1. Sidebar → Accounts → "+ Add Account"
2. Select type:
├─ Checking (everyday spending)
├─ Savings (emergency fund, goals)
├─ Credit Card (track debt)
├─ Investment (retirement, stocks)
└─ Other (mortgage, loans)
3. Enter starting balance
4. Click "Create" Step 2: Reconcile Starting Balances
After creating accounts, reconcile them with actual bank balances:
1. Click account name
2. Click "Reconcile" button
3. Enter current bank balance
4. Review discrepancies
5. Add missing transactions
6. Mark as reconciled Importing Transactions
From CSV or OFX Files:
1. Open account
2. Click "Import" button
3. Select file type (CSV, OFX, QFX, QIF)
4. Choose file from bank download
5. Map columns:
├─ Date → Transaction Date
├─ Payee → Merchant/Vendor
├─ Amount → Transaction Amount
└─ Notes → Optional memo
6. Review imported transactions
7. Click "Import X Transactions" CSV Format Example:
Date,Payee,Amount,Notes
2025-01-15,Grocery Store,-125.43,Weekly shopping
2025-01-14,Gas Station,-45.00,Fuel
2025-01-13,Salary,3500.00,Biweekly paycheck
2025-01-12,Electric Company,-89.50,Monthly bill Tips for Clean Imports:
- Use ISO date format: YYYY-MM-DD
- Negative amounts for expenses, positive for income
- Remove currency symbols (use -125.43, not -$125.43)
- One transaction per line
- Include header row
Bank Connection Setup
Option 1: SimpleFIN Bridge (Recommended for US Users)
SimpleFIN provides secure, read-only access to 10,000+ US financial institutions.
Setup Process:
- Create SimpleFIN account at
https://beta-bridge.simplefin.org - Connect your bank accounts (uses OAuth, no credentials stored)
- Generate SimpleFIN access token
- In Actual Budget:
Settings → Experimental → SimpleFIN ├─ Enable SimpleFIN ├─ Paste access token ├─ Click "Link Accounts" └─ Select accounts to sync
Features:
- Automatic transaction import (daily sync)
- No credential storage
- OAuth-based authentication
- Support for checking, savings, credit cards
- Cost: USD 1.50/month per user
Security: SimpleFIN uses banking APIs, not screen scraping. Your credentials never touch Actual Budget or SimpleFIN servers.
Option 2: GoCardless (European Banks)
For European users, GoCardless provides PSD2-compliant bank access.
Setup Process:
- Register at
https://bankaccountdata.gocardless.com - Create application (free tier: 100 transactions/month)
- Obtain API keys (Secret ID and Secret Key)
- In Actual Budget:
Settings → Experimental → GoCardless ├─ Enable GoCardless ├─ Enter Secret ID ├─ Enter Secret Key ├─ Select country ├─ Choose bank from list └─ Authorize access via bank login
Supported Countries:
- United Kingdom, Germany, France, Spain, Italy
- Netherlands, Belgium, Ireland, Austria
- Nordic countries (Sweden, Denmark, Norway, Finland)
- And 30+ more European countries
Limitations:
- 90-day access token expiration (must re-authorize)
- Some banks limit API access frequency
- Free tier has transaction limits
Option 3: Manual Import
For maximum privacy or unsupported banks:
Weekly Manual Import Workflow:
# Every Sunday evening:
1. Download transactions from bank (CSV or OFX)
2. Open Actual Budget
3. Import file to each account
4. Categorize new transactions (5-10 minutes)
5. Reconcile accounts
6. Review budget performance Benefits:
- No third-party service dependencies
- Works with any bank
- Complete privacy (no API access)
- Free
Time Investment: 15-30 minutes weekly
Data Import from Other Tools
Migrating from YNAB
Actual Budget can directly import YNAB4 budget files:
Export from YNAB:
YNAB4 (Desktop):
1. File → Export Budget Data
2. Save .ynab4 file
YNAB Web (nYNAB):
1. Account → Export Budget Data
2. Download .zip file
3. Extract to get .ynab4 format Import to Actual:
1. File → Import File
2. Select YNAB4 file
3. Review import summary:
├─ Accounts matched
├─ Categories mapped
├─ Transactions imported
└─ Budget amounts transferred
4. Click "Import"
5. Verify data accuracy What Gets Imported:
- All accounts with balances
- Categories and category groups
- Transaction history
- Budget amounts
- Payee names
What Doesn’t Import:
- Goals (must recreate manually)
- Reports/analytics
- Scheduled transactions (create new)
Migrating from Mint
Mint allows CSV export of transactions:
Export from Mint:
1. Transactions tab
2. Filter date range (max 1 year at a time)
3. Click "Export" → "Export all X transactions"
4. Save CSV file
5. Repeat for each year if needed Import to Actual:
1. Create accounts matching Mint accounts
2. For each account:
├─ Import CSV file
├─ Map columns (Date, Description, Amount, Category)
├─ Review transactions
└─ Categorize according to your budget
3. Set starting balances to match current bank accounts
4. Begin budgeting current month Post-Migration Tasks:
- Create budget categories matching your Mint categories
- Set up recurring transactions for bills
- Establish budget amounts based on Mint spending history
- Connect bank sync if desired
Migrating from Spreadsheets
If you track finances in Excel/Google Sheets:
Data Preparation:
Date,Account,Payee,Category,Inflow,Outflow
2025-01-01,Checking,Opening Balance,Starting Balance,2500.00,
2025-01-02,Checking,Grocery Store,Food,,125.43
2025-01-03,Checking,Employer,Income,3500.00, Import Process:
- Export spreadsheet to CSV
- Create accounts in Actual Budget
- Import transactions to corresponding accounts
- Verify balances match your records
- Build budget based on historical spending
Security Best Practices for Financial Data
Encryption at Rest
Enable Full Disk Encryption:
# For Ubuntu/Debian with LUKS
sudo cryptsetup luksFormat /dev/sdX
sudo cryptsetup luksOpen /dev/sdX actual-data
sudo mkfs.ext4 /dev/mapper/actual-data
# Mount encrypted volume
sudo mount /dev/mapper/actual-data /mnt/actual-data
# Update docker-compose.yml volume path
volumes:
- /mnt/actual-data:/data File-Level Encryption Alternative:
# Create encrypted container with VeraCrypt or LUKS
# Mount before starting Docker container
# Unmount when not in use Network Security
Enable HTTPS with Reverse Proxy:
Using Caddy (simplest):
budget.yourdomain.com {
reverse_proxy localhost:5006
tls {
dns cloudflare YOUR_CLOUDFLARE_API_TOKEN
}
} Using Nginx:
server {
listen 443 ssl http2;
server_name budget.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/budget.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/budget.yourdomain.com/privkey.pem;
location / {
proxy_pass http://localhost: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;
}
} Access Control
Option 1: Server Password
Set in docker-compose.yml:
environment:
ACTUAL_PASSWORD: 'your-complex-password-here-min-16-chars' Option 2: Reverse Proxy Authentication
Using Traefik with BasicAuth:
# Generate password hash
echo $(htpasswd -nb username password) | sed -e s/\$/\$\$/g
# Add to docker-compose.yml labels:
- "traefik.http.middlewares.actual-auth.basicauth.users=username:$$apr1$$hash$$here"
- "traefik.http.routers.actual.middlewares=actual-auth" Option 3: VPN-Only Access
Restrict access to VPN network:
services:
actual-server:
ports:
- '10.8.0.1:5006:5006' # Bind to VPN IP only Backup Encryption
Encrypt Backups Before Cloud Storage:
#!/bin/bash
# backup-actual-encrypted.sh
BACKUP_DIR="/home/user/actual-budget/data"
BACKUP_DEST="/backups/actual"
DATE=$(date +%Y%m%d-%H%M%S)
# Create encrypted backup
tar czf - "$BACKUP_DIR" | gpg --symmetric --cipher-algo AES256 --output "$BACKUP_DEST/actual-backup-$DATE.tar.gz.gpg"
# Keep only last 30 days
find "$BACKUP_DEST" -name "actual-backup-*.tar.gz.gpg" -mtime +30 -delete
echo "Encrypted backup created: actual-backup-$DATE.tar.gz.gpg" Restore Process:
# Decrypt and extract
gpg --decrypt actual-backup-20250115-120000.tar.gz.gpg | tar xzf - Audit Logging
Monitor access to your budget:
# Enable Docker logging
docker compose logs -f actual-server | grep -i "GET|POST" >> /var/log/actual-access.log
# Review periodically for unauthorized access
grep "POST /api/login" /var/log/actual-access.log Backup Procedures
Automated Daily Backups
Backup Script:
#!/bin/bash
# /usr/local/bin/backup-actual.sh
ACTUAL_DATA="/home/user/actual-budget/data"
BACKUP_DIR="/backups/actual"
DATE=$(date +%Y%m%d)
RETENTION_DAYS=30
# Create backup
mkdir -p "$BACKUP_DIR"
tar czf "$BACKUP_DIR/actual-budget-$DATE.tar.gz" "$ACTUAL_DATA"
# Verify backup
if [ $? -eq 0 ]; then
echo "Backup successful: actual-budget-$DATE.tar.gz"
# Calculate size
SIZE=$(du -h "$BACKUP_DIR/actual-budget-$DATE.tar.gz" | cut -f1)
echo "Backup size: $SIZE"
else
echo "ERROR: Backup failed!"
exit 1
fi
# Remove old backups
find "$BACKUP_DIR" -name "actual-budget-*.tar.gz" -mtime +$RETENTION_DAYS -delete
# Optional: Upload to remote location
# rclone copy "$BACKUP_DIR/actual-budget-$DATE.tar.gz" remote:backups/actual/ Schedule with Cron:
# Edit crontab
crontab -e
# Add daily backup at 2 AM
0 2 * * * /usr/local/bin/backup-actual.sh >> /var/log/actual-backup.log 2>&1 Backup to Cloud Storage
Using Rclone (supports 40+ cloud providers):
# Configure rclone (one-time setup)
rclone config
# Create remote named "backups" (Google Drive, Dropbox, etc.)
# Update backup script
rclone copy "$BACKUP_DIR/actual-budget-$DATE.tar.gz" backups:actual-budget/ Testing Backup Restoration
Quarterly Restore Test:
#!/bin/bash
# test-restore.sh
TEST_DIR="/tmp/actual-restore-test"
LATEST_BACKUP=$(ls -t /backups/actual/actual-budget-*.tar.gz | head -1)
# Create test directory
mkdir -p "$TEST_DIR"
# Extract backup
tar xzf "$LATEST_BACKUP" -C "$TEST_DIR"
# Verify critical files exist
if [ -f "$TEST_DIR/data/db.sqlite" ]; then
echo "✓ Database file exists"
else
echo "✗ Database file missing!"
exit 1
fi
if [ -f "$TEST_DIR/data/files/default-budget.sqlite" ]; then
echo "✓ Budget file exists"
else
echo "✗ Budget file missing!"
exit 1
fi
# Cleanup
rm -rf "$TEST_DIR"
echo "Restore test successful!" Troubleshooting
Container Won’t Start
Check logs:
docker compose logs actual-server
# Common issues:
# - Port 5006 already in use
# - Permission denied on /data volume
# - Invalid environment variables Solutions:
# Port conflict - change port
ports:
- '5007:5006' # Use different external port
# Permission fix
sudo chown -R $USER:$USER ./data
chmod 755 ./data
# Reset container
docker compose down
docker compose up -d Budget File Won’t Sync Across Devices
Verify sync server:
# Check server logs for sync errors
docker compose logs -f actual-server | grep sync
# Test sync endpoint
curl http://YOUR-SERVER-IP:5006/health
# Should return: {"status":"ok"} Client-side fixes:
- Settings → Advanced → Reset Sync
- Close and reopen budget file
- Verify correct server URL
- Check network connectivity
Bank Sync Not Working
SimpleFIN troubleshooting:
# Verify access token is valid
# Settings → SimpleFIN → Test Connection
# Check for expired credentials
# SimpleFIN dashboard → Connections → Refresh token
# Review sync logs
docker compose logs actual-server | grep simplefin GoCardless troubleshooting:
# Access tokens expire after 90 days
# Settings → GoCardless → Reconnect Account
# Some banks require re-authorization more frequently
# Check GoCardless dashboard for connection status Import Fails with CSV Error
Common CSV issues:
# Date format - use YYYY-MM-DD
# Wrong: 01/15/2025
# Right: 2025-01-15
# Currency symbols - remove all
# Wrong: -$125.43
# Right: -125.43
# Encoding - use UTF-8
# Convert with: iconv -f ISO-8859-1 -t UTF-8 input.csv > output.csv
# Line endings - use Unix (LF)
# Convert with: dos2unix transactions.csv Slow Performance with Large Budget
Optimization steps:
# 1. Archive old transactions
# Settings → Advanced → Archive transactions older than 2 years
# 2. Increase Docker memory
docker compose down
# Edit docker-compose.yml:
deploy:
resources:
limits:
memory: 1G
docker compose up -d
# 3. Clear browser cache
# Browser settings → Clear browsing data → Cached files
# 4. Compact database
# Settings → Advanced → Compact Database Conclusion
Actual Budget provides a powerful, privacy-respecting alternative to commercial budgeting services. By self-hosting, you maintain complete control over your financial data while accessing enterprise-grade budgeting features at zero ongoing cost.
What you’ve accomplished:
- Deployed a secure, self-hosted budgeting platform
- Configured automatic bank synchronization (optional)
- Implemented robust backup and security measures
- Established a zero-based budgeting workflow
Next steps:
- Spend first month learning your spending patterns
- Adjust budget categories based on actual expenses
- Set up financial goals and tracking
- Explore reporting features for spending insights
- Consider adding Actual Budget mobile access via VPN
Additional resources:
- Official documentation:
https://actualbudget.org/docs/ - Community Discord: 3,000+ active users
- GitHub repository:
https://github.com/actualbudget/actual - Reddit: r/ActualBudget for tips and workflows
With Actual Budget running in your homelab, you’ve taken a significant step toward financial awareness and privacy. The zero-based budgeting method, combined with self-hosted control, provides a powerful foundation for achieving your financial goals.