chandan@dubey:~/blog/backupcroncat product-overview.md

product writeup · june 2026

BackupCron: a self-hosted backup manager for MySQL, files, and remote servers.

I run production servers for a living, and every single one of them needs backups that actually work. BackupCron is the self-hosted manager I built to replace hand-written cron scripts, scattered SSH checks, and the sinking feeling of finding out a backup broke three weeks too late. It schedules MySQL dumps and file archives from local and remote servers, ships them to local disk or Wasabi cloud storage, keeps the last N copies, emails you on failure, and gives you one dashboard to see all of it at once.

what Self-hosted backup manager

Scheduled MySQL dumps and file/directory archives from local and remote SSH servers, with retention, cloud offload, and email alerts.

who Founders, sysadmins, small teams

Anyone running 1–50 Linux servers with MySQL/MariaDB who is tired of per-server cron scripts and per-GB SaaS backup pricing.

how One box, one dashboard

Deploy on a single Lightsail/VPS. SSH into your targets, run dumps/archives, push to Wasabi, alert on failure. You own the credentials.

The problem BackupCron solves

If you run production servers, you already know the backup story. It is rarely "we don't have backups". It is almost always one of these:

  • Silent cron failure. A mysqldump cron job broke when the DB user's password was rotated. Nobody noticed for three weeks. The "backup" folder had 21 empty .sql files.
  • Per-server SSH sprawl. You have 6 servers. To check backups you SSH into each, tail a log, ls -lh a folder. It takes 20 minutes and you only do it when something breaks.
  • SaaS pricing that scales with data. You looked at managed backup SaaS. The bill grows with every GB. You have a 40GB MySQL db and 200GB of uploads. The quote was more than your application server.
  • No off-site copy. Backups live on the same box as the database. If the box dies, the backups die with it. You keep meaning to set up rsync to S3. You haven't.
  • Zero alerts. Backups run, or they don't. The first time you find out they stopped is at restore time — the worst possible time.
  • Handing SSH keys to a third party. The SaaS option wants root SSH access to your production box. Your security review says no.

BackupCron is the answer to all six. It is a single self-hosted service you control, a single dashboard you check, and a single set of alerts that fire the moment a job fails. Your credentials never leave your infrastructure.

[ image: backupcron-dashboard.png ] Dashboard overview — KPI cards (servers, jobs, success rate, storage), backup size history area chart, status donut, 7-day volume bars, recent activity table.
BackupCron dashboard. Everything you need to know about every backup in one screen.

What BackupCron does

BackupCron is a web application you host on your own Linux server (a $5/mo Lightsail or small VPS is plenty). Once it is up, you point it at every server you want to back up and define jobs. A job is a single backup definition: what to back up, from which server, on what schedule, where to store it, and how many copies to keep.

Two backup types, one scheduler

  • Database backups (MySQL / MariaDB). Runs mysqldump --single-transaction --quick on the target server over SSH and streams the dump straight back to the BackupCron box. --single-transaction gives you a consistent snapshot without locking InnoDB tables — your app keeps serving traffic while the dump runs.
  • File / directory backups. Archives any path on the target server (/var/www, /etc, /home, uploads folder, config dirs) using tar or zip. BackupCron auto-detects which is available on the remote box and uses zip -1 for fast streaming when it can.

Schedules that match your real life

Every job runs on a standard cron expression. The UI ships with the common presets — daily at midnight, every 12 hours, hourly, weekly Sunday, monthly 1st — and a custom field for anything else (*/15 * * * *, 0 2 * * 1-5, etc.). You are not locked into a vendor's "we run it whenever" model. You own the schedule.

[ image: backupcron-jobs-list.png ] Backup Jobs page — table of jobs with name, source, cron schedule, last run timestamp + status pill, and action buttons (Run now, History, Duplicate, Edit, Delete).
The jobs list. Run any job on demand, view full history, duplicate, edit, or stop a running backup in one click.

Two storage destinations, your choice per job

  • Local storage. Saves the backup to the BackupCron server's disk — the default path or any custom absolute path (including mounted NAS / external volumes). Great for fast restores and for teams that already have a NAS or large attached volume.
  • Wasabi cloud storage. Streams the same backup to a Wasabi bucket (S3-compatible, flat-rate, no egress fees). You pick the bucket and a folder prefix per job. Optionally, BackupCron deletes the local copy after a successful upload — so you get off-site durability without doubling your disk footprint.

Why Wasabi specifically? Because it is S3-compatible, ~1/5 the price of AWS S3, and does not charge for egress. For backups, that combination is hard to beat. BackupCron uses the official AWS SDK with forcePathStyle: true and regional Wasabi endpoints, so it works with any Wasabi region out of the box.

[ image: backupcron-wasabi-config.png ] Create Job form with destination type set to "Wasabi Cloud Storage", bucket dropdown (auto-populated from your Wasabi account), folder prefix picker, and the "delete local after upload" checkbox.
Per-job Wasabi destination. Buckets and folders are auto-listed from your Wasabi account — no manual typing.

Retention that actually cleans up

Every job has a retention count (default: keep last 7). After each successful run, BackupCron prunes older successful backups — both the local files and the matching Wasabi objects. So your storage does not quietly grow forever, and your cloud bill stays predictable. Pruned logs are marked in the history table so you can audit what was removed.

Email alerts the moment a job fails

Configure SMTP once in Settings. BackupCron sends an HTML email on every failed run with the job name, timestamp, and the actual error message (e.g. "SSH connection failed: All configured authentication methods failed"). Successful runs are silent unless you want them noisy. The point is: you find out about a broken backup at 2:07am, not 2 weeks later at restore time.

[ image: backupcron-failure-email.png ] Sample failure alert email — red header, job name, "FAILED" status, timestamp, error message body, BackupCron footer.
A real failure alert. The error message is the actual exception from the backup run — not a generic "something went wrong".

How it works under the hood (public-safe)

This is a customer writeup, not a source dump, so I am keeping the architecture description at the level you need to evaluate it — not the level you would need to clone it. The shape is what matters.

  • Runtime: Node.js + Fastify backend, React + Vite frontend served by the backend in production. One process, one port, behind Nginx.
  • Scheduler: node-cron with an in-memory Map<jobId, task>. Jobs are loaded from MySQL on boot and re-registered on edit.
  • Remote execution: SSH via the ssh2 library. Password or private key auth, both supported. Streams the dump/archive directly back over the SSH channel — no temp file on the target server.
  • Database: MySQL/MariaDB for the app's own state (servers, jobs, logs, settings, users) via Prisma v7 with the MariaDB driver adapter.
  • Cloud: Wasabi via the AWS SDK v3 (@aws-sdk/client-s3 + lib-storage for multipart uploads). Regional endpoint resolution is automatic.
  • Auth: JWT on every API route. Optional signed download URLs for backup files stored in Wasabi (15-minute expiry).
  • Encryption at rest: AES-256-GCM. Server passwords, private keys, per-job DB passwords, Wasabi secret keys, and SMTP passwords are all encrypted in the database. The encryption key lives in ENCRYPTION_KEY on the server, never in the DB.

Production-grade details that took real failures to learn

The boring parts of BackupCron are the ones I am most proud of, because each one came from a real incident:

  • Hot, non-locking MySQL dumps. mysqldump --single-transaction --quick on InnoDB gives you a consistent snapshot without table locks. Your app does not stall while the dump runs.
  • Load-aware execution. Every dump/archive runs under nice -n 10 ionice -c 2 -n 7 (DB) or nice -n 19 (files). Backups should never be the reason your app gets slow at 2am.
  • Pre-flight disk space check. Before a remote file backup, BackupCron checks the source size over SSH and compares it to free space on the destination. If there is not 1.1x headroom, the job fails before it starts writing, with a clear message. No more 0-byte half-written archives.
  • Pre-flight zip availability check. Some minimal servers do not have zip installed. BackupCron detects this and falls back to tar | gzip automatically.
  • Concurrent-run queueing. If a job is still running when its next cron tick fires, the new run is queued in-process and in the database — not dropped, not double-run. When the active run finishes, the queued one starts.
  • Stale-run cleanup on restart. If the BackupCron box itself restarts mid-backup, on boot it marks every running / queued log as failed with the reason "Server restarted while backup was running". No zombie jobs pretending to be alive.
  • Manual stop that actually stops. Clicking Stop kills the local spawn process or ends the SSH session, cancels queued runs, deletes the partial file, and marks the log cancelled. The job does not linger in a half-state.
  • Plaintext credential auto-encryption. On startup, BackupCron scans for any plaintext credentials in the DB and encrypts them in place. Idempotent — it will not double-encrypt. Useful for migrating older installs.

The dashboard: one screen, every backup

This is the part most cron-script setups are missing. You should not have to write a Grafana panel to answer "did my backups run last night". BackupCron's dashboard gives you, on a single page:

  • Total servers and active jobs counts.
  • Success rate — color-coded green/amber/red so a glance tells you if you are above 90%, 70%, or below.
  • Storage volume — total bytes across all successful backups, formatted to MB/GB.
  • Backup size history — an area chart of the last 8 successful runs so you can spot a database suddenly shrinking (dropped table?) or growing (log table not being pruned?).
  • Execution status donut — success / failed / cancelled breakdown at a glance.
  • Weekly activity volume — stacked bars of success vs failed runs for the last 7 days.
  • Recent activity table — last 10 runs with status, job name, start time, duration, and size.

Every chart is rendered as inline SVG — no chart library bloat, no third-party tracking, fast first paint. Click any job's history icon to see the full run log: status, duration, size, error message, and a download button for the backup file itself.

[ image: backupcron-history-modal.png ] Job history modal — table of past runs with status pill, start time, duration, size, message (truncated with tooltip), and a download icon on successful rows.
Per-job history. Download any successful backup straight from the browser — local file or signed Wasabi URL, handled automatically.

Self-hosted vs. managed backup SaaS

I am not going to pretend self-hosting is always the right answer. Managed backup SaaS exists for a reason. But for the kind of customer BackupCron is built for — technical founders, sysadmins, small infra teams — the math usually comes out in favor of self-hosting. Here is the honest comparison:

BackupCron (self-hosted)
Managed backup SaaS
Monthly cost at 250GB stored
~$6 (VPS) + ~$1.25 (Wasabi) = ~$7
Typically $25–$75, scales with GB
SSH access to your production servers
You keep it. BackupCron SSHes from your box.
Vendor needs inbound SSH or an agent
Credentials stored where?
Your DB, AES-256-GCM encrypted, key on your box
Vendor's vault
Custom cron schedules
Any standard cron expression
Usually fixed tiers (daily / weekly)
Failure alerts with real error message
Yes, via your own SMTP
Yes, usually
Off-site / cloud copy
Wasabi (S3-compatible, no egress fees)
Vendor's storage (often S3 under the hood)
Maintenance burden
You patch the box. ~30 min/month.
Zero — vendor handles it
Data sovereignty
Full. Your servers, your storage, your borders.
Depends on vendor region selection

The short version: if "zero maintenance" is worth $30–$60/month to you, use a SaaS. If you already run servers, want to keep your SSH keys, and would rather pay ~$7 flat than $50-and-growing, BackupCron is the better fit.

Security: your credentials never leave your box

This is the part I care about most, because the worst possible backup tool is one that becomes the attack surface. BackupCron is built so that even if the dashboard database is stolen, the attacker gets ciphertext, not your production SSH passwords.

  • AES-256-GCM at rest. Server passwords, private keys, per-job DB passwords, Wasabi secret keys, and SMTP passwords are encrypted in the Setting / Server / BackupJob tables. The ENCRYPTION_KEY lives in the server's .env, never in the database, never in git.
  • JWT-gated API. Every /api/* route except /api/auth/login and /health requires a valid JWT. Download URLs accept a short-lived token in the query string for direct browser downloads.
  • Credentials masked in the UI. When a job or server is returned to the frontend, the password fields are replaced with ********. Saving the form back without changing the field preserves the existing encrypted value — it does not overwrite with the mask.
  • No plaintext ever written to logs. SSH commands are constructed with the password passed via --password=... on the remote shell; the BackupCron log lines record job status, never the constructed command string.
  • SMTP credentials decrypted only at send time. The transporter is built per-send from the decrypted setting. The password is not held in memory long-term.
  • Production refuses to boot without JWT_SECRET and ENCRYPTION_KEY. No silent fallback to a weak dev key in production. The process exits.

Deployment: one box, one command

BackupCron is designed to be deployed on a single small Linux server. My production instance runs on AWS Lightsail on the smallest instance that comfortably holds the app + MySQL + Nginx + PM2. The deploy is a single script from the repo root:

./deploy_remote.sh

That rsyncs the code (excluding node_modules, .env, the local dev DB, backup storage, and .git), runs npm ci in both packages, regenerates the Prisma client, runs prisma db push, builds the React frontend, and restarts the PM2-managed backupcron process. The remote .env is never overwritten. The database is never reset.

Production topology is boring on purpose:

  • Nginx on :8085 as the public proxy.
  • Fastify backend bound to 127.0.0.1:3001 — not exposed to the internet.
  • PM2 keeps the Node process alive and restarts on crash.
  • MySQL database backup for app state only — not for storing your backups.
  • Backup files written to BACKUP_STORAGE_PATH (default /var/lib/backupcron/backups), optionally offloaded to Wasabi and pruned.

You can run it on Lightsail, DigitalOcean Droplet, Hetzner Cloud, EC2, or any Linux box with Node 18+ and MySQL/MariaDB. If you already have a monitoring server or a bastion host, BackupCron is happy to live there.

[ image: backupcron-servers-page.png ] Servers page — cards or table of registered servers with name, host, auth type (password/key), port, and number of jobs. "Add Server" button top right.
The servers page. Add a server once, then attach as many jobs to it as you like.

A real workflow: from zero to scheduled daily MySQL + Wasabi in 5 minutes

Here is what onboarding actually looks like for a new customer. Five steps, no SSH-key handover to a third party, no agent install on your production box.

  1. Add your production server. In Servers → Add Server, enter a friendly name, the host, SSH port (default 22), username, and auth type — password or private key. The credentials are AES-256-GCM encrypted before they hit the database. Click Save.
  2. Create a database backup job. In Backup Jobs → Add Job, pick Database (MySQL Dump), enter the DB name as the source, pick the server from step 1, optionally set a per-job DB username/password if the dump should use a different account than the SSH user. Choose a schedule preset (e.g. Daily (Midnight)) and a retention count (e.g. 14).
  3. Configure Wasabi once in Settings. Enter your Wasabi access key ID, secret access key, region, and an optional default bucket. Click Test Connection — BackupCron lists your buckets and confirms access. The secret key is encrypted at rest; the UI shows ******** after save.
  4. Set the job destination to Wasabi. Back in the job form, choose Destination Type: Wasabi Cloud Storage. Pick a bucket from the auto-populated dropdown, optionally pick a folder prefix (also auto-listed), and tick Delete local backup file after successful upload if you want to keep the BackupCron box slim.
  5. Click Run Now. The job starts immediately. You can watch it in the jobs list (spinner on the row), follow it in the recent activity table on the dashboard, and when it finishes, download the file from the history modal. If it failed, you get an email within seconds with the actual error.

From that point on, the job runs on its cron schedule. You never have to think about it again — unless it fails, in which case you hear about it immediately, with a real error message, before any customer does.

Who BackupCron is for

  • Founders running 1–10 production servers who have been meaning to "set up proper backups" for months and keep not doing it.
  • Sysadmins / SREs at small teams who are tired of per-server cron scripts and want one dashboard for all backups.
  • Agencies managing client servers who want to offer "backups included" without writing bespoke scripts per client.
  • Anyone under a data-sovereignty or compliance constraint where handing SSH access to a third-party SaaS is not allowed.
  • Anyone who has been burned by a silent cron failure and wants email alerts with the actual error.
  • Anyone whose backup SaaS bill has crept past $30/month and would rather pay ~$7 flat for the same durability.

It is not for you if you want a zero-maintenance set-and-forget SaaS, if you do not have a Linux box to run it on, or if your team has nobody who can spend ~30 minutes a month on patching. BackupCron trades a little ops time for a lot of control and a much smaller bill.

What is intentionally not in here

A few things I deliberately left out of this writeup, because this is a customer blog, not a source dump:

  • The exact internal endpoint names and route file layout beyond the public API surface.
  • Specific production IPs, bucket names, or customer infrastructure details.
  • The actual ENCRYPTION_KEY / JWT_SECRET values (obviously) and the exact deploy host.
  • Any business rules around pricing tiers, licensing, or customer-specific configurations.

If you are evaluating BackupCron for your team and want a deeper technical walkthrough — architecture diagram, schema, security review, on-call runbook — I am happy to do that on a call. The point of this post is to give you enough to decide whether it is worth the call.

Get BackupCron set up for your servers

If you are running production MySQL or files on Linux and your current backup story is "cron scripts we never check", we should talk. I can:

  • Deploy and configure BackupCron on your infrastructure (Lightsail, DO, Hetzner, EC2, or an existing box you already run).
  • Wire it into your Wasabi / S3-compatible bucket and your SMTP for failure alerts.
  • Migrate your existing cron backup scripts into managed jobs without losing history.
  • Set up retention, schedules, and per-job cloud destinations for each server.
  • Hand over a working dashboard, documentation, and a 30-minute walkthrough so your team owns it after I leave.

Project work when you want it set up properly. Ongoing when you want someone to keep it running. Book a 15-minute call below, or just email me about your setup.

Book 15 min or email me about your backup setup