Backup strategies for an SFTP server that stores files in a database

· ~9 minute read

Back in June we shipped the database storage backend and the first question from every operations team was the same: “How do I back this thing up?”

Fair question. With a traditional filesystem-based SFTP server you already have two separate backup stories — one for the user home directories on disk and another for the metadata database. When the files themselves live in the database, those two stories collapse into one. That sounds simpler, and it is, but it also changes what you need to protect, how often, and what a restore actually looks like. This post walks through the full picture: what to back up, the safety net the server already provides, and the restore procedure you should test before you need it.

The two-backup problem with filesystem storage

When your SFTP server stores files on disk, a complete backup requires two things that are not aware of each other.

First, you back up the home directories. Every user’s uploaded files live under ${STYLUS_SFTPSERVER_DATA}/homes/${username}. You point your backup agent at that tree, it walks it, it copies bytes. Straightforward enough — until you consider that an upload might be in progress. Stylus SFTP Server writes active transfers into a centralized .staging/ directory as filename.UUID.tmp files, then atomically renames them into the user’s home on channel close. A snapshot taken mid-transfer might capture a half-written .tmp file that will never be renamed. Not dangerous, but it means your restore can include orphans your users never sent.

Second, you back up the database. The embedded H2 database (or your external SQL Server, MySQL, or PostgreSQL instance) holds the audit trail, user accounts, lockout state, notification history, and quota records. That database has its own backup cadence, its own retention, and its own restore procedure. It knows nothing about the files on disk.

The result is two timelines that can drift apart. You might restore the database to 3:00 AM and the filesystem to 3:17 AM. The audit table says a file arrived at 3:10 AM; the filesystem copy includes it; the database has no record. Or the reverse: the audit log references a file that the filesystem snapshot missed. Neither inconsistency is catastrophic — the SFTP server will keep running — but the audit trail and the actual file state disagree, and that disagreement is exactly the kind of thing an auditor will ask about.

One backup when files are in the database

When you switch the storage backend to database in filesystem-config.xml, every uploaded file lands in the same relational database that holds everything else. The file bytes go into sftp_chunk_data as ref-counted BLOB chunks. The file metadata — path, owner, size, timestamps — goes into sftp_file_storage. The join table sftp_file_chunks maps each file to its ordered list of chunks.

This means one database backup captures everything: the files, the audit trail, the user accounts, the quota state, the lockout history, the notification log. No second tool. No two timelines. No drift. A database backup taken at 3:00 AM is a consistent snapshot of every file, every event, every row, from the same point in time.

The deduplication layer makes those backups smaller than you might expect. When the JDBC backend copies a file — during a rename, a portal download-and-reupload, or a directory copy — it does not duplicate the BLOB data. It inserts new rows in sftp_file_chunks pointing at the same sftp_chunk_data rows and increments the ref_count. A thousand copies of the same 50 MB file cost one copy of storage in the database. Your backup inherits that same compactness.

What to back up

Regardless of which storage backend you use, a complete backup of a Stylus SFTP Server installation covers three things.

1. The database

For the default embedded H2 database, the data files live at ${STYLUS_SFTPSERVER_DATA}/db/sftpdb.mv.db. On new installations H2 is encrypted with AES-256 — the JDBC URL contains CIPHER=AES — so the database files on disk are ciphertext. Your backup agent does not need to understand the encryption; it copies bytes. But a backup of these files is useless without the keys to open them, which brings us to the next item.

For an external database (SQL Server, MySQL, PostgreSQL), use whatever backup tool your DBA already runs. The server’s tables (sftp_users, sftp_audit, sftp_file_storage, sftp_chunk_data, and the rest) are ordinary tables in an ordinary schema. Nothing special about backing them up.

2. The credentials

Two files in ${STYLUS_SFTPSERVER_DATA}/conf/ hold the secrets:

If you restore the H2 database files without these two files, you cannot open the database. The AES cipher key inside credentials.p12 is the only thing that can decrypt it. Treat credentials.p12 and master.key the way you would treat a TLS private key: back them up, encrypt the backup, and restrict access.

3. The configuration

The conf/ directory also holds the XML configuration files: sftp-server.xml, filesystem-config.xml, users.xml, keys.xml, and drivers-catalog.xml. These change infrequently but they are the difference between a restored server that starts and one that does not. Include them in every backup.

If you use filesystem storage, add the home directories tree (homes/) to this list. If you use database storage, the home directories are empty — the files are in the database already.

The built-in upgrade backup

The installer has its own backup mechanism, designed around the riskiest moment in any server’s life: the upgrade.

When you run the installer over an existing installation, three things happen before any file is touched:

  1. Lock gate. The installer attempts to acquire an exclusive file lock on every .mv.db database file. It tries 30 times at 500 ms intervals — 15 seconds total. If any lock fails (because the server process is still running), the upgrade aborts immediately with an error naming the held files. No backup is taken, no files are modified. This prevents the installer from copying a database that another process is actively writing to.
  2. Offline backup. Once the locks succeed, the installer copies every database file (*.mv.db, *.trace.db) into a timestamped directory: ${STYLUS_SFTPSERVER_DATA}/db.backup/2026-09-01-090000-upgrade/. Lock files (*.lock.db) are excluded — they are transient AUTO_SERVER state, not data. The copy preserves file attributes and uses REPLACE_EXISTING semantics.
  3. Abort on failure. If the backup copy fails for any reason — insufficient disk space, a permission error, an I/O fault — the entire upgrade is aborted. The installer reports the exact file that failed and confirms that nothing was modified. This guarantee was hardened after a real incident in August 2026 where an upgrade ran against a database that had been manually restored from an older backup; the lock gate caught it, but the experience motivated making the abort path even more explicit.

The upgrade backup is not a substitute for your own backup schedule. It protects one specific moment. But it means that if an upgrade goes wrong, the previous database state is one directory copy away from a rollback.

Backup strategies by storage model

Filesystem storage + embedded H2

This is the default configuration. Your backup plan needs two passes:

  1. Back up ${STYLUS_SFTPSERVER_DATA}/db/ (the H2 files), conf/credentials.p12, conf/master.key, and the conf/*.xml files. These are small — a few megabytes in total for most installations.
  2. Back up ${STYLUS_SFTPSERVER_DATA}/homes/. This is where the volume is. Size depends entirely on your users’ transfer patterns.

If you can stop the server briefly, the simplest H2 backup is a file copy of sftpdb.mv.db while the server is down. H2’s AUTO_SERVER mode means the database is not locked by a single process, but a cold copy is guaranteed consistent.

If you cannot stop the server, H2 supports online backup via the BACKUP TO SQL command, which produces a compressed ZIP of the database at a consistent point. The Web Admin console exposes this as a one-click operation.

Database storage + embedded H2

Same as above, but you skip the homes directory — the files are inside H2. The database files will be larger (proportional to the volume of files your users store), so size your backup window accordingly. The BACKUP TO command still works; the resulting ZIP will contain the file BLOBs alongside everything else.

Database storage + external database

This is the cleanest model from a backup perspective. Your DBA’s existing backup schedule covers everything. SQL Server backups, pg_dump, mysqldump, or whatever your team already uses — the file content is ordinary table data. The only additional items are the conf/ directory (credentials, config files, and master.key), which can ride along with any lightweight file backup.

If your external database is already replicated — SQL Server Always On, PostgreSQL streaming replication, MySQL InnoDB Cluster — then the file content replicates with it. You get geographic redundancy for your file transfers without a second replication story.

Testing the restore

A backup you have never restored is a hypothesis. Here is what a tested restore procedure looks like for the database storage model.

  1. Install the same version of Stylus SFTP Server on a clean machine. Run the installer but do not start the server.
  2. Replace the data directory. Copy your backed-up db/, conf/, and (if using filesystem storage) homes/ into ${STYLUS_SFTPSERVER_DATA}. The key files are sftpdb.mv.db, credentials.p12, master.key, and the XML configs.
  3. Start the server. It should read the restored database, find all tables intact, and begin accepting connections. If the H2 database is AES-encrypted, the server resolves the cipher password from the restored credentials.p12 using the restored master.key — if either file is missing or mismatched, startup will fail with a clear error.
  4. Verify. Connect with an SFTP client. List directories. Download a file and compare checksums. Check the Web Admin audit log — events up to the backup timestamp should be present.

Do this once a quarter. The entire procedure takes less than 15 minutes on a test VM. The confidence it provides is worth considerably more than 15 minutes.

Common mistakes

The take-home

The database storage backend does not eliminate the need for backups — nothing does. What it eliminates is the coordination problem. One backup tool, one schedule, one restore procedure, one consistent point in time. The files, the audit trail, the accounts, the configuration state — they all travel together.

If you are already running the database backend from our June post, add the credential files to your existing database backup and test a restore. If you are still on filesystem storage and your backup story has become two separate systems that never quite agree on what happened at 3:00 AM, consider whether collapsing everything into the database is simpler than maintaining both.

The simplest backup strategy is the one that protects everything in one pass. For an SFTP server that stores files in a database, that is exactly what you get.

Ready to simplify your backup story?

Free evaluation key. Install on Windows or Linux, switch to the database backend, and your next backup captures everything — files, audit trail, accounts — in one pass.

Request Evaluation Key More articles ›