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.
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.