Self-hosted invoicing software compared
Invoice Ninja, InvoiceShelf, InvoicePlane, Kimai and ERPNext on one VPS: recurring billing, tax, client portal, payment gateways and licence terms.
Which self-hosted invoicing software should you pick?
Self-hosted invoicing software comes down to five realistic choices on one VPS, and they are not close substitutes. Invoice Ninja is the most finished product: recurring invoices, a client portal, payment gateways and e-invoice formats all work on the day you install it. InvoiceShelf is the lighter fully open source option. InvoicePlane is a plain PHP application with nothing to orchestrate. Kimai bills tracked hours instead of line items. ERPNext is a full accounting system that also prints invoices.
Two problems sink a self-hosted invoicing setup far more often than a missing feature. The first is email. An invoice sitting in a spam folder is an unpaid invoice, and nothing tells you it happened. The second is backups. This is the one self-hosted database whose loss becomes a tax problem rather than an inconvenience.
Read the licence before you read the feature list. Three of these five are copyleft, one is permissive, and one is source-available with real limits on what you may do with it.
The five options, and who each one is for
Invoice Ninja
A Laravel application, currently on the v5 line. Self-hosting is free and unlocked: recurring invoices, quotes, projects, time tracking, expenses, a client portal and payment gateways including Stripe and PayPal. The project calls itself "a source-available invoice, quote, project and time-tracking app built with Laravel". The one paid piece is branding. In the project's own words: "We offer a $40 per year white-label license to remove the Invoice Ninja branding from client-facing parts of the app". That price is current as of August 2026.
Pick it if you want the most complete product and you accept running the largest stack.
InvoiceShelf
A fork of Crater, under the GNU Affero General Public License version 3 (AGPL-3.0). It covers invoices, estimates, recurring billing, expenses, taxes, multi-currency, a customer portal and multi-company. Payments and white-labelling live in separate official module repositories, both AGPL-3.0 and both free. Card payment runs through Stripe checkout.
Pick it if you want a small, fully open source application and you do not need thirty gateways.
InvoicePlane
A PHP application on CodeIgniter, released under the MIT licence, and the only option here that installs by unzipping files into a web root. No Docker and no Node build step. PDFs come from mPDF, so templates are HTML and CSS. Clients get a guest link to view and pay an invoice.
The catch is payments. The documentation states plainly that InvoicePlane 1.6 supports only Stripe by default, and that other providers were removed in that version. The published requirements for the 1.6 line are PHP 8.0 to 8.1 with MySQL 5.5 or the matching MariaDB. Read the notes on whichever release you download, because the 1.7 line is still moving.
Pick it if you are on shared hosting or a 512 MB VPS.
Kimai
An AGPL-3.0 time tracker that generates invoices from timesheets. It is the right answer for anyone who bills hours. Invoice templates are uploaded as DOCX, ODS or XLSX files, which the documentation confirms are the only accepted formats, and rendered out to PDF or HTML. You lay out your invoice in a word processor instead of writing CSS, which most people find easier.
Kimai has no client portal and no payment gateway, so you email the PDF. Structured e-invoicing is a paid store plugin called E-Invoice Plus, listed at 99 EUR excluding VAT per year for a self-hosted install as of August 2026, covering the EN16931, XRechnung, ZUGFeRD, Factur-X and Peppol profiles.
Pick it if the invoice is the output of tracked time.
ERPNext
GPL-3.0, and a different category of software. A sales invoice is one document type inside a full accounting system with a chart of accounts, journal entries, stock and payroll. It is the right answer once invoicing has stopped being the whole job. For one freelancer with eight clients it is far too much software. Standing it up is its own project, covered in running ERPNext on a VPS.
Recurring invoices depend on a scheduler you cannot see
Invoice Ninja, InvoiceShelf, InvoicePlane and ERPNext all generate recurring invoices. Kimai has no recurring invoice feature in core, because an invoice there is built from the timesheet entries for a period.
The failure mode is the same in every Laravel-based one, and it is quiet. Recurring invoices are created by a scheduled task, php artisan schedule:run, which something outside the application must call every minute. If nothing calls it, the recurring invoice list still shows a next send date, and that date simply passes. No invoice appears. No error is logged, because no code ran.
So after you create your first recurring invoice, set its next send date a few minutes ahead and wait for it. If nothing arrives, the scheduler is not running. In a Docker install that usually means the cron or scheduler service in the compose file never started, or it exited.
docker compose ps
docker compose logs --tail 50Every service in the file should report running. A service sitting in exited state is the one whose logs you need to read.
Multi-currency and tax: what to check before you commit
Feature lists say "multi-currency" and mean very different things by it. Run these four checks on a demo instance, using real numbers from your own business, because a mistake here is found by your accountant rather than by you.
- Does the invoice store the exchange rate that applied on its issue date? It must. If the application recalculates totals from a live rate, last quarter's reports change every time you open them, and your books stop matching what you already filed.
- Is tax applied per line item, per invoice, or both? A single invoice-level rate is fine right up until one line is zero-rated.
- Can you enter prices tax inclusive as well as tax exclusive? Converting after the fact introduces rounding differences of a cent per line, and those cents accumulate across a year.
- What does the document show for a client in another country who owes no VAT? You need the exemption note printed on the invoice, not just a zero in the tax column.
Invoice Ninja and ERPNext handle all four. InvoiceShelf lists taxes and multi-currency among its features. InvoicePlane and Kimai are simpler, so test them against your most awkward invoice rather than your easiest one.
Client portal and payment gateways
A client portal is a page where a client sees their invoices, downloads the PDF and pays. It matters more than it sounds, because chasing payment by email costs more of your time than producing the invoice did.
Invoice Ninja is strongest here, with a full portal, many gateways, stored payment methods and auto-billing on recurring invoices. InvoiceShelf has a customer portal and adds Stripe checkout through its payments module. InvoicePlane gives each invoice a guest link and supports Stripe. Kimai has neither, so the PDF goes out as an attachment.
One point gets confused constantly. Self-hosting the invoicing application does not mean holding card numbers. With Stripe checkout the client is redirected to Stripe, types the card in there, and your VPS never sees the number. Your PCI (payment card industry) scope stays small precisely because you did not build the payment form. If you later need more than one processor, or want to route payments by country, that belongs in a separate layer such as self-hosted payment orchestration with Hyperswitch.
PDF quality, and the template you will end up editing
You will edit the template. Every default here puts your address in the wrong place for your country.
Invoice Ninja renders HTML and CSS templates to PDF using a headless Chromium bundled into the image, which is why the container is large. Budget memory for it: a headless browser is the heaviest process in the stack, so a 1 GB VPS is tight. InvoicePlane uses mPDF, which is pure PHP and light, at the cost of ignoring some modern CSS. Kimai takes the opposite approach and has you upload a DOCX or ODS file, so the layout is done in LibreOffice or Word by someone who has never written a stylesheet.
If a designed invoice matters to you and CSS does not, Kimai's approach wins. If you want the template in version control and diffable, HTML wins.
Does the licence let you use it commercially?
All five let you invoice your own clients. The differences appear when you do anything beyond that.
Invoice Ninja is under the Elastic License 2.0. Two clauses matter.
You may not provide the software to third parties as a hosted or managed service, where the service provides users with access to any substantial set of the features or functionality of the software.
You may not move, change, disable, or circumvent the license key functionality in the software.
So running it for your own business is fine, and running instances for clients as a product is not. The second clause is why removing the branding is a $40 licence rather than a patch to the source.
InvoiceShelf, Kimai and Crater are AGPL-3.0. Commercial use is free and there is no tier above you. The obligation is section 13: if you modify the code and let other people use your modified version over a network, you must offer them the source of your changes. An unmodified instance is already covered by upstream's public source, so most self-hosters never touch this.
InvoicePlane is MIT, the lightest obligation in the group, although its README notes that the name and logo stay with the project.
ERPNext is GPL-3.0. Modifications you distribute must be shared, and running it privately carries no obligation.
That is a description of the licence text and not legal advice.
Is the project still maintained? Check the commit log
Crater was the popular open source invoicing application for several years, and plenty of guides still recommend it. Its default branch has had no commit for 741 days.
The data behind this chart
[
{
"label": "ERPNext",
"commit_gap": 0
},
{
"label": "Invoice Ninja",
"commit_gap": 1
},
{
"label": "Kimai",
"commit_gap": 1
},
{
"label": "InvoicePlane",
"commit_gap": 3
},
{
"label": "InvoiceShelf",
"commit_gap": 4
},
{
"label": "Crater",
"commit_gap": 741
}
]Of the 6 projects in that block, five had a commit within the past week. InvoiceShelf is the fork that carried Crater's work forward, under the same AGPL-3.0 licence and with the same data model, so moving across is a data migration rather than a rewrite. Install InvoiceShelf, not Crater.
Installing each one on a VPS
Everything except InvoicePlane runs most easily under Docker Compose. If that is new to you, start with a first Docker Compose stack on a VPS and come back here.
Invoice Ninja ships its own compose repository.
git clone https://github.com/invoiceninja/dockerfiles.git
cd dockerfiles
docker run --rm -it invoiceninja/invoiceninja php artisan key:generate --showCopy that key into the env file in the repository, along with your real APP_URL. Then set the ownership the image expects and start it.
chmod 755 docker/app/public
sudo chown -R 1500:1500 docker/app
docker compose up -dThe 1500:1500 ownership matches the user inside the image. Skip it and the application cannot write its cache or its generated PDFs. In the setup wizard, give db as the database host, because that is the service name on the compose network. localhost there points at the application container itself.
InvoiceShelf publishes ready compose files, one per database engine.
curl -fLO https://raw.githubusercontent.com/InvoiceShelf/docker/master/docker-compose.mysql.yml
mv docker-compose.mysql.yml docker-compose.ymlOpen that file before you start it. Every password in it is a placeholder and the file says so in its own comments. APP_URL, SESSION_DOMAIN and SANCTUM_STATEFUL_DOMAINS all default to localhost, and all three need your real hostname set before the first login. Then bring it up.
docker compose up -dIt publishes port 8090 on the host and serves 8080 inside the container. Put a reverse proxy with TLS (transport layer security) in front of it before you send a single client to it.
Kimai production compose file, from the project documentation
services:
sqldb:
image: mysql:8.3
volumes:
- mysql:/var/lib/mysql
environment:
- MYSQL_DATABASE=kimai
- MYSQL_USER=kimaiuser
- MYSQL_PASSWORD=kimaipassword
- MYSQL_ROOT_PASSWORD=changemeplease
command: --default-storage-engine innodb
restart: unless-stopped
healthcheck:
test: mysqladmin -uroot -p$$MYSQL_ROOT_PASSWORD ping -h 127.0.0.1 --silent
interval: 10s
timeout: 5s
retries: 12
start_period: 30s
kimai:
image: kimai/kimai2:stable
depends_on:
sqldb:
condition: service_healthy
restart: true
volumes:
- data:/opt/kimai/var/data
- plugins:/opt/kimai/var/plugins
ports:
- 8001:8001
environment:
- APP_SECRET=your-fantastic-long-random-and-ultra-secure-secret
- TRUSTED_HOSTS=kimai.example.com
- ADMINMAIL=admin@example.com
- ADMINPASS=changemeplease
- DATABASE_URL=mysql://kimaiuser:kimaipassword@sqldb/kimai?charset=utf8mb4&serverVersion=8.3.0
restart: unless-stopped
volumes:
data:
mysql:
plugins:Set APP_SECRET to a long random string of your own and TRUSTED_HOSTS to the domain you will actually use. ADMINMAIL and ADMINPASS create the first administrator on first boot, so change that password in the interface afterwards and take it back out of the file.
InvoicePlane has no container. Create an empty database first.
sudo mysql -e "CREATE DATABASE invoiceplane CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"Download the archive from the project's releases page, extract it into your web root, copy ipconfig.php.example to ipconfig.php, and set your URL inside it following the comments in that file. Then open https://your-domain.example/index.php/setup and work through the installer.
Why your invoice lands in the spam folder
This is the failure that costs you money, and the application will happily report the invoice as sent.
Do not send invoice mail from the VPS's own IP address. A new VPS IP has no sending history, and large receivers treat a first message from an unknown address in a hosting range as suspicious by default. Send through an SMTP (simple mail transfer protocol) relay on a domain you have authenticated. Doing that end to end is its own guide, and sending mail reliably from a self-hosted app covers the relay, the DNS records and the testing.
The From address is where most invoicing setups go wrong. It must be on a domain you control, and one your relay signs with DKIM (domainkeys identified mail). Putting the client's own address in From feels polite and breaks delivery, because the receiving server checks that the From domain matches the domain that authenticated the message. Gmail rejects the mismatch outright:
550 5.7.1 Unauthenticated email from example.com is not accepted due to domain's DMARC policyKeep your own address in From and put the client in Reply-To. Check your published records before you send anything real.
dig +short TXT example.com
dig +short TXT _dmarc.example.com
dig +short TXT selector1._domainkey.example.comThe first should return an SPF (sender policy framework) record naming your relay. The second should return a DMARC (domain-based message authentication, reporting and conformance) policy. The third depends on the selector your relay gave you, and an empty answer means the DKIM record is missing or published under the wrong name, so every message you send will fail its signature check.
Then send a real invoice to an account you own at a large provider and read the raw headers. dkim=pass and spf=pass in the Authentication-Results header is the only proof that counts. The application's own "sent" flag means only that it handed the message to the relay.
Backups: the one database you cannot lose
Losing a media server means downloading files again. Losing your invoicing database means you cannot show what you billed, what you were paid, or what you still owe. Treat it accordingly.
Back up four things. The database. The storage volume, which holds logos, generated PDFs and expense receipts. The application's environment file. And the rendered PDFs kept as ordinary files, because a PDF opens in ten years without the application that produced it.
The environment file matters more than people expect. Laravel encrypts sensitive stored values, including payment gateway credentials, using the APP_KEY in that file. Restore the database next to a freshly generated key and those values do not decrypt. Your invoices are all present and your gateway settings are unreadable.
A dump from the InvoiceShelf stack looks like this. Its database service runs mariadb:10, so the tool inside the container is mariadb-dump.
docker compose exec -T database mariadb-dump \
--user=root --password=YOUR_ROOT_PASSWORD \
--single-transaction --databases invoiceshelf > invoiceshelf.sql--single-transaction takes a consistent snapshot of the InnoDB tables without locking them, so the application keeps serving while the dump runs. -T stops Docker allocating a terminal, which would otherwise corrupt the redirected output and give you a dump file that will not import.
Send that off the server on a schedule with encrypted restic backups to off-site storage, and set the retention long enough to cover the period your accountant names.
Then restore it. Bring the dump up in a throwaway container, log in, and open last month's invoice. Until you have done that once, you do not know whether the backup works.
What your accountant will ask for
Do not take tax advice from a tutorial. What follows is what this software can produce, so you can take the list to the person who advises you.
Exportable data. You should be able to export your invoices and payments to CSV from the interface, without a database client and without opening a support ticket. A SQL dump is a backup, not an export, because your accountant cannot open one.
Immutable numbering. Invoice numbers should run in sequence and never be reused, with no gaps. Deleting an invoice leaves a gap that then has to be explained. Cancel or credit instead, which every application here supports. Whether gapless numbering is required of you depends on where you are registered, and it is worth asking early.
Retention. Keep the database dumps and the rendered PDFs for as long as your rules require. The PDF is the document that was actually sent, and that is the artefact that matters if an invoice is ever disputed.
A change history. Being able to show who edited an invoice and when is worth having, and it varies most between these applications. Invoice Ninja keeps a per-record activity log.
Structured e-invoicing has become a real requirement rather than a nice extra in parts of the EU. Invoice Ninja generates the common formats, covering ZUGFeRD and XRechnung, Facturae, FatturaPA and generic EN16931. Delivery over the Peppol network from a self-hosted instance goes through Invoice Ninja's own access point: you register your entity with them and buy credits per submission, so it is a paid service as of August 2026. Kimai reaches the same formats through its paid plugin. The other three produce no structured e-invoices at all today, which is the single strongest reason to pick Invoice Ninja if you invoice inside the EU.
FAQ
Should I install Crater or InvoiceShelf?
InvoiceShelf. Crater's default branch has had no commit for 741 days as of 21 August 2026, and its own issue tracker points people at the fork. InvoiceShelf carries the same AGPL-3.0 licence and the same data model, so moving between them is a database migration rather than a rewrite. Any guide still recommending Crater was written before the project went quiet.
Can I use Invoice Ninja for my own business without paying?
Yes. Self-hosted Invoice Ninja is fully functional at no cost under the Elastic License 2.0, including recurring invoices, the client portal and payment gateways. Two things cost money. Removing the Invoice Ninja branding from client-facing pages needs the white-label licence, "$40 per year" in the project's own words as of August 2026. Sending e-invoices over the Peppol network is metered separately. What the licence forbids is running the software as a hosted service for other people.
Why do my invoice emails go to the spam folder?
Almost always because the message is not authenticated for the domain in its From address. Send through an SMTP relay rather than from the VPS itself, then publish SPF and DMARC records for your domain and add the DKIM record under the selector your relay gives you. Keep your own address in From and put the client in Reply-To. Setting the client's address as the sender makes Gmail reject the message with 550 5.7.1 Unauthenticated email from example.com is not accepted due to domain's DMARC policy. Confirm the fix by finding dkim=pass and spf=pass in the Authentication-Results header of a test message.
Which one should I pick if I bill by the hour?
Kimai. It is a time tracker first, so the invoice is generated from approved timesheet entries and the line items are the work you logged. Templates are DOCX, ODS or XLSX files you design in a word processor, which is easier than editing CSS. The trade is that Kimai has no client portal and no payment gateway, so you email the PDF and get paid by transfer. Invoice Ninja also tracks time and does have a portal, so choose that instead if online card payment matters more to you than timesheet detail.
What exactly do I need to back up?
The database, the storage volume holding logos and generated PDFs, the environment file, and the rendered PDFs kept as plain files. The environment file is the one people miss: Laravel applications such as Invoice Ninja and InvoiceShelf encrypt stored credentials with APP_KEY, so a database restored next to a newly generated key leaves your payment gateway settings unreadable. Restore the whole thing into a throwaway instance once and open an old invoice, because that is the only way to know the backup works.