I installed Akaunting on Ubuntu — the artisan installer hung until php-xml was loaded

I installed Akaunting on Ubuntu — the artisan installer hung until php-xml was loaded

I wanted invoices and double-entry books on my VPS, not another SaaS tab. Akaunting is Laravel plus a module App Store. On Ubuntu 24.04 the artisan installer hung until the PHP extensions were actually loaded, then a 500 showed up until storage belonged to www-data.

 Akaunting accounting dashboard

Caption: Akaunting self-hosted Laravel accounting and invoicing dashboard.

Introduction

I wanted invoices, bills, and a proper ledger on a box I control. Spreadsheets and a rotating set of SaaS tabs were eating evenings. Akaunting is a Laravel accounting app with a Vue/Tailwind UI, a module App Store, and a CLI installer. Latest line I used for this write-up is v3.1.21.

I put it on Ubuntu 24.04 LTS with Nginx, MariaDB, PHP 8.3, Composer, and Node. Git clone into /var/www/akaunting, then php artisan install. That is the path I would run again. The tarball from akaunting.com and community Docker images exist if you do not want Git.

On a fresh Ubuntu 24.04 box this install is famous for hanging when a PHP extension is missing, then serving a 500 until storage and bootstrap/cache are writable by the FPM user. I treat those as lab facts, not a war story about a client.

Where it broke

1. php artisan install hung — missing PHP extensions

On a fresh Ubuntu 24.04 box this is the failure this install is famous for. The CLI installer sits there, or you get a blank page, because xml, intl, gd, or zip never made it into the FPM build.

Shape of the problem:

PHP Extension xml is required.
# or the installer never finishes and laravel.log is quiet

Fix from this post: install the modules this app actually lists, then restart FPM.

sudo apt install -y php8.3-bcmath php8.3-curl php8.3-gd php8.3-intl \
    php8.3-mbstring php8.3-mysql php8.3-xml php8.3-zip php8.3-bz2
sudo systemctl restart php8.3-fpm

2. 500 / blank page after install — missing APP_KEY or storage ownership

Akaunting is Laravel. If APP_KEY is empty or www-data cannot write logs, you get a 500.

cd /var/www/akaunting
sudo -u www-data php artisan key:generate
sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cache

Prerequisites

Akaunting v3.1.x wants a normal Laravel stack. I used:

Hardware (1–20 users, hundreds of transactions):

  • CPU: 2 cores (4+ if you generate a lot of PDFs)
  • RAM: 2 GB (4 GB is more comfortable)
  • Storage: 10 GB+ free, SSD if you can

OS: Ubuntu 24.04 LTS (Debian 12 is fine). Windows/WSL2 or Docker work; I stay on Linux for production.

Software:

  • PHP 8.3+ (8.1 minimum) with bcmath, curl, gd, intl, mbstring, mysql, openssl, xml, zip, bz2
  • Composer 2.8+
  • Node.js 20+ and npm (frontend assets)
  • Nginx (or Apache)
  • MariaDB 10.6+ / MySQL 8.0+ (PostgreSQL or SQLite are supported)
  • Git

Accounts / extras:

  • Domain or subdomain (accounting.example.com) and Let’s Encrypt
  • SSH with sudo
  • SMTP if you want invoice mail; payment gateways via apps

Checklist:

  • Fresh Ubuntu 24.04 with sudo
  • Backups if this is not a throwaway VPS
  • DNS pointing at the server
  • Firewall allowing 80/443

Installation Guide

I used the official Git clone. Updates are git pull plus Composer/npm. The tarball from akaunting.com is the other official path.

Step 1: System preparation (Ubuntu 24.04)

sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx mariadb-server php8.3-fpm php8.3-cli \
    php8.3-bcmath php8.3-curl php8.3-gd php8.3-intl \
    php8.3-mbstring php8.3-mysql php8.3-xml php8.3-zip \
    php8.3-bz2 git unzip curl nodejs npm build-essential

Install Composer:

curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer

Secure MariaDB:

sudo mysql_secure_installation

Create database and user (replace strong_password):

sudo mysql -u root -p
CREATE DATABASE akaunting CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'akaunting_user'@'localhost' IDENTIFIED BY 'strong_password';
GRANT ALL PRIVILEGES ON akaunting.* TO 'akaunting_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Step 2: Download and install Akaunting

sudo mkdir -p /var/www/akaunting
cd /var/www/akaunting
sudo git clone https://github.com/akaunting/akaunting.git .
sudo chown -R www-data:www-data /var/www/akaunting

Install dependencies:

sudo -u www-data composer install --no-dev --optimize-autoloader
sudo -u www-data npm install
sudo -u www-data npm run dev

If npm run dev fails, I install build-essential (already in the apt line above) and retry. That is the other common stall on a minimal Ubuntu image.

Step 3: Configure Nginx

sudo nano /etc/nginx/sites-available/akaunting

Production config (replace accounting.example.com):

server {
    listen 80;
    server_name accounting.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name accounting.example.com;

    root /var/www/akaunting/public;
    index index.php index.html;

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

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }
}

Enable and restart:

sudo ln -s /etc/nginx/sites-available/akaunting /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl restart nginx php8.3-fpm

Document root must be public/. If CSS and images 404, that is almost always Nginx pointing at the project root instead of public.

Step 4: Run the installer

cd /var/www/akaunting
sudo -u www-data php artisan install \
  --db-name="akaunting" \
  --db-username="akaunting_user" \
  --db-password="strong_password" \
  --admin-email="admin@accounting.example.com" \
  --admin-password="your_strong_admin_password"

Optional sample data:

sudo -u www-data php artisan sample-data:seed

Alternative: tarball
Download the latest package from https://akaunting.com/download, unzip to /var/www/akaunting, then use the web installer at https://accounting.example.com.

Alternative: Docker
Community images exist on the GitHub repo. I still want the same .env and permission habits if I go that route.

Configuration

Akaunting copies a Laravel .env during install. I edit production values with:

sudo -u www-data nano .env

Sample production configuration:

APP_NAME="Akaunting"
APP_ENV=production
APP_DEBUG=false
APP_URL=https://accounting.example.com

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=akaunting
DB_USERNAME=akaunting_user
DB_PASSWORD=strong_password

MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=your-smtp-user
MAIL_PASSWORD=your-smtp-pass
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@accounting.example.com
MAIL_FROM_NAME="Akaunting"

# Optional: queue for background jobs
QUEUE_CONNECTION=database

APP_KEY is generated by the installer / key:generate. I do not paste a placeholder key into production.

Clear caches:

sudo -u www-data php artisan config:cache
sudo -u www-data php artisan route:cache

Permissions (repeat this if logs stay empty and you still get 500s):

sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cache

Cron for scheduled tasks (recurring invoices, reports):

sudo -u www-data crontab -e

Add:

* * * * * cd /var/www/akaunting && php artisan schedule:run >> /dev/null 2>&1

If you set QUEUE_CONNECTION=database, you still need a worker (php artisan queue:work) or jobs sit in the table. Recurring invoices also need that cron line.

Packages Information

Akaunting is Laravel. The README will get you Composer and the installer. Operators still need to know what that stack implies:

  • Laravel.env, artisan, storage/ and bootstrap/cache ownership, config cache. The 500 after a “successful” install is almost always this layer, not “Akaunting is broken.”
  • Modules / App Store — extra apps (inventory, payroll, and so on) are modules, not a second CMS. After you install an app, keep storage writable and rerun caches if the UI looks half-built.
  • Vue + Tailwind assetsnpm install and npm run dev are part of the Git clone path. Skip them and the UI looks like 2009.
  • Queues (optional) — this post already sets QUEUE_CONNECTION=database. That is Laravel’s database queue, not Horizon. If you enable it, run a worker. I would not invent Redis/Horizon here unless you add them yourself.

I am not claiming Spatie or Horizon live in this app. I did not open composer.json on this rewrite and I will not invent packages.

Usage

  1. Open the app: https://accounting.example.com. Log in with the admin you created in php artisan install.
  2. Dashboard: cash flow, receivables, payables, profit/loss.

Akaunting Dashboard
Figure 1: Akaunting modern dashboard with cash flow, receivables, payables, and profit & loss summaries.

  1. First invoice: Sales → Invoices → New Invoice. Customer, line items, send.

Akaunting features
Figure 2: Akaunting invoices list and detail view with payment status and quick actions.

  1. Smoke test:
    • Purchases → Expenses
    • Banking reconciliation
    • Profit & loss / cash flow reports

Akaunting Invoices
Figure 3: Akaunting invoices interface showing overdue, open, and draft statuses with real-time summaries.

The UI is usable on a phone. Extra behaviour lives in the App Store and the REST API, not in a hidden Laravel package I would guess at.

A feature I would not have found from the homepage: companies and the App Store are first-class. Akaunting is not “one .env and you are done.” After php artisan install you still pick a company, currencies, and which modules to enable. Recurring invoices only fire if the cron line above is actually in www-data’s crontab — the dashboard will not warn you loudly. I would enable the database queue only after I have watched one invoice email leave with sync (or after I have a worker). php artisan sample-data:seed is useful on a lab box and dangerous on a company that already has live invoices.

Day two on the VPS

Akaunting holds invoices. After the first real invoice I would:

  • Confirm the www-data cron is present (crontab -l -u www-data) so recurring invoices are not a dashboard lie.
  • If QUEUE_CONNECTION=database, run a worker under Supervisor — same pattern as Bagisto, without Horizon unless I add it myself.
  • mysqldump akaunting and copy storage/. Attachments and logs are not only in MySQL.
  • APP_DEBUG=false, HTTPS, strong admin password. The App Store can wait until mail actually sends.
  • Keep npm run dev in the Git update path I already listed. A git pull without npm is how the UI looks half-upgraded.

Missing php8.3-xml / intl / gd is still the hang. I would rather reinstall those packages than stare at a silent artisan process.

Troubleshooting

I already walked the two walls above. The rest I keep next to the box:

Error Cause Fix
500 / blank page Missing APP_KEY or permissions php artisan key:generate + storage ownership
Database connection failed Wrong .env credentials Verify DB user/password and re-run installer
npm run dev fails Missing build tools Install build-essential and retry
Images/CSS not loading Wrong Nginx document root root /var/www/akaunting/public;
Emails not sending SMTP misconfigured Test .env mail settings and check logs
Installer hangs Missing PHP extensions Reinstall required PHP modules and restart PHP-FPM

Logs: storage/logs/laravel.log or journalctl -u nginx.

Conclusion

Akaunting is running on my Ubuntu 24.04 VPS behind Nginx: Git clone, Composer, npm, php artisan install, cron for the scheduler. Books stay on the server. Next I would add Redis only if the database queue gets slow, turn on a backup module or mysqldump, and keep SSL and 2FA in the apps that provide them.

Updates I would actually run:

git pull origin master
composer install --no-dev --optimize-autoloader
npm run dev
php artisan akaunting:upgrade

Official resources:

Did you hit the same wall?

I got stuck on php artisan install hanging until php8.3-xml (and the rest of the extension list) was actually loaded. Did you hit the same thing, or a different one — Composer memory, storage permissions, PHP extensions, a queue worker that never started? Tell me in the comments. I read them.

Need this done on your server?

I deploy and harden Laravel/CodeCanyon apps on cPanel or VPS, and offer monthly Server Watch retainers. Hire for deploy · Care plan

Share:

Get new posts in your inbox

No spam. One short email per new article — practical PHP, Laravel, devops, and AI-assisted workflows.

Comments

Powered by GitHub Discussions via Giscus. A free GitHub account is required.