I’ve been using BorgBackup for years to protect critical data across Proxmox hosts and bare-metal servers. One recurring pain point is managing what gets backed up—especially when developers or admins drop temporary caches, logs, or test data into shared directories. Manually editing exclude patterns every time a new noisy folder appears is error-prone and tedious.
The solution I’ve settled on is simple: let Borg skip any directory that contains a .nobackup file. This uses the --exclude-if-present flag, turning a static exclude list into a dynamic, self-service mechanism. No more chasing down rogue node_modules or .cache dirs—they opt out by dropping a zero-byte file.
How --exclude-if-present Works in Borg
Borg’s exclude patterns support a special mode where instead of matching a path, it looks for a file. If that file exists in a directory, Borg skips backing up that entire directory tree. The syntax is straightforward:
borg create --exclude-if-present .nobackup /mnt/repo::hostname-{now:%Y-%m-%d} /data
Here, Borg walks /data. Whenever it encounters a directory containing a file named .nobackup, it stops descending and excludes that dir from the archive. The marker file itself is never backed up—it’s purely a signal.
This is cleaner than regex-based excludes because it doesn’t require predicting folder names. It also avoids the performance hit of scanning every path against a long exclude list.
Practical Example: Excluding App Caches and Logs
On our file servers, developers often mount /srv/app where each subdir is a service. Some generate large cache directories; others write verbose debug logs. Instead of asking them to remember backup policies, we let them opt out.
After a deployment, a service might leave behind:
/srv/app/api/v2/cache/
/srv/app/api/v2/logs/
/srv/app/worker/tmp/
If any of these contain .nobackup, Borg ignores them. We enforce this via a post-deploy script that touches the file in known noisy locations:
# After deploying api/v2
touch /srv/app/api/v2/cache/.nobackup
touch /srv/app/api/v2/logs/.nobackup
Now the next backup skips those trees automatically. No Borg config change needed.
Combining with Other Excludes
You can still use regular --exclude patterns alongside --exclude-if-present. For example, I keep global excludes for OS-level junk:
borg create \
--exclude '*/.cache/*' \
--exclude '*/__pycache__/*' \
--exclude-if-present .nobackup \
/mnt/backups::daily-{now:%Y-%m-%d} \
/srv /etc /root
Here, Borg first applies the static excludes, then checks for .nobackup during the walk. The order doesn’t matter much for correctness, but placing --exclude-if-present after broad patterns can make intent clearer in scripts.
Marker File Best Practices
I’ve found a few conventions help avoid confusion:
- Use an empty file:
touch .nobackupis sufficient. - Commit it to version control if the dir is in Git—so the opt-out follows the code.
- Document it in your team’s runbook: "Add
.nobackupto exclude a dir from backups." - Never rely on it for security; it’s a convenience tool, not an access control.
One gotcha: if you back up the .nobackup file itself (e.g., via a broader include), Borg will still exclude the dir but save the marker. To avoid even that, ensure your include/exclude logic doesn’t accidentally capture the file. In practice, since we exclude the whole dir, it’s a non-issue.
Monitoring and Auditing
To verify it’s working, I check the Borg output for A (added) vs x (excluded) flags. A dry-run shows exclusions clearly:
borg create --dry-run --exclude-if-present .nobackup --list /mnt/repo::test /srv
Look for lines like:
x srv/app/api/v2/cache/
x srv/app/api/v2/logs/
A srv/app/api/v2/src/
If you see a dir marked x that shouldn’t be excluded, check for a stray .nobackup. Conversely, if something large is still being added, confirm the marker exists and is readable by the Borg process.
I also log backup stats to our monitoring system. A sudden spike in excluded data often means a new cache-heavy deployment went live—which is actually useful feedback.
Why This Beats Manual Exclude Lists
Before this approach, I maintained a growing --exclude list in /etc/borg/excludes. It required:
- Predicting folder names
- Updating the file on every backend change
- Risking typos that either over-excluded or under-excluded
With .nobackup, the decision lives where the data lives. Developers control their own backup footprint without needing infra tickets. It’s shifted left in the best way: policy as code, enforced by convention.
As I mentioned before in my post about [Restic snapshot diffs](https://furkanikkan.com/urun/restic-anlik-goruntu-degisikliklerini-diff-ve-jq-ile-gorsellestirme-85), automation reduces toil—but only when it’s intuitive. This pattern strikes that balance: zero maintenance overhead, clear semantics, and full compatibility with existing Borg workflows.
If you’re tired of chasing down backup bloat, try adding --exclude-if-present .nobackup to your next borg create. Chances are, you’ll find yourself dropping marker files more often than editing exclude lists.
Cover image: Lenharth Systems · CC0 (Openverse / kamu malı) · https://stocksnap.io/photo/computer-hard-2J3PLNMO9M
