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:
- hostname DNS — the hostname does not resolve to this server yet; the certificate cannot be issued.
- certificate … pending_dns / failed — a client's domain does not point here (every A/AAAA record must match the server) or Let's Encrypt validation failed; the hourly timer retries with increasing backoff (1 h → 24 h).
- account … configuration not applied — a service
refused the rendered configuration; the account keeps its previous
working files. Run
panelctl rebuild --account <name>after fixing the cause shown inpanelctl account show. - disk — below 15 % free is a warning, below 5 % a failure.
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. Run:
panelctl dns setup powerdns --ns ns1.example.net --ns ns2.example.net
This 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.
Zones that already exist at the provider. When a
client adds a domain whose zone is already in your Cloudflare account or
cPanel cluster, panelOwl does not touch it until it is claimed:
that happens by itself when the zone's apex address already points at
this server, otherwise the zone shows as unclaimed on
the DNS page with a claim this zone button. Check that the
domain really belongs to that client before claiming; claiming replaces
the zone's apex, www, MX and mail TXT records with this
server's.
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).
Licence
panelOwl is licensed per server at $39.95 per month with no limit on
accounts; buy or renew at panelowl.com with a Stripe or
PayPal subscription. The key (POWL1.…) arrives by email;
install it under Licence in the admin UI or with
panelctl license set KEY
(install.sh --license KEY on a fresh install). It is
verified on the server itself with the publisher's public key, so the
panel does not depend on panelowl.com being reachable.
A paid month is a month of service: the key carries its expiry. Once
a day, next to the update check, the server asks panelowl.com for a
renewed key for its licence id and installs it when there is one
(panelctl license refresh does the same at once); if the
service cannot be reached nothing changes. After the expiry there is a
7-day grace period with a warning; after that, creating accounts
and domains is refused until a valid key is installed. Nothing
else is ever affected: hosted sites, mail, client panels, backups, DNS
and updates keep working. panelctl license show and
panelctl doctor report the state.
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 an account with:
panelctl account suspend NAME
(see the reference for what suspension 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).
Moving accounts from cPanel
An account moves from cPanel to panelOwl in one step, with its files, mail, databases and all their passwords. There are two ways to do it. Both end with the same import; pick whichever fits.
Way 1: fetch the account from the old server (one command)
This needs root SSH access from the panelOwl server to the cPanel server. Run on the panelOwl server, as root:
panelctl import remote root@old.server.example --list
This prints the cPanel accounts (user name and main domain). Then, for the account you want:
panelctl import remote root@old.server.example kvibes
panelOwl packages the account on the old server
(/scripts/pkgacct), copies the archive over, shows you the
plan and asks for confirmation, then imports it and follows the
progress. The archive is removed from the old server after the copy and
from this server after a successful import.
Options: --port 2222 and
--identity /root/.ssh/migrate for the SSH connection,
--email owner@example.com when the cPanel account has no
contact address, --yes to skip the confirmation,
--keep to keep the archive, and several user names at
once:
panelctl import remote old.server.example --port 2222 --identity /root/.ssh/migrate kvibes acme shop --yes
SSH asks about the host key and the password as it normally would; an SSH key avoids retyping the password for every account.
Way 2: bring the backup file yourself
In cPanel, open Backup → Download a Full Account Backup and download the file. It is named like
backup-10.2.2026_15-36-05_kvibes.tar.gz(orcpmove-kvibes.tar.gzwhen made with/scripts/pkgacct).Copy the file to the panelOwl server's imports directory. From the machine that holds the file:
scp backup-10.2.2026_15-36-05_kvibes.tar.gz root@panel.example.com:/var/lib/minipanel/imports/Import it, either in the admin UI (Accounts → Import from cPanel, pick the file, read the plan, click Import this account) or on the panelOwl server as root:
panelctl import plan backup-10.2.2026_15-36-05_kvibes.tar.gzThis only reads the archive and shows what would happen: account name, domains, mailboxes, databases, and any problems (an existing account or domain with the same name, a missing contact email). Nothing is changed yet. When the plan looks right:
panelctl import cpmove backup-10.2.2026_15-36-05_kvibes.tar.gzAdd
--email owner@example.comif the plan asked for one, or--account newnameto use another account name.
What comes over
- The account, under the cPanel user name unless you pass
--account, with its disk quota. - All domains (main, addon, sub, parked) with their document roots.
- The home directory, owned by the new user. cPanel's own directories
(
.cpanel,.softaculous, statistics caches) are left out. - Mailboxes with their passwords and quotas, every folder and message, forwarders and catch-alls.
- Databases with their contents, their users and their passwords, so WordPress and other applications keep working without edits.
- Cron jobs that fit the server's rules (jobs running more often than the minimum interval are listed, not imported).
- When this server hosts the DNS: the custom records of the zones, with the old server's address rewritten to this one's.
- The client's cPanel login password works for the panel and for SFTP.
What does not come over
Autoresponders and mail filters, FTP sub-accounts, AutoSSL certificates (Let's Encrypt issues new ones once the domains resolve here) and DKIM keys (the panel signs with its own key; if the zone stays at another provider, update the DKIM record shown on the domain's DNS page). Everything that was skipped is listed at the end of the import.
Order of a migration
Import while the domains still point at cPanel, check the site on the new server (a hosts-file entry on your workstation works), then change the DNS. Mail delivered to the old server after the backup was made is not copied, so take the backup as late as possible.
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.