Deployment guide

LedgerSMB

Overview

LedgerSMB is an open source double-entry accounting and ERP system for small and medium businesses. It began as a fork of SQL-Ledger in 2006 and has been maintained continuously since, with its own published security advisories and a release cadence that has kept the 1.13 line current through 2026.

LedgerSMB covers the whole commercial cycle in one ledger:

  • Sales: quotations, sales orders, invoices, receipts and customer statements.
  • Purchasing: requests for quotation, purchase orders, vendor invoices and payments.
  • Operations: parts and inventory, projects and timecards, fixed assets, budgeting and bank reconciliation.
  • Accounting: a full general ledger with trial balance, income statement and balance sheet reporting.
  • Documents and integration: template-driven, customer-facing documents rendered as PDF, HTML, CSV and ODF, plus a REST interface.

This SecureAnyCloud image delivers LedgerSMB 1.13.8 on Ubuntu 24.04, served over HTTPS, with a company database and chart of accounts already created. Every secret is generated on the first boot of your VM.

What you get:

  • LedgerSMB 1.13.8 at /opt/ledgersmb, installed from the vendor's own release asset and verified by SHA-256
  • starman, upstream's recommended PSGI server, bound to 127.0.0.1:5762 only
  • nginx serving HTTPS on port 443 with a certificate generated for your VM. Port 80 only redirects to HTTPS.
  • PostgreSQL 16 on the loopback address only, holding the company ledger
  • A company database named ledgersmb with a general chart of accounts already loaded, so you can sign in and start work
  • No default login. The administrator password, the database passwords and the TLS key are all generated on your VM's first boot and written to a root-only file.
  • The setup.pl database administration console, gated by two independent checks: an HTTP authentication challenge at the web server, and PostgreSQL authentication in the application
  • postgresql@16-main, ledgersmb and nginx systemd services, enabled and active
  • 24/7 SecureAnyCloud support

LedgerSMB is a trademark of its respective owner. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them. This image packages the unmodified open source software, which is distributed under the GNU General Public License version 2.

Prerequisites

You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region.

Standard_B2s (2 vCPU, 4 GiB RAM) is the recommended size and is the size this image is tested at. LedgerSMB runs under starman with three preforked workers, each holding a resident copy of the application, so memory rather than CPU is the constraint. Choose a larger size for many concurrent users or heavy reporting.

Configure these network security group inbound rules: 22/tcp from your management network and 443/tcp for the web interface. Optionally allow 80/tcp, which only redirects to HTTPS.

1

Deploy from the Azure Marketplace

Find LedgerSMB on Ubuntu 24.04 LTS in the Azure Marketplace and choose Create. Complete the wizard with these settings:

  • Your subscription and resource group, and your region
  • VM size Standard_B2s
  • An administrator username of azureuser with your SSH public key
  • Inbound ports 22 and 443
  • The default managed disk for the OS disk

The VM is ready to use as soon as first boot finishes provisioning. This takes under a minute after the VM reports Running.

2

Deploy from the Azure CLI

Run these commands on your own workstation, not on the VM. Replace the resource group, region and management CIDR to suit.

az group create --name ledgersmb-rg --location eastus
az network vnet create --resource-group ledgersmb-rg --name ledgersmb-vnet \
  --address-prefix 10.10.0.0/16 --subnet-name app --subnet-prefix 10.10.1.0/24
az network nsg create --resource-group ledgersmb-rg --name ledgersmb-nsg
az network nsg rule create --resource-group ledgersmb-rg --nsg-name ledgersmb-nsg \
  --name allow-ssh --priority 1000 --destination-port-ranges 22 \
  --source-address-prefixes <your-mgmt-cidr> --access Allow --protocol Tcp
az network nsg rule create --resource-group ledgersmb-rg --nsg-name ledgersmb-nsg \
  --name allow-https --priority 1010 --destination-port-ranges 443 \
  --source-address-prefixes Internet --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:ledgersmb-ubuntu-24-04:default:latest
az vm create --resource-group ledgersmb-rg --name ledgersmb-01 \
  --image sac1647283583153:ledgersmb-ubuntu-24-04:default:latest \
  --size Standard_B2s --admin-username azureuser --generate-ssh-keys \
  --vnet-name ledgersmb-vnet --subnet app --nsg ledgersmb-nsg \
  --public-ip-sku Standard
3

Confirm the stack is running

SSH to the VM as azureuser and check the three services. postgresql@16-main is the unit that holds the database. The postgresql wrapper unit can report active while the real cluster unit has failed, so check this one.

systemctl is-active postgresql@16-main ledgersmb nginx cat /etc/ledgersmb/version
sudo -u postgres psql -tAc 'SHOW server_version'

Expected output:

active active active 1.13.8 16.15 (Ubuntu 16.15-0ubuntu0.24.04.1)
4

Check what is listening

Only SSH and the two web ports are reachable from outside the VM. The application server and the database are both bound to the loopback address, so neither is reachable from the network.

sudo /usr/local/sbin/ledgersmb-port-check.sh

Expected output:

off-box listeners: [22 80 443] expected: [22 80 443] LEDGERSMB_PORTS_OK

To see the detail, including the loopback-only bindings:

ss -lnt | grep -E '5762|5432|:443|:80 |:22'
5

Retrieve your credentials

Nothing secret is baked into the image. On the first boot of your VM, LedgerSMB generates its own TLS key pair, its own database role password and its own administrator password. It writes them to a file that only root can read.

sudo ls -l /root/ledgersmb-credentials.txt
sudo grep -E '^LEDGERSMB_(URL|COMPANY|ADMIN_USER|SETUP_USER)=' /root/ledgersmb-credentials.txt

Expected output:

-rw------- 1 root root 767 Sep 20 15:52 /root/ledgersmb-credentials.txt LEDGERSMB_URL=https://20.0.0.10/ LEDGERSMB_COMPANY=ledgersmb LEDGERSMB_ADMIN_USER=admin LEDGERSMB_SETUP_USER=lsmb_setup

The address, the timestamp and the exact byte count differ on your instance. The passwords are generated per VM, so the file's length is not fixed.

To read the two passwords themselves:

sudo awk -F= '/^LEDGERSMB_ADMIN_PASSWORD=/{print $2}' /root/ledgersmb-credentials.txt
sudo awk -F= '/^LEDGERSMB_SETUP_PASSWORD=/{print $2}' /root/ledgersmb-credentials.txt

Keep this file, or copy the values into your password manager. These values are unique to this VM and are not recorded anywhere else. Neither SecureAnyCloud nor anyone else can recover them for you.

6

Sign in to the ledger

Browse to https://<the VM's public address>/. LedgerSMB redirects you to the sign-in page. Enter the company ledgersmb, the username admin and the administrator password from the previous step.

The certificate generated on first boot is self-signed, so your browser warns you the first time. This is expected. Step 12 replaces it with a certificate for your own domain.

You can confirm that the credential works end to end from the command line before you open a browser. The script performs a real sign-in through the front door, then requires the resulting session to land on an authenticated page. A status code alone is not proof, because LedgerSMB issues a session cookie to anonymous requests too.

sudo /usr/local/sbin/ledgersmb-verify-login.sh \
  "$(sudo awk -F= '/^LEDGERSMB_ADMIN_USER=/{print $2}' /root/ledgersmb-credentials.txt)" \
  "$(sudo awk -F= '/^LEDGERSMB_ADMIN_PASSWORD=/{print $2}' /root/ledgersmb-credentials.txt)" \
  "$(sudo awk -F= '/^LEDGERSMB_COMPANY=/{print $2}' /root/ledgersmb-credentials.txt)"

Expected output:

LEDGERSMB_LOGIN_OK (authenticate=200 landing=200)
7

The chart of accounts

The company database ships with a general chart of accounts already loaded, so the ledger is usable immediately instead of presenting an empty database and a setup wizard. View the chart under General Ledger → Chart of Accounts in the web interface, or count the accounts directly:

sudo -u postgres psql -d ledgersmb -tAc \
  "SELECT count(*) || ' accounts, ' || count(*) FILTER (WHERE category='A') || ' asset' FROM account"

Expected output:

102 accounts, 20 asset

To start from a different national chart, note that LedgerSMB ships charts for 29 countries. List them, then load one through the setup.pl console, described in the next step, when you create a new company:

sudo ls /opt/ledgersmb/locale/coa/
sudo ls /opt/ledgersmb/locale/coa/us/
8

The database administration console, and why it is gated twice

LedgerSMB has a second front door at /setup.pl. It is not part of the accounting application. It creates, upgrades, copies, backs up and destroys company databases, and it authenticates as a PostgreSQL superuser. On this image, two independent checks protect it.

The first check is at the web server. An unauthenticated request never reaches the application:

curl -sk -o /dev/null -w 'unauthenticated /setup.pl -> %{http_code}\n' https://127.0.0.1/setup.pl

Expected output:

unauthenticated /setup.pl -> 401

This check covers every spelling of the path, not just the obvious one. It keeps working even if the database is stopped, because it consults no database:

for p in /setup.pl /Setup.pl //setup.pl /./setup.pl /x/../setup.pl /%73etup.pl; do printf '%-20s -> %s\n' "$p" "$(curl -sk -o /dev/null -w '%{http_code}' https://127.0.0.1$p)" done

Expected output:

/setup.pl -> 401 /Setup.pl -> 401 //setup.pl -> 401 /./setup.pl -> 401 /x/../setup.pl -> 401 /%73etup.pl -> 401

The second check is in the application itself, which authenticates the same credential against PostgreSQL. Published, blank and weak credentials are refused:

for c in admin:admin postgres:postgres lsmb_setup: lsmb_setup:password root:root; do printf '%-22s -> %s\n' "$c" "$(curl -sk -o /dev/null -w '%{http_code}' -u "$c" https://127.0.0.1/setup.pl)" done

Expected output:

admin:admin -> 401 postgres:postgres -> 401 lsmb_setup: -> 401 lsmb_setup:password -> 401 root:root -> 401

Your own per-VM credential passes both checks:

curl -sk -o /dev/null -w 'per-VM setup credential -> %{http_code}\n' \
  -u "$(sudo awk -F= '/^LEDGERSMB_SETUP_USER=/{print $2}' /root/ledgersmb-credentials.txt):$(sudo awk -F= '/^LEDGERSMB_SETUP_PASSWORD=/{print $2}' /root/ledgersmb-credentials.txt)" \
  https://127.0.0.1/setup.pl

Expected output:

per-VM setup credential -> 200

To use the console in a browser, go to https://<the VM's public address>/setup.pl. Sign-in takes two steps, because the two checks are separate systems:

  • Your browser shows an HTTP authentication prompt. This is nginx. Enter lsmb_setup and the setup password.
  • LedgerSMB then shows its own form, headed Database administrator credentials. Enter the same username and password, and ledgersmb as the database. This time PostgreSQL authenticates you.

Passing the first check does not get you past the second. One first-boot credential opens both, so there is only one password to keep. An attacker, however, has to defeat two independent mechanisms, and the first never reaches the application or the database.

Once you pass both checks, the console reports the database it found and offers the administrative actions: creating and copying companies, adding users, loading templates, taking backups and running diagnostics.

To give a second person access to setup.pl, add them to the web server's credential file and create a matching PostgreSQL role. Both checks must pass, so both must know about the new user.

9

Add your own users

Every LedgerSMB user is also a PostgreSQL role, so you create users inside the application rather than in a configuration file. Sign in as admin and go to System → Users → Add User. Give the account a username, a password and the permission sets it needs, then save.

Confirm that the account was created:

sudo -u postgres psql -d ledgersmb -tAc \
  "SELECT username FROM users ORDER BY username"

After a fresh deployment, before you add anyone, the query lists only the administrator created on first boot:

admin
10

Create a second company

A LedgerSMB "company" is a separate database with its own ledger. This is how you keep several legal entities apart on one instance. Create one from the setup.pl console: sign in with the setup credential, enter a new database name, choose a chart of accounts, and create the initial user for that company.

The console does the same work as the command-line tool shipped with the application. List the commands the tool offers:

sudo ls /opt/ledgersmb/lib/LedgerSMB/Admin/Command/ | sed 's/\.pm$//'

Expected output:

backup copy create destroy rename restore setup template upgrade user

Run the tool as sudo PERL5LIB=/opt/ledgersmb-deps/lib/perl5 perl -I /opt/ledgersmb/lib -I /opt/ledgersmb/old/lib /opt/ledgersmb/bin/ledgersmb-admin --help for its full usage text. The tool exits with a non-zero status after it prints help or version information. This is upstream behavior, not an error.

11

Change the administrator password

Change the password from the web interface under Preferences → Password. This is the supported path, and it keeps the application's own records consistent.

If you need to reset the password from the command line, for instance because it has been lost, use the provisioning helper that first boot itself uses. The helper reads both passwords from standard input, so neither appears in ps output, in /proc or in the system journal. Replace <new-password> with the password you want:

printf '%s\n%s\n' \
  "$(sudo awk -F= '/^LEDGERSMB_SETUP_PASSWORD=/{print $2}' /root/ledgersmb-credentials.txt)" \
  '<new-password>' \
  | sudo PERL5LIB=/opt/ledgersmb-deps/lib/perl5 perl \
  -I /opt/ledgersmb/lib -I /opt/ledgersmb/old/lib \
  /usr/local/sbin/ledgersmb-provision-admin.pl ledgersmb lsmb_setup admin

Afterwards, update /root/ledgersmb-credentials.txt so that the file and the account continue to agree.

12

Use your own domain and certificate

The certificate generated on first boot is self-signed. Point a DNS A record at the VM's public address, then obtain a certificate from a public authority. Replace <your-domain> with your hostname:

sudo apt-get install -y certbot
sudo certbot certonly --standalone -d <your-domain> --agree-tos -m admin@<your-domain> --non-interactive sudo sed -i "s#/etc/ledgersmb/tls/ledgersmb.crt#/etc/letsencrypt/live/<your-domain>/fullchain.pem#" /etc/nginx/sites-available/ledgersmb
sudo sed -i "s#/etc/ledgersmb/tls/ledgersmb.key#/etc/letsencrypt/live/<your-domain>/privkey.pem#" /etc/nginx/sites-available/ledgersmb
sudo nginx -t && sudo systemctl reload nginx

nginx answers for any hostname, so no other change is needed. The certbot timer that the package installs handles renewal.

13

Verify the security posture

Run this short self-test at any time. It confirms that the per-VM credential works, that published defaults are refused, that the administration console is gated, and that nothing but SSH and the web ports is reachable from off the VM.

echo "--- off-box listeners ---" sudo /usr/local/sbin/ledgersmb-port-check.sh
echo "--- unauthenticated setup.pl ---" curl -sk -o /dev/null -w 'setup.pl -> %{http_code}\n' https://127.0.0.1/setup.pl
echo "--- published default refused ---" curl -sk -o /dev/null -w 'admin:admin -> %{http_code}\n' -u admin:admin https://127.0.0.1/setup.pl
echo "--- administrator password does not expire ---" sudo -u postgres psql -tAc "SELECT rolname, COALESCE(rolvaliduntil::text,'infinity') FROM pg_roles WHERE rolname IN ('admin','lsmb_setup')"
echo "--- credentials file permissions ---" sudo stat -c '%n %U:%G %a' /root/ledgersmb-credentials.txt /etc/ledgersmb/tls/ledgersmb.key

Expected output:

--- off-box listeners --- off-box listeners: [22 80 443] expected: [22 80 443] LEDGERSMB_PORTS_OK --- unauthenticated setup.pl --- setup.pl -> 401 --- published default refused --- admin:admin -> 401 --- administrator password does not expire --- admin|infinity lsmb_setup|infinity --- credentials file permissions --- /root/ledgersmb-credentials.txt root:root 600 /etc/ledgersmb/tls/ledgersmb.key root:root 600
14

Back up and restore

Your ledger lives in the ledgersmb database. Back it up with standard PostgreSQL tooling, and keep the backups off the VM.

sudo -u postgres pg_dump --format=custom --file=/var/tmp/ledgersmb-$(date +%F).dump ledgersmb ls -lh /var/tmp/ledgersmb-*.dump

To restore into a fresh company database, replace <backup-dir> with the directory holding your dump:

sudo -u postgres createdb ledgersmb_restored
sudo -u postgres pg_restore --dbname=ledgersmb_restored <backup-dir>/ledgersmb-2026-09-20.dump

Also keep a copy of /root/ledgersmb-credentials.txt. The database dump does not contain the role passwords, and without them you cannot sign in to the restored company.

15

Keep the instance patched

The image ships with unattended-upgrades enabled, so security updates install automatically. To apply everything immediately:

sudo apt-get update
sudo apt-get upgrade -y sudo systemctl restart ledgersmb nginx

LedgerSMB itself is installed from the upstream release tarball rather than from a distribution package, so application upgrades are a deliberate step. Take a backup and unpack the new release alongside the current one. Then run the schema upgrade from setup.pl, which detects the version difference and applies the migration.

16

Grow the disk as your ledger grows

The ledger, including document attachments, lives in PostgreSQL on the OS disk. To expand the disk, resize it in the Azure portal or CLI while the VM is deallocated, then grow the filesystem:

lsblk
sudo growpart /dev/sda 1
sudo resize2fs /dev/sda1 df -h /

Troubleshooting

The web interface does not answer at all. The web tier deliberately refuses to serve until first boot has finished generating this VM's secrets. Check that first boot completed:

systemctl is-active ledgersmb-firstboot.service ls -l /var/lib/sac/data/ledgersmb-firstboot.done
sudo journalctl -u ledgersmb-firstboot.service --no-pager -n 40

A browser certificate warning. This is expected until Step 12, because the first-boot certificate is self-signed.

setup.pl prompts for a password and will not accept the administrator password. The two are different credentials. setup.pl wants LEDGERSMB_SETUP_USER and LEDGERSMB_SETUP_PASSWORD. The ledger itself wants LEDGERSMB_ADMIN_USER and LEDGERSMB_ADMIN_PASSWORD.

Sign-in is refused and you are certain the password is right. Confirm that the account has no expiry. An expiry is the one thing that would make a correct password stop working:

sudo -u postgres psql -tAc "SELECT rolname, COALESCE(rolvaliduntil::text,'infinity') FROM pg_roles WHERE rolname='admin'"

The query must print infinity. If it prints a date, the account was re-created outside the helper in Step 11. Re-run that helper to restore the account.

The application is running but slow. starman preforks three workers. On a larger VM size, raise the worker count in /etc/systemd/system/ledgersmb.service, then run sudo systemctl daemon-reload && sudo systemctl restart ledgersmb.

Checking the application's own log. LedgerSMB logs to the journal under its unit:

sudo journalctl -u ledgersmb --no-pager -n 40

Support

SecureAnyCloud provides 24/7 technical support for this image by email at sales@secureanycloud.com and by live chat, with a one-hour average response time for critical issues.

We help with deployment, chart of accounts selection, company database creation and schema upgrades, document and invoice template customization, the REST interface, user and role administration, backup and restore, PostgreSQL administration, and troubleshooting.

Deploy on Azure

Launch LedgerSMB on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure