自托管开票软件对比:Invoice Ninja、InvoiceShelf等
对比 Invoice Ninja、InvoiceShelf、InvoicePlane、Kimai 和 ERPNext 在一台 VPS 上的周期性开票、税费、客户门户、支付网关与许可证差异。
应选择哪款自托管开票软件?
在一台 VPS 上,自托管开票软件有 5 个实际可行的选择,但它们并不是相近的替代品。Invoice Ninja 是功能最完整的产品:定期开票、客户门户、支付网关和电子发票格式在安装当天即可使用。InvoiceShelf 是更轻量的完全开源选项。InvoicePlane 是纯 PHP 应用,无需编排。Kimai 按记录的工时计费,而不是按明细项目开票。ERPNext 是完整的会计系统,同时也能打印发票。
自托管开票系统出现问题,通常不是因为缺少功能,而是因为以下两个问题。第一个是电子邮件。发票进入垃圾邮件文件夹,就意味着发票未付款,而且你不会得知这一情况。第二个是备份。这是少数数据丢失后会引发税务问题,而不只是带来不便的自托管数据库。
先阅读许可证,再查看功能列表。这 5 个软件中,有 3 个采用著作左权许可证,1 个采用宽松许可证,另 1 个虽然提供源代码,但对软件的使用方式有实际限制。
五个选项分别适合谁
Invoice Ninja
这是一个 Laravel 应用,目前处于 v5 系列。自托管版本免费且不受功能限制,支持周期性发票、报价单、项目、时间跟踪、费用、客户门户,以及 Stripe 和 PayPal 等支付网关。项目将自身称为“使用 Laravel 构建的、源码可用的发票、报价单、项目和时间跟踪应用”。唯一收费部分是品牌标识。项目方的原话是:“我们提供每年 $40 的白标许可证,用于移除应用面向客户部分中的 Invoice Ninja 品牌标识。”截至 August 2026,该价格仍然有效。
如果您需要功能最完整的产品,并且可以接受运行规模最大的技术栈,请选择它。
InvoiceShelf
这是 Crater 的一个分支,采用 GNU Affero General Public License version 3(AGPL-3.0)授权。它支持发票、估算单、周期性计费、费用、税费、多币种、客户门户和多公司管理。支付功能和白标功能分别位于官方模块仓库中,两个模块都采用 AGPL-3.0 授权,且均可免费使用。银行卡支付通过 Stripe checkout 完成。
如果您需要一个小型、完全开源的应用,并且不需要三十个支付网关,请选择它。
InvoicePlane
这是一个基于 CodeIgniter 的 PHP 应用,采用 MIT 许可证授权。这里列出的选项中,只有它可以通过将文件解压到 web root 来安装。不需要 Docker,也不需要 Node 构建步骤。PDF 由 mPDF 生成,因此模板使用 HTML 和 CSS。客户会获得一个访客链接,用于查看和支付发票。
问题在于支付功能。文档明确说明,InvoicePlane 1.6 默认只支持 Stripe,该版本移除了其他支付提供商。1.6 系列发布的要求是 PHP 8.0 至 8.1,以及 MySQL 5.5 或对应版本的 MariaDB。请阅读所下载版本的说明,因为 1.7 系列仍在持续开发。
如果您使用共享主机或 512 MB VPS,请选择它。
Kimai
这是一个采用 AGPL-3.0 授权的时间跟踪工具,可以根据工时表生成发票。对于按小时收费的用户,这是正确的选择。发票模板以 DOCX、ODS 或 XLSX 文件上传。文档确认,这些是唯一接受的格式,之后模板会被渲染为 PDF 或 HTML。您可以在文字处理器中设计发票,而不必编写 CSS,这通常更容易。
Kimai 没有客户门户,也没有支付网关,因此您需要通过电子邮件发送 PDF。结构化电子发票功能由一个名为 E-Invoice Plus 的付费商店插件提供。截至 August 2026,自托管安装的价格为每年 99 EUR,不含 VAT,支持 EN16931、XRechnung、ZUGFeRD、Factur-X 和 Peppol 配置文件。
如果发票是时间跟踪的结果,请选择它。
ERPNext
它采用 GPL-3.0 授权,属于另一类软件。销售发票只是完整会计系统中的一种单据类型。该系统还包括会计科目表、日记账分录、库存和工资管理。如果开票已经不再是全部工作内容,这是正确的选择。对于只有八个客户的自由职业者来说,它包含的功能过多。部署 ERPNext 本身就是一个项目,详见在 VPS 上运行 ERPNext。
定期发票依赖一个您看不到的调度器
Invoice Ninja、InvoiceShelf、InvoicePlane 和 ERPNext 都支持生成定期发票。Kimai 的核心功能不支持定期发票,因为它会根据某个时间段内的工时表条目生成发票。
所有基于 Laravel 的这类应用都有相同且不易察觉的故障模式。定期发票由计划任务 php artisan schedule:run 创建,该任务必须由应用外部的组件每分钟调用一次。如果没有任何组件调用它,定期发票列表仍会显示下一次发送日期,但该日期会直接过去。不会生成发票。日志中也不会记录错误,因为相关代码根本没有运行。
因此,创建第一张定期发票后,将其下一次发送日期设置为几分钟后,然后等待。如果没有收到发票,说明调度器没有运行。在 Docker 安装中,这通常表示 compose 文件中的 cron 或 scheduler 服务从未启动,或者已经退出。
docker compose ps
docker compose logs --tail 50文件中的每个服务都应显示 running。处于 exited 状态的服务就是需要查看其日志的服务。
多币种和税务:提交承诺前需要检查什么
功能列表中的“多币种”可能代表完全不同的功能。请在演示实例上执行以下4项检查,并使用您自己业务中的真实数字,因为这类错误通常是由会计师发现,而不是您自己发现。
- 发票是否保存了开票日期实际适用的汇率?必须保存。如果应用根据实时汇率重新计算总额,您每次打开上季度的报表时,报表都会发生变化,账目也会与已经申报的记录不一致。
- 税费是按行项目、按发票,还是两者都计算?在某一行适用零税率之前,统一的发票级税率可能看起来没有问题。
- 是否既可录入含税价格,也可录入不含税价格?事后转换会导致每行出现几分钱的舍入差异,这些差异会在一年中不断累积。
- 对于不承担 VAT 的其他国家/地区客户,单据会显示什么内容?发票上必须打印免税说明,不能只在税额列显示0。
Invoice Ninja 和 ERPNext 可以处理以上4项。InvoiceShelf 将税费和多币种列为其功能。InvoicePlane 和 Kimai 更为简单,因此请用您最复杂的发票进行测试,而不是用最简单的发票测试。
客户端门户和支付网关
客户端门户是客户查看发票、下载 PDF 并付款的页面。这项功能的重要性高于表面看起来的程度,因为通过电子邮件催款所耗费的时间,可能比开具发票本身还多。
Invoice Ninja 在这方面最完善,提供完整的门户、多种支付网关、已保存的支付方式,以及对周期性发票进行自动扣款的功能。InvoiceShelf 提供客户门户,并通过其支付模块加入 Stripe checkout。InvoicePlane 为每张发票提供访客链接,并支持 Stripe。Kimai 两者都不支持,因此只能将 PDF 作为附件发送。
有一点经常被混淆。自行托管发票应用并不意味着要保存银行卡号。使用 Stripe checkout 时,客户会被重定向到 Stripe,并在那里输入卡号;您的 VPS 从未接触到该卡号。您的 PCI(支付卡行业)合规范围之所以较小,正是因为您没有自行构建支付表单。如果之后需要接入多个支付处理商,或希望按国家路由支付,这应放在独立层中,例如 使用 Hyperswitch 自行托管支付编排。
PDF 质量,以及您最终要编辑的模板
您将编辑该模板。此处的每个默认设置都可能导致地址在您所在国家/地区显示在错误的位置。
Invoice Ninja 使用集成在镜像中的无头 Chromium 将 HTML 和 CSS 模板渲染为 PDF,因此容器体积较大。请为此预留内存:无头浏览器是整个堆栈中占用资源最多的进程,因此 1 GB VPS 的内存空间比较紧张。InvoicePlane 使用 mPDF。它是纯 PHP 实现,资源占用较低,但会忽略部分现代 CSS。Kimai 采用相反的方式,要求您上传 DOCX 或 ODS 文件。这样,布局由使用 LibreOffice 或 Word、但从未编写过样式表的人员完成。
如果您重视设计好的发票,而不在意 CSS,Kimai 的方式更合适。如果您希望将模板纳入版本控制并支持差异比较,HTML 更合适。
许可证允许商业使用吗?
这 5 个项目都允许您向自己的客户开具账单。只有当您进行更进一步的操作时,差异才会显现。
Invoice Ninja 采用 Elastic License 2.0。以下两项条款很重要。
您不得将该软件作为托管服务或代管服务提供给第三方,前提是该服务允许用户访问该软件的大部分功能。
您不得移动、更改、禁用或绕过软件中的许可证密钥功能。
因此,为自己的企业运行它没有问题;但将其实例作为产品提供给客户则不允许。第二项条款也是去除品牌标识需要购买 $40 许可证,而不是直接修改源代码的原因。
InvoiceShelf、Kimai 和 Crater 采用 AGPL-3.0。商业使用免费,也不存在更高的授权层级。相关义务位于第 13 条:如果您修改代码,并允许他人通过网络使用修改后的版本,就必须向这些用户提供您所做修改的源代码。未修改的实例已经由上游公开源代码覆盖,因此大多数自托管用户不需要处理这一点。
InvoicePlane 采用 MIT 许可证,是这些项目中限制最少的许可证。不过,其 README 说明名称和徽标仍归该项目所有。
ERPNext 采用 GPL-3.0。您分发的修改版本必须公开共享,但私下运行不会产生该项义务。
以上内容是对许可证文本的说明,不构成法律建议。
项目仍在维护吗?检查提交日志
Crater 多年来一直是广受欢迎的开源发票应用,许多指南仍在推荐它。其默认分支已经 741 天没有提交。
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
}
]该区块中的 6 个项目里,有 5 个项目在过去一周内提交过代码。InvoiceShelf 是延续 Crater 开发工作的分支,使用相同的 AGPL-3.0 许可证和相同的数据模型,因此迁移过去属于数据迁移,而不是重写应用。请安装 InvoiceShelf,不要安装 Crater。
在 VPS 上逐一安装
除 InvoicePlane 外,其他应用都可以使用 Docker Compose 更方便地运行。如果您不熟悉 Docker Compose,请先阅读在 VPS 上创建第一个 Docker Compose 栈,然后再返回这里。
Invoice Ninja 提供了自己的 compose 仓库。
git clone https://github.com/invoiceninja/dockerfiles.git
cd dockerfiles
docker run --rm -it invoiceninja/invoiceninja php artisan key:generate --show将该密钥复制到仓库中的 env 文件,并填入真实的 APP_URL。然后设置镜像所需的所有权并启动应用。
chmod 755 docker/app/public
sudo chown -R 1500:1500 docker/app
docker compose up -d1500:1500 的所有权与镜像内的用户匹配。如果跳过这一步,应用将无法写入缓存或生成的 PDF。在设置向导中,将 db 作为数据库主机,因为它是 compose 网络中的服务名称。localhost 指向应用容器本身。
InvoiceShelf 为每种数据库引擎提供一个现成的 compose 文件。
curl -fLO https://raw.githubusercontent.com/InvoiceShelf/docker/master/docker-compose.mysql.yml
mv docker-compose.mysql.yml docker-compose.yml启动前先打开该文件。文件中的每个密码都是占位符,文件注释也明确说明了这一点。APP_URL、SESSION_DOMAIN 和 SANCTUM_STATEFUL_DOMAINS 都默认使用 localhost;首次登录前,必须为这三项设置真实的主机名。然后启动应用。
docker compose up -d它在主机上发布 8090 端口,在容器内提供 8080 端口。在让任何客户端访问它之前,先在其前面配置带 TLS(传输层安全)的反向代理。
Kimai 生产环境 compose 文件,来自项目文档
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:将 APP_SECRET 设置为您自行生成的长随机字符串,将 TRUSTED_HOSTS 设置为实际使用的域名。ADMINMAIL 和 ADMINPASS 会在首次启动时创建第一个管理员账户,因此之后请在界面中修改该密码,并将密码从文件中删除。
InvoicePlane 没有容器。请先创建一个空数据库。
sudo mysql -e "CREATE DATABASE invoiceplane CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"从项目的发布页面下载压缩包,将其解压到 Web 根目录,把 ipconfig.php.example 复制为 ipconfig.php,并按照该文件中的注释在其中设置 URL。然后打开 https://your-domain.example/index.php/setup 并完成安装程序。
为什么发票会进入垃圾邮件文件夹
这是最容易直接造成经济损失的问题,而应用仍会正常报告发票已发送。
不要使用 VPS 自身的 IP 地址发送发票邮件。新的 VPS IP 没有发送历史记录,大型邮件服务商默认会将托管网段中未知地址发来的第一封邮件视为可疑邮件。应通过已完成身份验证的域名上的 SMTP(简单邮件传输协议)中继发送。端到端完成此配置需要单独的指南,从自托管应用可靠发送邮件介绍了中继、DNS 记录和测试。
大多数开票配置的问题都出在 From 地址上。该地址必须属于您控制的域名,并且中继必须使用 DKIM(域名密钥识别邮件)为该域名签名。将客户自己的地址放入 From 看似礼貌,实际上会破坏投递,因为接收服务器会检查 From 域名是否与用于验证邮件的域名一致。Gmail 会直接拒绝不匹配的邮件:
550 5.7.1 Unauthenticated email from example.com is not accepted due to domain's DMARC policy将您自己的地址保留在 From 中,并将客户地址放入 Reply-To。发送真实邮件前,先检查已发布的记录。
dig +short TXT example.com
dig +short TXT _dmarc.example.com
dig +short TXT selector1._domainkey.example.com第一条命令应返回 SPF(发件人策略框架)记录,并包含您的中继。第二条命令应返回 DMARC(基于域的消息身份验证、报告和一致性)策略。第三条命令取决于中继提供的 selector。如果返回空结果,表示 DKIM 记录缺失,或发布时使用了错误的名称,因此您发送的每封邮件都会无法通过签名检查。
然后向您在大型邮件服务商处拥有的账户发送一张真实发票,并查看原始邮件头。Authentication-Results 标头中的 dkim=pass 和 spf=pass 是唯一有效的证明。应用自身的“已发送”标记只能说明它已将邮件交给中继。
备份:不可丢失的唯一数据库
媒体服务器丢失后,只需重新下载文件。即使是 自行托管的 PhotoPrism 或 Immich 照片服务器,通常也保存着一份原始照片副本,而另一份仍在拍摄照片的手机上。丢失发票数据库后,您将无法确认开具了哪些账单、收到了哪些款项,或仍欠哪些款项。请按同等重要性处理。
请备份以下四项:数据库;存储卷,其中保存徽标、生成的 PDF 和费用收据;应用的环境文件;以及以普通文件形式保存的已渲染 PDF。因为即使没有生成该 PDF 的应用,十年后仍可打开 PDF。
环境文件的重要性超出许多人的预期。Laravel 使用该文件中的 APP_KEY 加密敏感存储值,包括支付网关凭据。将数据库恢复到使用新生成密钥的环境旁时,这些值将无法解密。您的发票全部存在,但网关设置无法读取。
InvoiceShelf 堆栈的转储命令如下。其数据库服务运行在 mariadb:10 上,因此容器内使用的工具是 mariadb-dump。
docker compose exec -T database mariadb-dump \
--user=root --password=YOUR_ROOT_PASSWORD \
--single-transaction --databases invoiceshelf > invoiceshelf.sql--single-transaction 会在不锁定 InnoDB 表的情况下创建一致性快照,因此转储期间应用仍可继续提供服务。-T 会阻止 Docker 分配终端,否则终端可能破坏重定向的输出,导致生成的转储文件无法导入。
请按计划使用 加密的 restic 备份将其发送到异地存储,并将保留期限设置为足够覆盖会计师指定的期间。
然后执行恢复。将转储导入临时容器,登录并打开上个月的发票。在实际完成一次恢复前,您无法确认备份是否有效。
会计师会要求提供的资料
不要根据教程获取税务建议。下面列出的是此软件可以生成的内容,您可以将这份清单交给为您提供建议的人员。
可导出的数据。 您应能直接在界面中将发票和付款记录导出为 CSV,无需使用数据库客户端,也无需提交支持工单。SQL dump 是备份,不是导出文件,因为您的会计师无法直接打开它。
不可变的编号。 发票编号应连续递增,不能重复使用,也不应出现间隔。删除发票会留下一个间隔,之后必须对此作出说明。请改用取消或贷项处理,本节介绍的每个应用都支持这两种方式。您是否必须使用无间隔编号,取决于您的注册地;建议尽早确认这一点。
保留期限。 按照适用规则要求的期限,保留数据库 dump 和渲染后的 PDF。PDF 是实际发送出去的文件;如果发票发生争议,重要的就是这一份文件。
变更历史。 能够显示谁在何时编辑了发票非常有用,而这也是这些应用之间差异最大的部分。Invoice Ninja 会为每条记录保留活动日志。
在欧盟部分地区,结构化电子发票已经成为实际要求,而不再只是可选功能。Invoice Ninja 可生成常见格式,包括 ZUGFeRD 和 XRechnung、Facturae、FatturaPA 以及通用 EN16931。通过自托管实例经由 Peppol 网络发送时,需要使用 Invoice Ninja 自己的接入点:您需要向其注册实体,并按每次提交购买额度。因此,截至 August 2026,这是一项付费服务。Kimai 通过其付费插件支持相同格式。其他三个应用目前完全不能生成结构化电子发票;如果您在欧盟境内开具发票,这就是选择 Invoice Ninja 的最充分理由。
FAQ
我应该安装 Crater 还是 InvoiceShelf?
InvoiceShelf。截至 21 August 2026,Crater 的默认分支已有 741 天没有提交,其自身的 issue tracker 也将用户引导到该 fork。InvoiceShelf 使用相同的 AGPL-3.0 许可证和相同的数据模型,因此两者之间的迁移属于数据库迁移,而不是重写应用。任何仍然推荐 Crater 的指南,都是在该项目停止活跃之前编写的。
我可以免费将 Invoice Ninja 用于自己的业务吗?
可以。自行托管的 Invoice Ninja 在 Elastic License 2.0 下无需付费即可完整运行,包括定期开票、客户端门户和支付网关。有两项功能需要付费。要从面向客户端的页面中移除 Invoice Ninja 品牌,需要购买白标许可证;按该项目截至 August 2026 的说法,价格为 "$40 per year"。通过 Peppol 网络发送电子发票需要单独按量计费。该许可证禁止的是将此软件作为托管服务提供给其他人。
为什么我的发票邮件会进入垃圾邮件文件夹?
几乎总是因为邮件没有针对其 From 地址中的域名完成身份验证。不要直接从 VPS 发送,而应通过 SMTP 中继发送,然后为你的域名发布 SPF 和 DMARC 记录,并在中继提供的 selector 下添加 DKIM 记录。将你自己的地址保留在 From 中,并将客户端地址放在 Reply-To 中。将客户端地址设置为发件人会导致 Gmail 使用 550 5.7.1 Unauthenticated email from example.com is not accepted due to domain's DMARC policy 拒收邮件。发送测试邮件后,在其 Authentication-Results 头中找到 dkim=pass 和 spf=pass,以确认修复生效。
如果我按小时收费,应该选择哪个?
Kimai。它首先是时间跟踪器,因此发票根据已批准的 timesheet 条目生成,明细项就是你记录的工作内容。模板是 DOCX、ODS 或 XLSX 文件,你可以在文字处理器中设计模板;这比编辑 CSS 更容易。代价是 Kimai 没有客户端门户和支付网关,因此你需要通过电子邮件发送 PDF,再通过转账收款。Invoice Ninja 也支持时间跟踪,并且提供客户端门户;如果在线刷卡支付对你比 timesheet 明细更重要,应改选 Invoice Ninja。
我到底需要备份哪些内容?
数据库、用于存放徽标和生成的 PDF 的存储卷、环境文件,以及以普通文件形式保存的已渲染 PDF。环境文件最容易被遗漏:Invoice Ninja 和 InvoiceShelf 等 Laravel 应用使用 APP_KEY 加密存储的凭据,因此将数据库恢复到使用新生成密钥的环境后,支付网关设置将无法读取。将完整备份恢复到一次性实例中,并打开一张旧发票,因为只有这样才能确认备份确实可用。