July 14, 2025

Self-Hosting Hledger Reports Without a Server

I’ve been using hledger for personal accounting for a while now, and one thing that always bothered me was accessing my financial reports when I wasn’t at my main computer. I wanted to check my balance or look at my spending patterns from anywhere, but I didn’t want to set up and maintain a server just for this.

The hledger-web command exists, but it requires keeping a server running somewhere. That felt like overkill for what I needed — just readonly access to my reports from time to time. What I really wanted was the convenience of self-hosting without the overhead of actually running a server.

The “Self-Hosting Without a Server” Paradox

I had my journal file in a Git repository, and I realized that since I only needed read access to my reports, I didn’t actually need a fancy server. I just needed a way to generate static HTML files from my journal and serve them somewhere.

This is where the “self-hosting without a server” concept clicked for me. I could have complete control over my data and hosting setup, but without the maintenance burden of keeping a server running 24/7.

The key insight came from the subreddit r/plaintextaccounting’s post where someone shared their approach using a bash script and Cloudflare Workers. They were generating static HTML reports from their hledger journal and serving them as a static website. This seemed perfect for my use case.

My Initial Approach

I started by adapting the bash script approach. The core idea is brilliant: since hledger can output HTML directly, you can generate all your reports as static files and serve them from anywhere that hosts static websites.

Here’s what the bash script does:

# Generate various reports as HTML
bse --base-url="" -o dist/real-balances.html
meb --base-url="" -o dist/real-monthly-ending-balances.html
ism --base-url="" -o dist/real-earnings-and-expenses-monthly.html

# Generate register views for each account
hl accounts | while read -r acct; do
  safe_name=$(echo "$acct" | tr ':' '_' | tr -cd '[:alnum:]_.-')
  hl reg "$acct" -o "dist/registers/${safe_name}.html"
done

The script also does something clever with the register links. Hledger’s HTML output includes links to register views (intended for hledger-web), but the script transforms these into links to the generated static register pages.

Moving to GitHub Actions

While the bash script worked locally, I wanted something more automated. I didn’t want to remember to run the build script and deploy manually every time I updated my journal. Since my journal was already in a GitHub repository, GitHub Actions seemed like the natural choice.

I created a workflow that:

  1. Installs hledger on the runner
  2. Generates HTML reports from my journal
  3. Commits the generated files back to the repository
  4. Deploys everything to Cloudflare Pages
- name: Install hledger
  run: |
    curl -fL -o hledger.tar.gz https://github.com/simonmichael/hledger/releases/download/1.43.2/hledger-linux-x64.tar.gz
    tar -xzvf hledger.tar.gz
    sudo mv hledger hledger-ui hledger-web /usr/local/bin/    

- name: Generate HTML reports
  run: |
    hledger balance -f ledger/journal.ledger -O html > lakaz-web/balance.html
    hledger balancesheet -f ledger/journal.ledger -O html > lakaz-web/balancesheet.html
    hledger incomestatement -f ledger/journal.ledger -O html > lakaz-web/incomestatement.html
    # ... more reports    

The workflow runs on every push to main, and also on a daily schedule at 6 AM, so my reports stay current even if I forget to push changes.

Cloudflare Zero Trust for Security

Since these are my personal financial reports, I obviously didn’t want them publicly accessible. I initially looked into Cloudflare Workers with custom authentication, but then discovered Cloudflare Zero Trust, which has a generous free plan.

Zero Trust lets you put an authentication portal in front of any website hosted on Cloudflare. You can configure it to use various identity providers (Google, GitHub, etc.), and it handles all the authentication logic for you. No custom code needed on your site.

Setting it up was straightforward:

  1. Create a Cloudflare Pages project
  2. Connect it to your GitHub repository
  3. Set up Zero Trust to protect the site
  4. Configure which email addresses can access it

The Current Setup: True Self-Hosting Without Server Maintenance

Now I have a completely automated system that gives me all the benefits of self-hosting without any of the traditional server headaches:

  • I update my journal locally and push to GitHub
  • GitHub Actions automatically generates new HTML reports
  • Cloudflare Pages deploys the updated site
  • I can access my reports from anywhere, protected by Zero Trust

The whole stack is free:

  • GitHub Actions
  • Cloudflare Pages
  • Cloudflare Zero Trust (free for up to 50 users)

It’s self-hosted in the truest sense — I control my data, my deployment pipeline, and my access controls — but without the operational overhead of managing infrastructure.

The beauty of this approach is that it works with your existing hledger workflow — you don’t need to change how you enter transactions or structure your journal. You just get the bonus of web access to your reports with minimal ongoing maintenance.

Powered by Hugo & Kiss.