Paymenter
Overview
Paymenter is a free and open source billing and client management platform for hosting providers and anyone selling recurring services. It gives your customers a public storefront, a checkout and a client area for their services, invoices and support tickets. It gives you an administration panel for products, pricing, currencies, coupons, taxes and recurring invoicing.
This SecureAnyCloud image delivers Paymenter fully installed on Ubuntu 24.04. Database migrations and seeded reference data are already in place, and there is no setup wizard to complete. You sign in and start building your catalogue. Payment gateways and server provisioning are handled by extensions, so orders can be paid through the processor you already use and handed to the system that creates the service.
What you get:
- Paymenter 1.5.7 (MIT licensed) on Laravel 12.63.0, served from
/var/www/paymenter/public - nginx 1.24 (TLS on :443, with :80 redirected to HTTPS), PHP 8.3.6 (php8.3-fpm with OPcache), MariaDB 10.11 and Redis 7.0, all from Ubuntu 24.04 main/universe. MariaDB holds the data, and Redis serves the cache, sessions and queue.
- A per-VM self-signed TLS certificate generated on first boot, so the panel loads over a secure context from the very first request
paymenter-queue.service(the Laravel queue worker) andpaymenter-schedule.timer(the scheduler, every minute) as enabled systemd units. They send invoices, process renewals and act on overdue accounts.- No user account of any kind in the shipped image. The first and only administrator is created on first boot with a random per-VM password, so there is no default login to find.
- Per-VM MariaDB password, Laravel
APP_KEYand Passport OAuth signing keys, all generated at first boot and written to a root-only file - Upstream telemetry shipped switched off, and no shared telemetry identity baked into the image
- 24/7 SecureAnyCloud support
Prerequisites
You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region.
Standard_B2ms (2 vCPU / 8 GiB RAM) is a good starting size. Scale up as your catalogue, customer base and ticket volume grow.
Configure the network security group (NSG) to allow inbound 22/tcp from your management network. Also allow 443/tcp (HTTPS) and 80/tcp (which redirects to HTTPS) from your customers.
Step 1 - Deploy from the Azure Marketplace
In the Azure Marketplace, find Paymenter on Ubuntu 24.04 LTS by SecureAnyCloud and choose Get It Now, then Create.
Pick your resource group, region and VM size (Standard_B2ms or larger). Provide your SSH public key. In the networking step, allow inbound 443/tcp and 80/tcp. Then select Review + create.
Step 2 - Deploy with the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:paymenter:default:latest
az vm create \
--resource-group <your-rg> \
--name paymenter \
--image sac1647283583153:paymenter:default:latest \
--size Standard_B2ms \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name paymenter --port 443 --priority 1010
az vm open-port --resource-group <your-rg> --name paymenter --port 80 --priority 1020Step 3 - Connect to your VM
Connect over SSH as the azureuser account:
ssh azureuser@<vm-public-ip>Step 4 - Confirm the services are running
Six units back Paymenter, and all should report active. The client area answers HTTP 200 over HTTPS. The administration panel redirects an unauthenticated visitor to the sign-in page, and plain HTTP redirects to HTTPS.
systemctl is-active mariadb redis-server php8.3-fpm nginx paymenter-queue paymenter-schedule.timer curl -ks -o /dev/null -w 'client area /login -> HTTP %{http_code}\n' https://127.0.0.1/login curl -ks -o /dev/null -w 'admin panel /admin -> HTTP %{http_code}\n' https://127.0.0.1/admin curl -s -o /dev/null -w 'plain http :80 -> HTTP %{http_code}\n' http://127.0.0.1/Expected output: six lines of active, then client area /login -> HTTP 200, admin panel /admin -> HTTP 302 and plain http :80 -> HTTP 301. The 302 is the redirect to sign-in, which is what you want when nobody is logged in.
nginx listens on :80 and :443 on all interfaces. MariaDB (:3306) and Redis (:6379) are bound to loopback only and are never exposed to the network.
Step 5 - Retrieve your administrator credentials
The image ships with no accounts at all. On first boot, paymenter-firstboot.service creates the first and only administrator with a random password. It also generates the MariaDB password, the Laravel APP_KEY and the Passport OAuth signing keys, and writes them to a root-only file.
Check the file's permissions and view the non-secret entries:
stat -c '%a %U:%G %n' /root/paymenter-credentials.txt grep -E '^paymenter\.(url|admin\.url|admin\.user|db\.name|db\.user)=' /root/paymenter-credentials.txtTo see the passwords as well, read the whole file:
sudo cat /root/paymenter-credentials.txtThe file is mode 0600 root:root. It contains your sign-in address, the administrator email and password, and the MariaDB credentials. Copy them into your password manager, because nothing else on the VM can recover them.
You can verify that this administrator is genuinely per-VM, that the stored password is a bcrypt hash, and that no common default password works:
EMAIL="$(grep '^paymenter.admin.user=' /root/paymenter-credentials.txt | cut -d= -f2-)" PASS="$(grep '^paymenter.admin.pass=' /root/paymenter-credentials.txt | cut -d= -f2-)" HASH="$(mariadb -N -B paymenter -e "SELECT password FROM users WHERE email='${EMAIL}' LIMIT 1;")" echo "accounts on this VM: $(mariadb -N -B paymenter -e 'SELECT count(*) FROM users;')" echo "stored hash algorithm: $(echo "${HASH}" | cut -c1-4)" HH="${HASH}" PP="${PASS}" php -r 'echo "your password verifies: ", password_verify(getenv("PP"),getenv("HH")) ? "yes" : "no", "\n";' for g in password admin admin123 paymenter changeme; do HH="${HASH}" GG="$g" php -r 'echo " default \"", getenv("GG"), "\" accepted: ", password_verify(getenv("GG"),getenv("HH")) ? "YES" : "no", "\n";' doneExpected output: accounts on this VM: 1, stored hash algorithm: $2y$, your password verifies: yes, and no for every guessed default.
Step 6 - Sign in
Open https://<vm-public-ip>/login in your browser. The certificate is self-signed for this VM, so your browser shows a one-time warning. Accept it, or install your own certificate first (Step 10).
Sign in with the email address and password from Step 5.
Paymenter has a single sign-in for customers and staff. Your account holds the admin role, so the administration panel at https://<vm-public-ip>/admin opens once you are signed in. The dashboard shows live revenue, ticket and service figures, and a revenue chart over your chosen window.
Step 7 - Build your first catalogue
Paymenter sells from a catalogue of categories and products, and each product carries one or more pricing plans. In the administration panel:
- Go to Categories, then New category. Give it a name, a slug and a short description, for example Cloud Hosting.
- Go to Products, then New product. Name it and pick the category you just created. Open the Pricing tab and add a plan. Choose
Recurring, a billing period (for example every 1 month) and a price in your currency. - Save. The product appears in the catalogue immediately.
Your customers see the same products on the public storefront under Shop, with the recurring price and an order button. They can register an account and check out from there.
Currencies, coupons, taxes and configurable options live under Configuration. Payment gateways and server provisioning modules are installed from Extensions.
Step 8 - Understand where your public URL is stored
Paymenter keeps the customer-facing address in two places: the APP_URL key in /var/www/paymenter/.env, and an app_url row in the settings database table.
The database row is the authoritative one. Paymenter maps it onto Laravel's app.url config at runtime and generates every link, signed URL, asset path and invoice email from it. First boot sets both to this VM's address, and they must always agree. Compare them, and check the component versions:
echo "settings.app_url (database) = $(/usr/local/sbin/paymenter-setting app_url)"
echo ".env APP_URL = $(grep -E '^APP_URL=' /var/www/paymenter/.env | cut -d= -f2-)" grep -m1 "'version'" /var/www/paymenter/config/app.php
sudo -u www-data php /var/www/paymenter/artisan --version php -v | head -1; mariadb --version; nginx -vWhen you put Paymenter behind a real domain name, change both. Set the domain in the administration panel under Settings > General > App URL, then update .env to match:
sudo sed -i 's|^APP_URL=.*|APP_URL=https://billing.example.com|' /var/www/paymenter/.env
sudo -u www-data php /var/www/paymenter/artisan cache:clear
sudo -u www-data php /var/www/paymenter/artisan optimize:clear
sudo systemctl restart php8.3-fpm paymenter-queueIf you change only one of the two, the panel still loads, but generated links, password reset emails and payment redirects point at the old address.
Step 9 - The queue worker and the scheduler
Two background units make billing automatic. paymenter-queue.service is the Laravel queue worker. It processes jobs from Redis: sending mail, generating invoice PDFs, calling gateways and provisioning modules.
paymenter-schedule.timer fires schedule:run every minute. That run raises invoices, sends reminders and suspends overdue services on schedule.
systemctl is-active paymenter-queue.service paymenter-schedule.timer
systemctl list-timers paymenter-schedule.timer --no-pager | head -3 echo "failed jobs: $(mariadb -N -B paymenter -e 'SELECT count(*) FROM failed_jobs;')"Expected output: active twice, a timer line showing the next run within the next minute, and failed jobs: 0. Failed jobs are also listed in the administration panel under Other > Failed Jobs. The scheduler's own heartbeat is under System > Cron Statistics.
If you change .env, restart the queue worker so it picks up the new configuration. A long-running worker holds the old configuration in memory:
sudo systemctl restart paymenter-queue.serviceStep 10 - First boot, and replacing the self-signed certificate
paymenter-firstboot.service runs once, before nginx and the queue worker, and it is what makes each VM unique. It generates a certificate whose subject is this VM's own address. Inspect the service and the certificate:
systemctl show paymenter-firstboot.service -p Description,After,Before,ActiveState,Result openssl x509 -in /etc/nginx/ssl/paymenter.crt -noout -subject -issuer -dates ls -l /var/lib/sac/data/paymenter-firstboot.doneFor production, replace the self-signed certificate with one for your domain. Point a DNS A record at the VM and open 80/tcp and 443/tcp. Then issue a certificate and repoint nginx at it:
sudo apt-get update && sudo apt-get install -y certbot
sudo certbot certonly --standalone -d billing.example.com --agree-tos -m admin@example.com --non-interactive sudo sed -i 's|/etc/nginx/ssl/paymenter.crt|/etc/letsencrypt/live/billing.example.com/fullchain.pem|' /etc/nginx/sites-available/sac-paymenter
sudo sed -i 's|/etc/nginx/ssl/paymenter.key|/etc/letsencrypt/live/billing.example.com/privkey.pem|' /etc/nginx/sites-available/sac-paymenter
sudo nginx -t && sudo systemctl reload nginxThen set the App URL to https://billing.example.com in both places, as described in Step 8.
Step 11 - Mail, payments and telemetry
Out of the box, MAIL_MAILER is log. Invoice and password reset emails are written to storage/logs/laravel.log instead of being delivered.
Configure a real mailer under Settings > Mail in the administration panel, or in .env:
sudo -u www-data tee -a /var/www/paymenter/.env >/dev/null <<'ENV' MAIL_MAILER=smtp MAIL_HOST=smtp.example.com MAIL_PORT=587 MAIL_USERNAME=billing@example.com MAIL_PASSWORD=<set-at-first-boot> MAIL_ENCRYPTION=tls MAIL_FROM_ADDRESS=billing@example.com MAIL_FROM_NAME="Your Company" ENV
sudo -u www-data php /var/www/paymenter/artisan config:clear
sudo systemctl restart paymenter-queue.servicePayment gateways (Stripe, PayPal, Mollie and others) and server provisioning modules are installed from Extensions in the administration panel and configured there. No shell access is required.
Upstream telemetry is shipped disabled in this image, so nothing is reported without your consent, and no shared identifier is baked in. To support the project by sending anonymous usage data, remove the switch and let Paymenter generate an identity unique to your instance:
sudo sed -i '/^TELEMETRY_ENABLED=/d' /var/www/paymenter/.env
sudo -u www-data php /var/www/paymenter/artisan config:clearStep 12 - Backups
Everything that matters lives in the MariaDB database and in /var/www/paymenter/storage (uploaded logos, product images and invoice PDFs). Check what you would be backing up:
du -sh /var/www/paymenter/storage mariadb -N -B -e "SELECT CONCAT(table_schema,' = ',ROUND(SUM(data_length+index_length)/1024/1024,1),' MiB') FROM information_schema.tables WHERE table_schema='paymenter' GROUP BY table_schema;"Take a consistent dump plus the storage tree:
sudo mariadb-dump --single-transaction --routines --events paymenter \
| gzip > /var/backups/paymenter-$(date +%F).sql.gz
sudo tar -czf /var/backups/paymenter-storage-$(date +%F).tar.gz -C /var/www/paymenter storageAlso keep a copy of /root/paymenter-credentials.txt and /var/www/paymenter/.env. The .env file holds the APP_KEY, without which encrypted values in the database cannot be read back.
To restore onto a fresh VM, put the same APP_KEY back into .env, load the dump and unpack the storage tree. Then run php artisan cache:clear.
Step 13 - Updating Paymenter
The project publishes upgrades as new release tarballs, and you apply them with the built-in upgrade command. Take a backup first, then run:
cd /var/www/paymenter
sudo -u www-data php artisan down
sudo -u www-data php artisan app:upgrade
sudo -u www-data php artisan migrate --force sudo -u www-data php artisan cache:clear
sudo -u www-data php artisan up
sudo systemctl restart paymenter-queue.serviceThe administration panel also shows the available version under System > Updates.
Troubleshooting
The panel loads but links point at the wrong host. The two copies of the URL have drifted apart. Compare them with the commands in Step 8, set both to your real address, and clear the cache.
A browser certificate warning on every visit. This is expected until you install a certificate for your own domain. The shipped certificate is self-signed for this VM's IP address. See Step 10.
Invoices are not being raised, or emails never arrive. Check the two background units first: systemctl is-active paymenter-queue paymenter-schedule.timer. Then look at Other > Failed Jobs and System > Cron Statistics in the panel, and at /var/www/paymenter/storage/logs/laravel.log. If MAIL_MAILER is still log, emails are written to that log file instead of being sent. See Step 11.
A 500 error after editing .env. Laravel caches configuration. Clear it and restart the worker:
sudo -u www-data php /var/www/paymenter/artisan optimize:clear
sudo systemctl restart php8.3-fpm paymenter-queueTo check the logs, run:
journalctl -u paymenter-queue.service --no-pager -n 15 tail -n 15 /var/www/paymenter/storage/logs/laravel.log 2>/dev/null || echo "no application log yet" tail -n 15 /var/log/nginx/error.logSupport
This image is built and maintained by SecureAnyCloud with 24/7 support. Paymenter itself is an open source project. Its documentation is at paymenter.org/docs, and its source is at github.com/Paymenter/Paymenter under the MIT license.
Deploy on Azure
Launch Paymenter 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