Auto-discovers every public_html across your domains and sub-domains, archives each one into
its own .zip named after the domain, with integrity checksums, encryption, retention,
cloud upload (S3 · Dropbox · Google Drive) and scheduled jobs.
It detects what's already on your system so you click instead of type.
Why another backup tool? Because the usual answer — a
zip -rloop over/home/*/public_htmlwith the cloud key in the script and afind … -exec rm -rffor cleanup — runs as root, leaks credentials intops, silently ships a half-written archive when a site fails, and can wipe the wrong directory. This module fixes all of that and gives you a UI on top: it discovers your web-roots for you, builds every command from constant flags plus individually escaped values, never puts a secret on a command line, and only ever deletes its own timestamped backup sets.
Live under Webmin / Virtualmin? This fits right in — under Servers → File Backup.
- 🧲 Finds your sites for you — discovers every
public_htmlacross domains and sub-domains (Virtualmin, cPanel and Plesk layouts), and usesvirtualmin list-domainsfor exact domain names when available. - 🗜️ One
.zipper domain — each web-root becomesdomain.zip/sub.domain.zip, stored relative to the web-root for a clean restore. - ☁️ Cloud built in — upload the whole set to Amazon S3 (and S3-compatible), Dropbox, or Google Drive via
rclone, with safe copy or mirroring sync. Picks up your existingrcloneremotes. - 🔒 Secrets never touch argv — GnuPG passphrases via
--passphrase-fd; rclone keys/tokens written to a0600config we own, never wherepscan see them. - 🔐 Safe by construction — SHA-256 sums, optional GnuPG encryption at rest, and retention that can only delete its own backup sets — no
rm -rf, nofind -exec. - ⏰ Scheduled — cron jobs created through Webmin, with an "all web-roots" mode so newly added domains are backed up automatically.
- 👥 Granular ACLs & audited — per-user permissions, every change POST-only and written to the Webmin Actions Log (never logging a secret).
Drop your own PNGs into
docs/screenshots/— they'll render here.
| Dashboard | Discovered web-roots | Back up & schedule |
|---|---|---|
![]() |
![]() |
![]() |
|
Discovery — detected, not typed
Backups
|
Retention
Cloud upload (rclone)
Browse · verify · restore
Scheduling
Access control
|
| The old way | The problem | This module |
|---|---|---|
for d in /home/*/public_html; do zip … |
Misses sub-domains & non-standard layouts | Discovers every public_html, names each by its real domain |
RCLONE_KEY="…" in the script |
Secret in plaintext and in ps |
Keys/tokens in a 0600 config we own — never on argv |
zip -r out.zip $d (ignore errors) |
A failed archive still "succeeds" | Archive → verify exit + non-empty → checksum; empty sites skipped cleanly |
find $OUT -mtime +30 -exec rm -rf {} \; |
Can wipe the wrong directory | Deletes only timestamp-named sets, never follows symlinks |
rclone sync to one fixed remote |
sync can delete remote data |
Per-job copy (default) or sync, across S3 / Dropbox / Drive |
This is the whole point of the module.
| Guarantee | How |
|---|---|
| No secret on a command line | GnuPG via --passphrase-fd; rclone keys/tokens written to a 0600 config we own — never on argv. |
| No shell injection | Web-root discovery uses Perl's File::Glob (no shell); every value from a form, the config or a schedule is single-quote-escaped (sq()); only constant flags stay literal. |
| Targets are allow-listed | A backup or restore only ever touches a public_html discovery currently reports, or a path the admin explicitly configured — never an arbitrary path. Restore refuses unknown targets and symlinked web-roots. |
| Destruction stays in its lane | Retention/delete only ever touches timestamp-named child directories of the backup folder; lstat rejects symlinks; no rm -rf, no find -exec. |
| POST-only, ACL-gated, audited | All changes go through one dispatcher (act.cgi), reached only by POST, permission-checked, and logged with webmin_log — secrets are never logged or reflected into a page. |
| Integrity | SHA-256 for every archive; optional GnuPG AES-256 encryption at rest. |
- A host running Webmin (or Virtualmin)
zip(to create backups) andunzip(to restore)gpgfor optional encryptionrclonefor cloud upload (optional)- Perl
JSON::PP(ships with Perl 5.14+)
- Download
filebackup.wbm.gzfrom the latest release. - In Webmin go to Webmin Configuration → Webmin Modules → From uploaded file, choose the package, and click Install Module.
- Open it under Servers → File Backup.
Build it yourself from a checkout:
tar -czf filebackup.wbm.gz filebackup # the top-level dir in the archive must be "filebackup"- Open File Backup — the dashboard lists every
public_htmlit discovered. - Back up now — tick the domains/sub-domains to archive (or all of them), choose the destination (Browse), compression, retention, encryption, and a cloud remote.
- Cloud remotes → Detected remotes → Import to reuse an existing
rclonesetup, or add an S3 / Dropbox / Drive remote. - Schedules — add a cron job; leave it on "all web-roots" and new domains are picked up automatically.
- Browse a set to verify checksums, download a
.zip, or Restore it.
<backup dir>/
2026-06-23_031500_nightly/ # timestamp[_label]
example.com.zip # primary domain
blog.example.com.zip # sub-domain
shop.example.com.zip # sub-domain
SHA256SUMS
manifest.json
- Amazon S3 / S3-compatible — fully non-interactive: access key, secret, region (endpoint for non-AWS).
- Dropbox / Google Drive — run
rclone authorize "dropbox"(or"drive") on any machine with a browser and paste the JSON token. - Import — any remote already in root's
~/.config/rclone/rclone.confis offered for one-click import (keys/tokens come along).
Keys and tokens are written only to the module's own 0600 config — never passed to rclone on
the command line.
Under Webmin Users → user → File Backup, grant or deny each capability independently: view · backup · restore · remotes · schedule · delete. Default is full access (you're the admin); tighten per user as needed.
| File | Purpose |
|---|---|
filebackup-lib.pl |
Hardened core: discovery, shell quoting, validation, zip/unzip, encryption, checksums, retention, rclone, cron |
index.cgi |
Dashboard: status, discovered web-roots, quick backup |
backup.cgi |
Choose web-roots / path / compression / retention / cloud and run |
remotes.cgi |
Cloud remotes: S3 / Dropbox / Drive + import from system rclone |
browse.cgi · restore.cgi |
Browse / verify / download / delete · restore a domain into its web-root |
schedule.cgi · filebackup-run.pl |
Cron scheduling · unattended runner |
act.cgi |
The single POST action dispatcher (ACL-gated, audited) |
acl_security.pl · defaultacl · install_check.pl |
Per-user access control · auto-detection |
Issues and pull requests welcome. Every .cgi/.pl file is validated with perl -c in CI;
changes to command construction must keep the sq() + allow-list + secrets-off-argv discipline.
Copyright © 2026 MrZaKaRiA.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. It is distributed without any warranty — see the license for details.


