Actual Budget behind Caddy

Verified · Caddy 2.8

Config + gotchas checked against the official Actual Budget docs and the issues people actually hit · 2026-06-07

A Caddy reverse-proxy setup for Actual Budget, with automatic HTTPS on budget.example.com. Copy it, swap the domain — the gotchas that trip people up are solved below.

New to reverse proxies? Start with the Caddy + Docker guide.

Caddyfile
# Caddyfile for Actual Budget
# Replace budget.example.com with your real domain. Caddy issues TLS automatically.

budget.example.com {
	encode zstd gzip
	reverse_proxy actual-budget:5006
}

Replace budget.example.com with your domain. Your DNS A record must already point at the server.

Gotchas

What trips people up

Blank page or a 'SharedArrayBuffer is not defined' console error.

Actual needs a Secure Context (HTTPS) for its in-browser SQLite engine — over plain HTTP it won't start. Reach it only through the HTTPS proxy; Caddy issues the cert automatically. Don't expose port 5006 to the host.

Sync stalls or the client can't reach the server behind the proxy.

Actual syncs over standard HTTP(S), which Caddy's default reverse_proxy handles. If it hangs, confirm the app and Caddy share the `proxy` network and you're opening the HTTPS domain, not the container IP.

Deploy

How to deploy

  1. Create the shared proxy network once: `docker network create proxy`.
  2. Start the stack: `docker compose up -d`.
  3. Point budget.example.com at the server, open the HTTPS URL (not the IP), and set your server password on first visit.

Runnable

Full runnable stack

Actual Budget + Caddy + the shared network, in one compose file — so it runs on the first try, not after you stitch the proxy in yourself. Save the Caddyfile above next to it.

docker-compose.yml (app + Caddy)
# docker-compose.yml — Actual Budget + Caddy, ready to run.
# Pin caddy to a digest for production: caddy:2.8-alpine@sha256:<digest>
# 1. Save the Caddyfile above as ./Caddyfile
# 2. docker network create proxy
# 3. docker compose up -d
services:
  actual-budget:
    image: docker.io/actualbudget/actual-server:26.5.2@sha256:1aeeb3985db55556e716dec25e08f6ce09308c2571b65cddbc6746ee6d5e0d45
    restart: unless-stopped
    environment:
      TZ: ${TZ}
      ACTUAL_PORT: "5006"
    volumes:
      - actual-data:/data
    networks:
      - default
      - proxy
  caddy:
    image: caddy:2.8-alpine
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy-data:/data
      - caddy-config:/config
    networks:
      - proxy
volumes:
  actual-data: {}
  caddy-data: {}
  caddy-config: {}
networks:
  proxy:
    external: true

Full Actual Budget stack (compose + env + docs)