panelOwl docs 1.15.0

Operations

Daily health

panelctl doctor
systemctl status minipaneld minipanel-web
journalctl -u minipaneld -u minipanel-web --since today

doctor reports ok, WARN or FAIL per check. Warnings you will commonly see:

Notifications

Every hour minipanel-notify.timer runs the health checks and emails the admin address (or notify_email) when the set of warnings and failures changes — a new problem, a failed nightly backup, a newer release available, a certificate that stopped renewing — and once more with "all checks passed" when they are resolved. While problems persist, a reminder goes out weekly. Change the recipient with panelctl config set notify_email ADDRESS, switch off with panelctl config set notify off, try it with panelctl notify --test. The mail leaves through the local Postfix as minipanel@<hostname>, so the PTR and SPF for the hostname matter for it to reach external inboxes.

Protecting your own addresses from fail2ban

fail2ban bans addresses after repeated failed logins (SSH, mail, panel, admin UI, webmail, phpMyAdmin). Behind NAT, or when several people share an office address, one mistyped password can lock everyone out. List the networks that must never be banned:

panelctl config set fail2ban_ignore 10.0.1.0/24 203.0.113.7

The list is written to /etc/fail2ban/jail.d/00-minipanel-ignore.local (checked with fail2ban-client --test, then reloaded). Unban an address that is already banned with fail2ban-client unban ADDRESS.

Where everything lives

Path Contents
/etc/minipanel/config.toml server settings and default limits (edit, then systemctl restart minipaneld)
/etc/minipanel/ssl/ bootstrap self-signed certificate
/var/lib/minipanel/state.db the state database (SQLite): accounts, domains, mailboxes, databases, cron jobs, certificates, 2FA
/var/lib/minipanel/web/ the web app's session database and upload spool
/var/lib/minipanel/acme/ ACME challenge webroot
/var/log/minipanel/audit.log audit log (JSON lines, rotated by logrotate)
/var/log/apache2/minipanel/<account>/ per-domain access and error logs (clients see the last 200 lines in the panel)
/home/<account>/ client files: public_html/ (primary docroot), <domain>/ (other docroots), mail/, tmp/, .ssh/
/etc/apache2/minipanel/, /etc/php/*/fpm/pool.d/minipanel-*.conf, /etc/postfix/minipanel/, /etc/dovecot/minipanel-*, /etc/ssh/sshd_config.d/50-minipanel.conf, /var/spool/cron/crontabs/<account> managed files — never edit by hand, panelctl rebuild overwrites them
/var/lib/rspamd/dkim/<domain>.mp1.key DKIM private keys
/etc/letsencrypt/live/ certificates (certbot)
/usr/local/lib/minipanel/ binaries and the VERSION marker

Systemd units: minipaneld.service (helper, root), minipanel-web.service (panel, user minipanel, 127.0.0.1:8081 behind Apache on 8443), minipanel-ssl.timer (hourly certificate work), minipanel-usage.timer (daily disk usage).

Certificates

panelctl ssl status
panelctl ssl retry example.com

A certificate is requested automatically when a domain is added and every hour afterwards while it is pending. Before each attempt the helper checks with public resolvers that the domain — and www. if enabled — points at this server; names that do not are simply left out and added later (--expand) once DNS is fixed. Renewal is certbot's own timer; its deploy hook reloads Apache, and Postfix/Dovecot for the hostname certificate.

The panel, mail services and SFTP always present the hostname certificate; per-domain mail certificates are not part of v1, which is why clients configure panel.hoster.example as their mail and SFTP server.

DNS

Without a DNS provider the panel only shows clients which records to create. With one (DNS in the admin UI or panelctl dns setup), panelOwl publishes every hosted domain's records itself and clients get a zone editor. The three providers behave the same from the panel's point of view; what differs is where the zones live.

PowerDNS on this server. panelctl dns setup powerdns --ns ns1.example.net --ns ns2.example.net installs pdns-server with the sqlite backend, writes /etc/powerdns/pdns.d/minipanel.conf (API on 127.0.0.1:8053, key in /etc/minipanel/dns-secret), opens 53/tcp+udp in ufw and starts the service. It listens on the server's configured addresses plus 127.0.0.1 (not the wildcard, which would clash with systemd-resolved's stub). Point the ns1/ns2 names at this server at your registrar (glue records if they are under a domain you host here). For a second nameserver on another machine, pass --secondary <ip>: zones are created as primaries, the secondary may transfer them (AXFR) and gets NOTIFY; configure it as a secondary of this server's address. Re-run the setup command to change nameservers or secondaries; the API key is kept. dig +short @127.0.0.1 example.com A shows what the server answers.

Cloudflare. Create an API token (My Profile → API Tokens) with Zone:Read and DNS:Edit on the zones panelOwl should manage. To let panelOwl create zones for new domains, add Zone:Edit on the account and give the account ID (Overview page of any zone). Otherwise add the zone at Cloudflare first; the domain shows pending until it exists. Records are published unproxied (grey cloud); clients can unlock a record and manage the proxy setting at Cloudflare if they want it.

cPanel DNSOnly / WHM. In WHM on a cluster member, Manage API Tokens → create a token. Zones panelOwl creates or edits on that member replicate to the cluster when the member synchronises changes (DNS Cluster → Synchronize changes). A dedicated DNSOnly member (free licence) is the safest home for the token: a compromised token can then only touch the zones on that member, not your other servers' zones. panelOwl uses the classic zone functions (dumpzone, addzonerecord, removezonerecord, adddns, killdns), available on every supported WHM version. Tick skip the certificate check only for a member with a self-signed certificate on 2087.

How zones are managed. Each hosted domain belongs to one zone: the zone of its hosted parent if the same account hosts one (shop.example.com lives in example.com), else its own. panelOwl owns record sets (a name plus a type), never whole zones: it rewrites the sets it manages and leaves everything else alone, so records created directly at the provider are shown to the client as external and survive. Automatic sets (addresses, www, MX, SPF, DKIM, DMARC, autoconfig/autodiscover, SRV) are regenerated on every publish, so they follow IP assignments and DKIM keys; a client can unlock one to take it over. A zone panelOwl created is deleted with its last domain and with the account; a pre-existing zone only loses the automatic sets.

Publishing and failures. Records are published right after a domain is added, changed, removed or moved to another address, and minipanel-dns.timer re-checks every five minutes: zones that failed are retried with a back-off (5 minutes doubling to an hour) and healthy zones are re-verified hourly, so a record deleted at the provider by mistake comes back. A provider outage never blocks adding a domain; the zone shows error with the message on the DNS page and in panelctl dns zones, and panelctl doctor warns. panelctl dns sync publishes everything now. Tokens are never shown again after saving and never appear in logs; the audit log records every record edit with the account (or the admin acting on it).

Disk quotas

Per-account disk limits are enforced with Linux user quotas on the filesystem that holds /home. Everything the account's Unix user owns counts — website files, mail, temporary files; databases do not (the panel shows their sizes separately).

Enable enforcement once (ext4 or XFS):

panelctl quota status
panelctl quota enable

On ext4 this adds usrjquota=aquota.user,jqfmt=vfsv1 to the /etc/fstab entry of that filesystem (checked with findmnt --verify; the original is kept as /etc/fstab.minipanel-backup and restored if the remount fails), remounts it, creates the quota files and switches quotas on. The quota_v2 kernel module is loaded through /etc/modules-load.d/minipanel-quota.conf (so it also loads at boot); if the kernel lacks it, linux-modules-extra-<kernel> is installed. On XFS the uquota option is added and a reboot activates it (then run panelctl rebuild). In containers quotas cannot be enabled; limits are stored but not enforced.

Set limits:

panelctl account quota acme 10G          # or 500M, 1T, unlimited
panelctl account create shop --domain shop.example --email o@shop.example --quota 5G

or on the account page of the admin UI. New accounts get [limits] disk_quota_mb from config.toml (0 = unlimited). A full account cannot write files; incoming mail for it is deferred by Postfix and retried until space is freed. Clients see "X of Y" with a bar on their dashboard and a warning from 90%; doctor (and so the notification mail) lists accounts at 90% or more.

Suspending, limits and quotas

Suspend with panelctl account suspend <name> (see the reference for what it does). Per-account limits (domains, mailboxes, total mail quota, databases, cron jobs, PHP-FPM workers) take their defaults from [limits] in config.toml:

[limits]
domains = 10
mailboxes = 50
mail_quota_mb = 10240
databases = 10
cron_jobs = 20
fpm_max_children = 5

Disk usage is live when quotas are enabled (otherwise measured daily) and shown to the client; see Disk quotas.

Audit log

Every state change made through the panel or panelctl is recorded with who did it (caller uid and level, client IP for panel actions, a hashed session id), the account, the action, a short target such as domain:example.com, and the result. Failed and rejected requests are recorded too.

panelctl audit --account acme --since 7d
panelctl audit --limit 20

Login failures (password or second factor) are additionally logged to the journal of minipanel-web with the client IP; the fail2ban minipanel-panel jail bans after repeated failures, on top of the panel's own per-IP and per-account lockouts (5 failures in 15 minutes).

Backups

A full backup runs every night at 03:00 (minipanel-backup.timer) into /var/backups/minipanel/ (root-only). Run one by hand, see what exists, or back up a single account:

panelctl backup run
panelctl backup list
panelctl backup run --account acme

Each archive is a plain .tar.gz: a manifest, a consistent snapshot of the state database, config.toml, the audit log, /etc/letsencrypt, DKIM keys, a mariadb-dump per database, the database users with their password hashes, and every account home including mail. Retention keeps the newest 7 daily archives plus one per week for 4 further weeks ([backup] keep_daily / keep_weekly in config.toml); account archives are never pruned. panelctl doctor warns when the newest full backup is older than two days.

Copy the directory offsite — panelOwl does not do that for you. A nightly rsync -a /var/backups/minipanel/ backup-host:minipanel/ or an rclone sync to object storage from a cron job is enough; archives are not encrypted, so encrypt at that step if the destination requires it.

What clients can do themselves

The panel's Backups page shows each client the archives that contain their account. They can create an on-demand backup of their own account (one per hour, the three newest kept), download their slice of any backup (a derived archive with only their home, databases and database users — never the server archive), and restore their home and databases from any backup after entering their password. All of it is audited (backup.account.*).

Restoring one account

Puts an account's files (including mail) and databases back as they were in the archive. The account must exist; its settings (domains, mailboxes, keys, cron) stay as they are now.

panelctl backup restore minipanel-full-20260928T030000Z.tar.gz --account acme --confirm

The home is replaced wholesale (files added since the backup are gone), ownership is set to the account, document-root permissions are re-applied, and each database is re-created from its dump.

Restoring a whole server

On a fresh Ubuntu 24.04 server: run the installer with the same hostname, copy the archive into /var/backups/minipanel/, then:

panelctl backup restore minipanel-full-20260928T030000Z.tar.gz --confirm

This restores every home, the certificates, DKIM keys, database users (passwords included) and databases, then restarts minipaneld — which adopts the state database from the archive — and runs a full rebuild: Unix users are recreated with their original uids, and every vhost, pool, mail map, crontab, SSH key file and database grant is regenerated. Clients can log in with their old passwords straight away. config.toml is not overwritten (the new server's hostname and addresses stay); check panelctl doctor and panelctl ssl status afterwards.

Webmail and phpMyAdmin

Both are installed by the installer from Ubuntu's archive (Roundcube 1.6, phpMyAdmin 5.2), so their security updates come with unattended-upgrades, and served only on the server hostname: https://<hostname>/webmail/ and /phpmyadmin/. Switch either off under [apps] in /etc/minipanel/config.toml (webmail = false, phpmyadmin = false) and re-run the installer (install.sh --yes) — that removes the aliases, the pool and the jails.

What Where
PHP pool /etc/php/<default>/fpm/pool.d/00-minipanel-apps.conf, user minipanel-apps, socket /run/php/minipanel-apps.sock
Roundcube config /etc/minipanel-apps/roundcube/config.inc.php (managed), secrets.inc.php (generated once: des_key, database password)
Roundcube database minipanel_roundcube (user minipanel_rc), included in full backups
Roundcube logs /var/lib/minipanel/apps/roundcube/logs/ (errors.log holds failed logins for fail2ban)
phpMyAdmin config /etc/phpmyadmin/conf.d/minipanel.php (managed), /etc/minipanel-apps/phpmyadmin-secret.inc.php
PHP errors /var/lib/minipanel/apps/logs/php-error.log
fail2ban jails roundcube-auth and phpmyadmin-syslog in /etc/fail2ban/jail.d/minipanel-apps.local

The packages' own files in /etc/roundcube and /etc/phpmyadmin are left untouched. The installer also installs a tiny local package, minipanel-php-provides, and pins libapache2-mod-php* to never install: without them, Ubuntu's app packages would pull a second PHP version and mod_php from the PHP PPA.

Troubleshooting

Symptom Where to look
Panel shows "Panel unavailable" systemctl status minipaneld; journalctl -u minipaneld
A client's site returns 500 their error log in /var/log/apache2/minipanel/<account>/; usually php_value lines in .htaccess (unsupported under PHP-FPM — tell them to use .user.ini)
Certificate stuck in pending_dns panelctl --json ssl status shows the exact resolver result; a stray AAAA record is the classic cause
Mail not delivered journalctl -u postfix -u dovecot, /var/log/rspamd/rspamd.log; doveadm quota get -u user@domain for quota
Outbound mail lands in spam check the DKIM/SPF/DMARC records shown on the client's DNS page, and the PTR record
Client locked out of SSH panelctl account show (SSH mode and password-login flag), journalctl -u ssh, fail2ban (fail2ban-client status sshd)
Webmail "Connection to storage server failed" systemctl status dovecot; the webmail connects to 127.0.0.1:993; tail /var/lib/minipanel/apps/roundcube/logs/errors.log
phpMyAdmin login refused for a client the database user needs a grant on the Databases page; root logins are always refused
Account apply_state = failed panelctl account show <name> prints the checker's message; fix, then panelctl rebuild --account <name>
Something looks off after editing a managed file by hand panelctl rebuild — managed files are always regenerated from the state database

Log files: journalctl -u minipaneld (helper, includes every command it ran and any checker output), journalctl -u minipanel-web (panel, logins), /var/log/minipanel/audit.log.