Docs
Security
A backup archive is the most sensitive file on your server. Even with wp-config.php left out, it contains every user's password hash, every session token, and any API key a plugin has stored in your options table. Treat a downloaded archive the way you would treat a database dump, because that is what it is.
wp-config.php is excluded by default
Including it is a per-job choice you make each time, and it never carries over to the next backup. That file holds your database password and all eight authentication salts; with it, an archive is enough to connect to your database directly and to forge a login cookie for any account.
The Backups screen shows which archives contain it, so you always know which files on your disk are the dangerous ones.
What actually protects your archives
Four things — and it is worth knowing which of them is doing the work on your server.
- 1The download endpoint is the only way an archive is served. Every download passes four checks in order: logged in, capable, valid nonce, unspent download link.
- 2Download links are single-use and last five minutes. A link is bound to your user, one backup and one volume, and using it spends it. Links are stored as SHA-256 hashes — so a copy of your database, including one this plugin made, contains no usable download links.
- 3The storage directory has 32 random characters in its name. Generated once at activation and never guessable. That is the only thing between a stranger and your archives if your server serves the directory directly.
- 4
.htaccess— on Apache only. Beside the archives the plugin writes anindex.php, an emptyindex.html, and an.htaccessdenying all access.
On nginx, .htaccess does nothing. This was measured, not assumed.
A file placed in wp-content with Require all denied beside it was requested over HTTP while logged out, and the server returned it with 200 OK. On nginx, protections 1 and 3 are what you actually have — the download endpoint, and an unguessable directory name. That is defence by obscurity, not by configuration.
Add this to your server block for a real deny rule, replacing the suffix with your own directory name:
location ~ ^/wp-content/fiction-drafts-[a-f0-9]{32}/ {
deny all;
return 404;
}The strongest option: move the storage off the web root
A path the web server cannot serve cannot be requested. Point the storage somewhere outside the document root and the question of directory protection disappears entirely:
// wp-config.php
define( 'FICTION_DRAFTS_STORAGE_DIR', '/home/you/private/fiction-drafts' );The download endpoint honours the constant, so downloads keep working exactly as before.
Path containment
Downloads are served by PHP rather than by a public URL. Every path is resolved through a containment check that refuses symlinks and anything resolving outside the storage directory.
Multisite
Not supported in this release. manage_options is a per-site capability, so on a network install it belongs to every subsite administrator while the archives belong to one site. Rather than leave that open, Fiction Drafts requires manage_network_options whenever is_multisite() is true.
This will refuse someone who ought to be allowed
On a network where you activated the plugin for a single site, a site administrator who legitimately owns that site will be turned away. That is deliberate: a refused download is a better failure than another site's password hashes. Full multisite support is planned for a later version.