Borg : exclure des dossiers d'une sauvegarde
Motifs, --exclude-caches et --exclude-if-present : quelle règle gagne, comment le vérifier dans l'archive, et quoi exclure sur un serveur Debian.
Exclure un dossier avec Borg : deux familles de règles
Borg décide du contenu d'une archive en deux passes, et la plupart des exclusions qui ne s'appliquent pas viennent de là. La première passe est celle des motifs : --exclude, --exclude-from, --pattern et --patterns-from. La seconde est celle des marqueurs de dossier : --exclude-caches, qui repère un fichier CACHEDIR.TAG, et --exclude-if-present, qui repère un fichier témoin dont vous choisissez le nom.
Que se passe-t-il quand les deux se contredisent ? Prenez un dossier réclamé par un motif d'inclusion + et marqué en même temps par un CACHEDIR.TAG. Une seule des deux règles décide. Ce guide ne l'annonce pas à l'avance : on monte un petit arbre de test, on sauvegarde dans un dépôt local jetable, et on lit ce que l'archive contient vraiment. La vérification tient en une commande, donc vous pourrez la refaire telle quelle sur votre propre serveur.
Les commandes sont écrites pour borgbackup 1.4.0, le paquet de Debian 13 (version 1.4.0-5 du dépôt, en septembre 2026). Vérifiez la vôtre avec borg --version. La série 2 de Borg change la façon de désigner le dépôt, donc les lignes ci-dessous ne s'y transposent pas directement.
Installer borgbackup sur Debian 13
sudo apt update
sudo apt install -y borgbackup
borg --versionborg --version doit répondre borg 1.4.0. Le paquet borgbackup installe la commande borg et ses dépendances Python. Pour un dépôt local, il n'y a rien d'autre à installer, et aucun service à démarrer : Borg est un programme que vous lancez, pas un démon qui tourne.
Monter un arbre de test qui contient les cas réels
mkdir -p /tmp/labo-borg/donnees/app/node_modules
mkdir -p /tmp/labo-borg/donnees/cache
mkdir -p /tmp/labo-borg/donnees/medias
echo 'port: 8080' > /tmp/labo-borg/donnees/app/config.yml
echo 'module.exports = {}' > /tmp/labo-borg/donnees/app/node_modules/index.js
printf 'Signature: 8a477f597d28d172789f06886806bc55\n' > /tmp/labo-borg/donnees/cache/CACHEDIR.TAG
head -c 2M /dev/urandom > /tmp/labo-borg/donnees/cache/gros.bin
touch /tmp/labo-borg/donnees/medias/.nobackup
head -c 1M /dev/urandom > /tmp/labo-borg/donnees/medias/film.mp4
find /tmp/labo-borg/donneesCet arbre contient les trois situations que vous rencontrez sur un vrai serveur. app/node_modules est un dossier de dépendances : reconstructible, volumineux, sans valeur dans une archive. cache porte le marqueur standard des dossiers de cache. medias porte un fichier témoin .nobackup, la convention que vous posez vous-même quand un dossier ne doit jamais partir en sauvegarde.
Le contenu du CACHEDIR.TAG n'est pas décoratif. Borg lit les premiers octets du fichier et les compare à la chaîne Signature: 8a477f597d28d172789f06886806bc55, définie par la spécification Cache Directory Tagging. Un fichier vide portant le bon nom ne déclenche rien du tout, parce que la comparaison échoue. Beaucoup d'outils de build et de gestionnaires de paquets posent ce fichier tout seuls, donc --exclude-caches écarte souvent des dossiers que vous n'aviez pas listés.
Créer un dépôt local et lancer la sauvegarde
export BORG_PASSPHRASE='labo-jetable'
borg init --encryption=repokey-blake2 /tmp/labo-borg/depotLa phrase de passe passe ici par une variable d'environnement parce que ce dépôt est jetable et vit dans /tmp. Sur un vrai dépôt, elle ne se met pas en clair dans une ligne de commande que votre shell enregistre dans son historique, et elle se conserve ailleurs que sur la machine sauvegardée : sans elle, le dépôt est illisible, y compris par vous.
borg create --list --stats \
--exclude-caches \
--exclude-if-present .nobackup \
--pattern '+ pp:/tmp/labo-borg/donnees/cache' \
--pattern '+ pp:/tmp/labo-borg/donnees/medias' \
--exclude 'sh:**/node_modules' \
/tmp/labo-borg/depot::regles /tmp/labo-borg/donneesCette commande met volontairement les règles en conflit. Les deux --pattern réclament cache et medias avec un +, le préfixe d'inclusion. Le style pp: veut dire préfixe de chemin : il désigne le dossier et tout ce qu'il contient, sans caractère joker à interpréter. Et les deux drapeaux --exclude-caches et --exclude-if-present visent exactement ces deux mêmes dossiers. --stats affiche à la fin la taille traitée et la taille réellement ajoutée au dépôt.
Quelle règle gagne : la réponse est dans l'archive
borg list --format '{path}{NL}' /tmp/labo-borg/depot::reglestmp/labo-borg/donnees
tmp/labo-borg/donnees/app
tmp/labo-borg/donnees/app/config.ymlTrois entrées. Ni cache, ni medias, ni node_modules. Les deux inclusions + n'ont rien ramené : sur un dossier marqué, ce sont les marqueurs qui décident. L'ordre des lignes suit l'ordre de parcours du disque et non l'alphabet, donc seule la présence compte, pas la position.
Le mécanisme explique le résultat. Pour chaque chemin, Borg interroge d'abord la liste des motifs, avec une règle unique : le premier motif qui correspond décide. Si la réponse est non, le chemin est abandonné tout de suite. Si la réponse est oui, et seulement dans ce cas, Borg ouvre le dossier et cherche un CACHEDIR.TAG valide ou l'un des fichiers témoins de --exclude-if-present. Un marqueur trouvé à cette étape referme le dossier : il n'est pas archivé et Borg ne descend pas dedans. Le test du marqueur a donc lieu après le filtre des motifs, ce qui veut dire qu'aucun + ne peut annuler un marqueur.
Pour récupérer un dossier marqué, vous n'avez que deux leviers : retirer le drapeau de la ligne de commande, ou supprimer le fichier témoin sur le disque. Notez aussi que le chemin stocké perd son slash initial. borg list affiche tmp/labo-borg/donnees, pas /tmp/labo-borg/donnees. Borg retire ce slash de vos motifs aussi, donc les deux écritures fonctionnent dans un --exclude.
Le contrôle à refaire à chaque changement de règle
borg create --dry-run --list \
--exclude-caches --exclude-if-present .nobackup \
--exclude 'sh:**/node_modules' \
/tmp/labo-borg/depot::controle /tmp/labo-borg/donnees 2>&1 | grep '^x'--dry-run n'écrit rien dans le dépôt, et --list imprime une ligne par élément avec une lettre d'état devant. En version 1.4, x veut dire écarté et - veut dire retenu mais non écrit, puisque c'est un essai à blanc. Cette liste part sur la sortie d'erreur, d'où le 2>&1 avant le grep. Prenez l'habitude de lancer ce contrôle avant de modifier une règle en production : c'est le seul arbitre, et il coûte quelques secondes. En 1.4, --stats se combine sans erreur avec --dry-run, et l'essai à blanc n'ajoute toujours aucune archive au dépôt.
Garder le dossier et son marqueur avec --keep-exclude-tags
borg create --exclude-caches --exclude-if-present .nobackup --keep-exclude-tags \
/tmp/labo-borg/depot::marqueurs /tmp/labo-borg/donnees
borg list --format '{path}{NL}' /tmp/labo-borg/depot::marqueurstmp/labo-borg/donnees
tmp/labo-borg/donnees/app
tmp/labo-borg/donnees/app/config.yml
tmp/labo-borg/donnees/app/node_modules
tmp/labo-borg/donnees/app/node_modules/index.js
tmp/labo-borg/donnees/cache
tmp/labo-borg/donnees/cache/CACHEDIR.TAG
tmp/labo-borg/donnees/medias
tmp/labo-borg/donnees/medias/.nobackupLes deux dossiers marqués reviennent, avec leur fichier témoin et rien d'autre : gros.bin et film.mp4 restent dehors. node_modules est là parce que cette commande ne contient plus le --exclude de tout à l'heure, ce qui rappelle qu'un motif n'agit que si vous le passez.
L'intérêt est concret. À la restauration, le dossier existe déjà avec son propriétaire et ses droits, et son marqueur est en place pour la sauvegarde suivante. Sans cette option, vous restaurez une arborescence où un service attend un dossier qui n'existe plus. La documentation présente --keep-exclude-tags pour --exclude-if-present : la sortie ci-dessus montre qu'elle vaut aussi pour le CACHEDIR.TAG.
Exclure un dossier, ou seulement son contenu
borg create --exclude '/tmp/labo-borg/donnees/app/node_modules/' \
/tmp/labo-borg/depot::slash /tmp/labo-borg/donnees
borg list --format '{path}{NL}' /tmp/labo-borg/depot::slash | grep node_modulestmp/labo-borg/donnees/app/node_modulesLe dossier est là, son contenu non. La règle tient au slash final, et elle est documentée : un motif d'exclusion qui se termine par / garde le dossier et écarte son contenu, un motif sans slash final écarte les deux. C'est utile pour les dossiers dont l'existence, le propriétaire et le mode comptent au moment de la restauration : un /var/log/, un dossier de spool, un dossier de travail qu'un service refuse de créer lui-même.
Cette astuce ne marche pas avec le style pp:, qui ignore le slash final et désigne toujours le dossier plus son contenu. Elle vaut pour les styles à jokers, fm: et sh:, qui sont ceux de --exclude et de --pattern.
L'ordre des motifs décide, et il se démontre
La règle est la même partout : le premier motif qui correspond gagne, et les suivants ne sont pas consultés. Deux lignes suffisent à le montrer.
cat > /tmp/labo-borg/motifs.txt <<'EOF'
# le premier motif qui correspond decide
- pp:/tmp/labo-borg/donnees/cache
+ pp:/tmp/labo-borg/donnees/cache
EOF
borg create --patterns-from /tmp/labo-borg/motifs.txt /tmp/labo-borg/depot::ordre-1 /tmp/labo-borg/donnees
borg list --format '{path}{NL}' /tmp/labo-borg/depot::ordre-1 | grep cacheLe grep ne renvoie rien : l'exclusion était en tête. Inversez les deux lignes et relancez.
cat > /tmp/labo-borg/motifs.txt <<'EOF'
+ pp:/tmp/labo-borg/donnees/cache
- pp:/tmp/labo-borg/donnees/cache
EOF
borg create --patterns-from /tmp/labo-borg/motifs.txt /tmp/labo-borg/depot::ordre-2 /tmp/labo-borg/donnees
borg list --format '{path}{NL}' /tmp/labo-borg/depot::ordre-2 | grep cachetmp/labo-borg/donnees/cache
tmp/labo-borg/donnees/cache/CACHEDIR.TAG
tmp/labo-borg/donnees/cache/gros.binMêmes règles, ordre inverse, résultat inverse. Aucun drapeau --exclude-caches n'était passé ici, donc le marqueur reste inerte et seuls les motifs décident. Retenez la conséquence pratique : écrivez vos inclusions avant les exclusions larges qu'elles doivent contredire, comme le fait l'exemple de fichier de motifs fourni par Borg.
Les quatre formes ne se comportent pas de la même manière, et c'est là que les configurations partent en vrille.
--excludeet--exclude-fromn'acceptent que des exclusions, sans préfixe, et interprètent leurs motifs en stylefm:(fnmatch) par défaut.--patternet--patterns-fromacceptent les préfixes de commande et utilisent le stylesh:(shell) par défaut.- Dans un fichier de motifs,
+inclut,-exclut,!exclut sans descendre dans le dossier,Rdéclare une racine à sauvegarder etPchange le style par défaut des lignes suivantes. - Les lignes vides et les lignes commençant par
#sont ignorées dans les deux types de fichiers.
La différence entre - et ! compte : avec !, Borg ne descend pas dans le dossier, donc une inclusion placée en dessous ne sera jamais atteinte. La documentation l'utilise pour /proc, où il n'y a rien à sauvegarder et où le parcours coûte du temps.
Reste l'ordre entre les formes. La documentation de Borg 1.4 annonce un ordre par famille : les motifs de la ligne de commande d'abord, puis ceux de --patterns-from, et enfin les exclusions de --exclude-from. Cet ordre n'est pas celui que vous lisez dans votre commande, donc ne pariez pas dessus. Le montage qui se relit tout seul est un fichier unique passé à --patterns-from, où l'ordre des lignes est l'ordre d'évaluation, plus le contrôle --dry-run --list de la section précédente.
Ce qu'il faut vraiment exclure sur un serveur
- Les pseudo systèmes de fichiers :
/proc,/sys,/devet/run, qui décrivent l'état du noyau et n'ont aucun sens dans une archive. - Les caches reconstructibles :
/var/cache/apt/archives, les~/.cachedes comptes de service, les caches de build. - Les dépendances :
node_modules,vendor,.venv,target, que votre gestionnaire de paquets régénère. - Les fichiers énormes et volatils :
/swapfile, les images disque de machines virtuelles, les fichiers temporaires de rendu.
Les bases de données méritent une phrase à part. Copier /var/lib/mysql ou /var/lib/postgresql pendant que le serveur écrit produit une archive incohérente, parce que Borg lit les fichiers les uns après les autres alors que le moteur, lui, modifie plusieurs fichiers à la fois. La restauration peut alors échouer au démarrage ou perdre les dernières transactions. Excluez ces dossiers et sauvegardez à la place un export produit juste avant, avec mariadb-dump ou pg_dump, écrit dans un dossier que vous, vous archivez.
Pour Docker, /var/lib/docker mélange des couches d'images que vous pouvez retélécharger et des volumes nommés qui portent vos données. Exclure le dossier en bloc supprime les deux. Regardez où Docker Compose range réellement vos volumes avant d'écrire cette ligne.
Une dernière remarque sur le gain attendu. La déduplication limite le coût des données qui se répètent d'une sauvegarde à l'autre, donc exclure un dossier stable ne fait pas maigrir le dépôt autant qu'on l'imagine. Ce qui le fait grossir, ce sont les fichiers qui changent entièrement à chaque exécution : un bundle recompilé, une image, une archive régénérée. Ce sont ceux-là qu'il faut viser en premier. Et une exclusion ne remplace jamais un essai de restauration, parce que un instantané n'est pas une sauvegarde et qu'une archive jamais restaurée n'est qu'une hypothèse.
Quand le laboratoire a répondu à vos questions, effacez-le.
rm -rf /tmp/labo-borgOù vit le dépôt, et ce que le RGPD en attend
Le dépôt de ce guide est dans /tmp, sur la machine sauvegardée : c'est un laboratoire, pas une sauvegarde. Une sauvegarde utile vit sur une autre machine, dans un autre bâtiment. Borg 1.4 sait écrire dans un chemin local, y compris un montage, et sur un serveur atteint en SSH (secure shell) où borg doit être installé des deux côtés. Il n'écrit pas nativement vers du stockage objet compatible S3, et c'est souvent ce point qui tranche le choix entre Restic et BorgBackup.
Les cibles habituelles sont un VPS de stockage chez un hébergeur, un NAS (serveur de stockage en réseau) installé sur un autre site, ou la machine d'un associé. Sur la cible, créez un compte dédié et lancez le service côté serveur en mode borg serve --append-only, avec une restriction de chemin : un attaquant qui prend la main sur le client peut alors écrire, mais pas effacer vos archives. Utiliser un VPS comme cible de sauvegarde hors site détaille ce montage de bout en bout, et la mise en place de sauvegardes Restic sur un VPS montre l'équivalent quand la cible est du stockage objet.
Côté RGPD (règlement général sur la protection des données), la règle est simple à énoncer : si vos données contiennent des données personnelles, votre dépôt en contient aussi, et l'hébergeur qui héberge ce dépôt est un sous-traitant. Deux conséquences concrètes. La localisation du dépôt doit figurer dans votre registre des traitements, donc choisissez une région dont le pays est écrit noir sur blanc dans le contrat, et garder les données dans une région européenne évite la question des transferts hors Union européenne. Le chiffrement, ensuite : un dépôt initialisé avec repokey-blake2 ne livre à l'hébergeur que des blocs chiffrés, sans noms de fichiers ni contenus lisibles. Il voit la taille du dépôt et le rythme de vos sauvegardes, pas ce qu'il y a dedans.
Les exclusions participent à la même logique. Ce que vous n'archivez pas ne se retrouve pas dans une archive que vous conserverez des années. Un export de base qui contient des données personnelles hors de votre périmètre n'a rien à faire dans le dépôt : excluez le dossier, ou n'écrivez pas l'export au même endroit.
FAQ
Pourquoi mon exclusion Borg ne change rien ?
La cause la plus fréquente est un motif relatif comparé à un chemin absolu. Borg compare vos motifs au chemin stocké, à partir de son début, donc --exclude 'node_modules' ne correspond à rien quand la sauvegarde part de /srv : le chemin stocké commence par srv/. Écrivez le chemin complet, ou un motif qui traverse l'arborescence comme sh:**/node_modules. Pour trancher, lancez borg create --dry-run --list avec vos règles et filtrez les lignes qui commencent par x : ce sont les éléments écartés, et la liste part sur la sortie d'erreur, donc ajoutez 2>&1 avant le tube.
Comment exclure un dossier partout dans l'arborescence ?
Utilisez le style shell et le joker de niveaux : --exclude 'sh:**/node_modules'. Dans ce style, **/ correspond à zéro ou plusieurs niveaux de dossiers, donc le motif attrape srv/app/node_modules comme home/lea/projet/node_modules. Le préfixe sh: est explicite ici parce que le style par défaut de --exclude est fm:, où le joker * traverse aussi les slashs et produit des correspondances que l'on n'attendait pas.
Une inclusion peut-elle ramener un dossier marqué par CACHEDIR.TAG ?
Non. Borg évalue d'abord la liste des motifs, et n'ouvre le dossier pour y chercher un marqueur que si les motifs l'ont accepté. Le marqueur est donc consulté après, et il ferme le dossier quoi qu'ait dit le +. Vous pouvez le constater en sauvegardant un arbre de test avec --exclude-caches et une inclusion + pp: sur le dossier de cache, puis en listant l'archive : le dossier n'y est pas. Pour l'archiver, retirez --exclude-caches de la commande ou supprimez le fichier CACHEDIR.TAG.
Quelle différence entre --exclude-caches et --exclude-if-present ?
--exclude-caches cherche un fichier nommé exactement CACHEDIR.TAG et vérifie que ses premiers octets valent Signature: 8a477f597d28d172789f06886806bc55. Un fichier vide portant ce nom ne déclenche donc rien. --exclude-if-present NOM ne regarde que l'existence d'un objet portant ce nom dans le dossier, fichier ou dossier, sans lire son contenu, et l'option peut être répétée pour plusieurs noms. Dans les deux cas, --keep-exclude-tags conserve le dossier et son marqueur dans l'archive, et laisse le reste dehors.