---
title: "Automated PHP external .tar.gz backup rotation and retention period"
description: "With this PHP script, you can transform your Linux distro into an automated backup rotation server. Useful when in combination with automated cPanel backups."
canonical: "https://www.mauriziofonte.it/blog/post/automated-php-backup-rotation-retention.html"
author: "Maurizio Fonte"
date: "2016-02-08T17:23:26+01:00"
modified: "2016-02-08T17:23:26+01:00"
section: "PHP"
tags: ["PHP", "Linux & Server"]
license: "Riuso e citazione consentiti con attribuzione e link alla fonte"
---

[PHP](https://www.mauriziofonte.it/blog/cat/php.html) [Linux & Server](https://www.mauriziofonte.it/blog/cat/linux.html)

# [Automated PHP external .tar.gz backup rotation and retention period](https://www.mauriziofonte.it/blog/post/automated-php-backup-rotation-retention.html)

Lunedì 8 Febbraio 2016, alle 17:23 ![Automated PHP external .tar.gz backup rotation and retention period](https://www.mauriziofonte.it/image/000270200-automated-php-backup-rotation-retention.jpg)

You surely have experienced a **lot of pain** when handling your **backups coming from the various servers you have online**. A very important thing to remember is, in fact, to have backups of your live web servers, so if things go crazy, you have the "last resource".

Plus, **backups should be kept for at least some days**, so that if you find out a web server breach or something you haven't noticed before, you can roll back the situation to the **backup of N-days before**.

**If you're running cPanel, getting automated backups is actually pretty easy** and straightforward. With cPanel you can even have a default backup retention period of N days, and retain that backups directly on the main system. But, **that retention period is relative to the HDD of the main server** you're running on. And, another negative fact is, if your main cPanel server that keeps your 10-days old backups fails or gets destroyed by the sys admin?

**This is why, in my opinion, the best thing to do is have an external backup server where you send, via FTP, every type of backup from the webservers you are currently running.**

Having an external backup server also enables you to have more than one server to be backed-up. Personally, **I run a backup server that accepts backups from 3 different cPanel servers sources**.

To help your backup server, and to help you setting everything up quick, **I've coded a little script, that can be cron-jobbed and run via PHP CLI, that reads the backup destination directory** ( the one into which you make your remote cPanel FTP send into ) **and organizes the files contained into it by day, purging the backups that are too old to be retained**.

If you're looking at guides on how to turn on automated cPanel backup and FTP remote send, there are plenty of guides and walkthroughs. Google is you friend.

Let's have a look at the code:

```
<?php
/*
|--------------------------------------------------------------------------
| PHP / CGI Automated "Backup File Rotation" script
|--------------------------------------------------------------------------
|
| Version: 1.1.0
| Author: Maurizio Fonte
| Author URL: https://www.mauriziofonte.it
| Description:
| This script ( that needs to be run via CLI e.g /usr/bin/php -q this_script.php ) serves as an automated backup rotation organization.
| In fact, it takes all subdirectories under the main BACKUP_DIR folder and copies them to another location in which these backup files will be stored and rotated.
| For example, consider these directories and their contents:
|
| BACKUP_DIR = /root/backup
|
| /root/backup
| |-> folder_a
| |       -> folder_a_backup_file_1.tar.gz
| |       -> folder_a_backup_file_2.tar.gz
| |-> folder_b
| |       -> folder_b_backup_file_1.tar.gz
| |       -> folder_b_backup_file_2.tar.gz
| |       -> folder_b_backup_file_3.tar.gz
| |-> folder_c
| |       -> folder_c_backup_file_1.tar.gz
|
| For every execution day, the script will read the contents of the /root/backup folder, and re-organize the files under the directory /back_rot/
| The script will create this rotation structure:
|
| BACKUP_ROTATION_DIR = /back_rot/
|
| /back_rot/
| |-> 2016-01-01
| |-> folder_a
| |       -> folder_a_backup_file_1.tar.gz
| |       -> folder_a_backup_file_2.tar.gz
| |-> folder_b
| |       -> folder_b_backup_file_1.tar.gz
| |       -> folder_b_backup_file_2.tar.gz
| |       -> folder_b_backup_file_3.tar.gz
| |-> folder_c
| |       -> folder_c_backup_file_1.tar.gz
| |-> 2016-01-02
| ...
| ...
|
| After a backup file / folder in the BACKUP_ROTATION_DIR becomes "too old to survive" ( after BACKUP_RETENTION_PERIOD days ), the folder will be automatically purged.
|
| Please consider that, in order for this automated backup rotator to work *properly*, these conditions have to be met:
| 1) Each day, some other script ( from a remote server, for example ) has to fill its own directory on BACKUP_DIR ( in the example provided, "folder_a", "folder_b", "folder_c" )
| 2) This script needs to be run once per day ( nothing harmful will happen if you run this more than once per day, though. Simply, it will see there are no actions to do ).
| 3) This script is meant to be executed via CLI
*/
@ini_set('max_execution_time', 300);
@ini_set('memory_limit', '256M');
date_default_timezone_set ( 'Europe/Rome' );
define ( 'HOME_DIR', rtrim ( dirname ( __FILE__ ), '/' ) . '/' );
define ( 'LOGS_DIR', 'logs/' );
define ( 'BACKUP_DIR', '/BACKUPS/' );
define ( 'BACKUP_ROTATION_DIR', '/BACKUPS_ROTATION/' );
define ( 'BACKUP_RETENTION_PERIOD', 20 );
define ( 'EXCLUDE_BACKUP_FILENAME', 'exclude_this_backup_filename.tar.gz' );
define ( 'EXCLUDE_BACKUP_FOLDER', 'exclude_this_directory_name_while_parsing' );

ob_start ();
out ( '#############################################################################################' );
out ( '############# #############' );
out ( '############# Automated PHP/CGI BACKUP FILE ROTATION ALGORITHM  v1.1.0 #############' );
out ( '############# Copyright(c) 2016 Maurizio Fonte - mauriziofonte.it #############' );
out ( '############# #############' );
out ( '#############################################################################################' );
out ( '############### ' . date ( 'Y-m-d H:i:s' ) . ' ###############' );
out ( '#############################################################################################' );

// fase 0: se non esiste la cartella BACKUP_DIR, non possiamo fare ovviamente nulla...
if ( ! is_dir ( BACKUP_DIR ) ) closenow ( true, 'This magic script can't do anything as long BACKUP_DIR does not exists ... actual BACKUP_DIR is "' . BACKUP_DIR . '"' );

// fase 1: controllo che la directory dei backup giornalieri e dei backup rotation sia leggibile e scrivibile e mi pre-carico i suoi contenuti in un semplice array di file
out ( ' ** Starting "' . BACKUP_DIR . '" recursive directory iterator...' );
$backup_dir_contents = Array ();
$objects = new RecursiveIteratorIterator ( new RecursiveDirectoryIterator ( BACKUP_DIR ), RecursiveIteratorIterator::SELF_FIRST );
if ( $objects ) {
foreach ( $objects as $name => $object ){
if ( $name !== '.' && $name !== '..' ) {
$name = realpath ( $name );
if ( ! in_array ( $name, $backup_dir_contents ) && $name !== '/' && $name !== rtrim ( BACKUP_DIR, '/' ) ) $backup_dir_contents[] = $name;
}
}
}

// fase 1a: controllo che abbiamo realmente qualcosa da fare dentro $backup_dir_contents e creo le directory di destinazione, se non esistono
if ( count ( $backup_dir_contents ) == 0 ) closenow ( true, 'Nothing found on "' . BACKUP_DIR . '" that can be eligible to a copy/paste! Is that directory empty?' );

if ( ! is_dir ( BACKUP_ROTATION_DIR . '_placeholder' ) ) @mkdir ( BACKUP_ROTATION_DIR . '_placeholder', 0755 );
if ( ! is_dir ( BACKUP_ROTATION_DIR . '_placeholder' ) ) closenow ( true, 'Backup rotation folder ( ' . BACKUP_ROTATION_DIR . ' ) is not write-able ...' );
@touch ( BACKUP_ROTATION_DIR . 'test.txt' );
if ( ! is_file ( BACKUP_ROTATION_DIR . 'test.txt' ) ) closenow ( true, 'Backup rotation folder ( ' . BACKUP_ROTATION_DIR . ' ) is not write-able ...' );
@unlink ( BACKUP_ROTATION_DIR . 'test.txt' );

// fase 2: ciclo di ricognizione del file tree della directory SORGENTE dei backup
out ( ' ** Starting "' . BACKUP_DIR . '" folder accounts+files recognition...' );
$files_found = 0;
$accounts_found = 0;
$backup_tree = Array ();
$remember_backup_account_roots = Array ();
foreach ( $backup_dir_contents as $i => $fullpath ) {
$stripped_path = str_replace ( BACKUP_DIR, '', $fullpath );
$chunks = explode ( '/', $stripped_path );
if ( count ( $chunks ) == 1 ) {
if ( ! array_key_exists ( $chunks[0], $backup_tree ) && $chunks[0] != EXCLUDE_BACKUP_FOLDER ) {
$backup_tree[$chunks[0]] = Array ();
$remember_backup_account_roots[] = $fullpath;
$accounts_found++;
continue;
}
}
else if ( count ( $chunks ) == 2 ) {
if ( ! array_key_exists ( $chunks[0], $backup_tree ) && $chunks[0] != EXCLUDE_BACKUP_FOLDER ) {
$backup_tree[$chunks[0]] = Array ();
$remember_backup_account_roots[] = $fullpath;
$accounts_found++;
}
if ( is_file ( BACKUP_DIR . $chunks[0] . '/' . $chunks[1] ) && $chunks[1] != EXCLUDE_BACKUP_FILENAME ) {
$mtime = filemtime ( BACKUP_DIR . $chunks[0] . '/' . $chunks[1] );
$backup_tree[$chunks[0]][bdate($mtime)][] = BACKUP_DIR . $chunks[0] . '/' . $chunks[1];
$files_found++;
}
else continue;
}
else if ( count ( $chunks ) == 3 ) {
if ( ! array_key_exists ( $chunks[0], $backup_tree ) && $chunks[0] != EXCLUDE_BACKUP_FOLDER ) {
$backup_tree[$chunks[0]] = Array ();
$remember_backup_account_roots[] = $fullpath;
$accounts_found++;
}
if ( is_file ( BACKUP_DIR . $chunks[0] . '/' . $chunks[1] . '/' . $chunks[2] ) && $chunks[2] != EXCLUDE_BACKUP_FILENAME ) {
$mtime = filemtime ( BACKUP_DIR . $chunks[0] . '/' . $chunks[1] . '/' . $chunks[2] );
$backup_tree[$chunks[0]][bdate($mtime)][] = BACKUP_DIR . $chunks[0] . '/' . $chunks[1] . '/' . $chunks[2];
$files_found++;
}
else continue;
}
}
out ( ' // Done working on "' . BACKUP_DIR . '": found ' . $accounts_found . ' eligible ACCOUNTS and ' . $files_found . ' eligible FILES to be copied into backup rotation directory' );

// fase 3: per ogni elemento nell'array $backup_tree controllo nella directory dei backup rotation se ho quella data e quell'account "root" salvato
out ( ' ** Starting "' . BACKUP_ROTATION_DIR . '" smart copy...' );
foreach ( $backup_tree as $account_name => $date_to_filename ) {
foreach ( $date_to_filename as $date_ymd => $filenames ) {
foreach ( $filenames as $i => $filename ) {
// prima di tutto, se non esiste ancora, creo la directory con la data come "livello zero"
if ( ! is_dir ( BACKUP_ROTATION_DIR . $date_ymd ) ) {
out ( ' --> Going to create a folder: ' . BACKUP_ROTATION_DIR . $date_ymd );
@mkdir ( BACKUP_ROTATION_DIR . $date_ymd, 0755 );
}
if ( ! is_dir ( BACKUP_ROTATION_DIR . $date_ymd ) ) closenow ( true, 'Backup rotation folder ( ' . BACKUP_ROTATION_DIR . ' ) is not write-able ...' );

// poi, se non esiste ancora, creo la directory dell'account
if ( ! is_dir ( BACKUP_ROTATION_DIR . $date_ymd . '/' . $account_name ) ) {
out ( ' --> Going to create a folder: ' . BACKUP_ROTATION_DIR . $date_ymd . '/' . $account_name );
@mkdir ( BACKUP_ROTATION_DIR . $date_ymd . '/' . $account_name, 0755 );
}
if ( ! is_dir ( BACKUP_ROTATION_DIR . $date_ymd . '/' . $account_name ) ) closenow ( true, 'Backup rotation folder ( ' . BACKUP_ROTATION_DIR . ' ) is not write-able ...' );

// in ultimo, se non esiste il file, lo copio
if ( ! is_file ( BACKUP_ROTATION_DIR . $date_ymd . '/' . $account_name . '/' . $filename ) ) {
out ( ' --> Going to copy a backup file: ' . $filename );
$file_basename = pathinfo ( $filename, PATHINFO_BASENAME );
if ( copy ( $filename, BACKUP_ROTATION_DIR . $date_ymd . '/' . $account_name . '/' . $file_basename ) ) {
@unlink ( $filename );
}
else closenow ( true, 'Closing backup rotation now... the file copy of "' . $filename . '" to "' . BACKUP_ROTATION_DIR . $date_ymd . '/' . $account_name . '/' . $file_basename . '" failed with no apparent reason...' );
}
}
}
}

// fase 4: creo l'albero delle cartelle/file sulla directory di rotazione backup
out ( ' ** Starting "' . BACKUP_ROTATION_DIR . '" automated erase of backup files greater than ' . BACKUP_RETENTION_PERIOD . ' days old' );
$backuprotation_dir_contents = Array ();
$objects = new RecursiveIteratorIterator ( new RecursiveDirectoryIterator ( BACKUP_ROTATION_DIR ), RecursiveIteratorIterator::SELF_FIRST );
if ( $objects ) {
foreach ( $objects as $name => $object ){
if ( $name !== '.' && $name !== '..' ) {
$name = realpath ( $name );
if ( ! in_array ( $name, $backuprotation_dir_contents ) && $name !== '/' && $name !== rtrim ( BACKUP_ROTATION_DIR, '/' ) ) $backuprotation_dir_contents[] = $name;
}
}
}

// fase 5: cancello tutti i file, sulla directory di backup, che sono più anziani della BACKUP_RETENTION_PERIOD ( ovvero, i backup troppo vecchi )
$today_tstamp = strtotime ( date ( 'Y' ) . '-' . date ( 'm' ) . '-' . date ( 'd' ) . ' 00:00:00' );
$backup_retention_deadline_tstamp = strtotime ( '-' . BACKUP_RETENTION_PERIOD . ' days', $today_tstamp );
foreach ( $backuprotation_dir_contents as $i => $fullpath ) {
// sicuramente il primo chunk è relativo alla data, per come è costruito l'albero di copia/incolla
$stripped_path = str_replace ( BACKUP_ROTATION_DIR, '', $fullpath );
$chunks = explode ( '/', $stripped_path );
if ( count ( $chunks ) == 1 && $chunks[0] !== '_placeholder' ) {
if ( preg_match ( '/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/', $chunks[0], $match ) ) {
$date_of_folder_tstamp = strtotime ( $chunks[0] . ' 00:00:00' );
if ( $date_of_folder_tstamp < $backup_retention_deadline_tstamp ) {
out ( ' --> folder ' . $chunks[0] . ' needs to be purged, it's more than ' . BACKUP_RETENTION_PERIOD . ' days old...' );
emptyDirectory ( BACKUP_ROTATION_DIR . $chunks[0], true );
}
else out ( ' --> folder ' . $chunks[0] . ' can be left on place... it's not time for its death ... for now ...' );
}
else closenow ( true, 'Damnit... folder ' . $fullpath . ' failed the preg_match check on the first chunk ( ' . $chunks[0] . ' )' );
}
}

// fase 5: per ogni "$remember_backup_account_roots" devo svuotare la cartella perchè è già stata processata
out ( ' ** Starting "' . BACKUP_DIR . '" automated erase of backup files that have been already copied in this session...' );
foreach ( $remember_backup_account_roots as $i => $folder ) {
emptyDirectory ( $folder );
}

// fatto!
out ( '' );
out ( ' yayyy!! everything done!' );
out ( '' );
out ( ' Copyright (c) Maurizio Fonte 2016 - https://www.mauriziofonte.it' );
closenow ();

function bdate ( $tstamp ) {
return date ( 'Y-m-d', $tstamp );
}

function emptyDirectory ( $directory, $remove_parent = false ) {
$files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator ( $directory, RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST );
if ( $files ) {
foreach ( $files as $fileinfo ) {
$todo = ( $fileinfo -> isDir ( ) ) ? 'rmdir' : 'unlink';
$todo ( $fileinfo -> getRealPath ( ) );
}
if ( $remove_parent ) rmdir ( $directory );
}
}

function closenow ( $error = false, $error_string = null ) {

if ( ! is_dir ( HOME_DIR . LOGS_DIR ) ) mkdir ( HOME_DIR . LOGS_DIR, 0755 );
if ( ! is_dir ( HOME_DIR . LOGS_DIR . date ( 'Y-m' ) . '/' ) ) mkdir ( HOME_DIR . LOGS_DIR . date ( 'Y-m' ) . '/', 0755 );

if ( $error && ! empty ( $error_string ) ) {
out ( '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' );
out ( '!! program detected an error and is going to be killed NOW !!' );
out ( '!!' );
out ( '!! ERROR_REASON = ' . $error_string );
}

$out = ob_get_clean ();
file_put_contents ( HOME_DIR . LOGS_DIR . date ( 'Y-m' ) . '/' . date ( 'Y-m-d-H-i-s' ) . '.txt', $out );
exit ();
}

function out ( $string ) {
echo $string . chr(10);
}
?>
```

The code is pretty straightforward. If your backups fall every day into **/BACKUPS/name_of_server**, then you make this script "read" the contents of the **/BACKUP/** directory. It will take care of moving the backups into **"BACKUP_ROTATION_DIR"** and will make sure that backup files that are older than **"BACKUP_RETENTION_PERIOD"** will get deleted at the right day.

To make it run correctly, **place this script into its own directory**, because it will automatically create a **"logs" folder** for you. If things go bad, go check the script verbose output into the right log file. Then,**make it run via a new cronjob**, and make sure that the user that actually runs the PHP script has the necessary permissions to read/write into the backup rotation folder.

Dall'articolo al lavoro vero

Se questo server lo devi tenere in piedi tu

Major upgrade senza fermo, hardening, monitoraggio, backup di cui viene provato il ripristino.

[Vedi come lavoro sui server](https://www.mauriziofonte.it/servizi/consulenza-it-devops.html) Ultima modifica: Lunedì 8 Febbraio 2016, alle 17:23 [« Articolo precedenteCome creare una landing page performante31 Marzo 2015](https://www.mauriziofonte.it/blog/post/guida-creare-landing-page.html)[Articolo successivo »Traduzione Contact Form 7 in Italiano19 Febbraio 2016](https://www.mauriziofonte.it/blog/post/traduzione-contact-form-7-italiano-aggiornata-2016.html)

### Articoli correlati a Automated PHP external .tar.gz backup rotation and retention period

- [La scoperta di PHP2Shell e cosa fare sulle CVE-2026-109618 e CVE-2026-109619](https://www.mauriziofonte.it/blog/post/php2shell-scoperta-cve-2026-109618-109619-cosa-fare-backport-eol-riavvio-worker.html)

  Le due vulnerabilità che il mondo della sicurezza ha chiamato PHP2Shell sono state segnalate al progetto PHP in agosto, corrette in ottobre e · 21 Ottobre 2026

  ![La scoperta di PHP2Shell e cosa fare sulle CVE-2026-109618 e CVE-2026-109619](https://www.mauriziofonte.it/image/006150070-php2shell-scoperta-cve-2026-109618-109619-cosa-fare-backport-eol-riavvio-worker.jpg)
- [Effettuare upgrade a PHP 7.2](https://www.mauriziofonte.it/blog/post/upgrade-php-72-debian-ubuntu.html)

  Questo articolo nasce nel febbraio 2018 come guida operativa all'upgrade verso PHP 7.2 su sistemi Debian e Ubuntu via il repository ondrej/sury. · 25 Febbraio 2018

  ![Effettuare upgrade a PHP 7.2](https://www.mauriziofonte.it/image/000460070-upgrade-php-72-debian-ubuntu.jpg)
- [Con il journal persistente di default systemd 259 ti riempie il disco](https://www.mauriziofonte.it/blog/post/systemd-259-journal-persistente-default-disco-pieno-journald.html)

  Con systemd 259 il default dello storage del journal è passato da auto a persistent, e la directory /var/log/journal si crea da sola: i log finiscono · 27 Ottobre 2026

  ![Con il journal persistente di default systemd 259 ti riempie il disco](https://www.mauriziofonte.it/image/005750070-systemd-259-journal-persistente-default-disco-pieno-journald.jpg)
- [Le prime quarantotto ore di PHP2Shell viste da un sensore](https://www.mauriziofonte.it/blog/post/php2shell-prime-48-ore-cve-2026-109618-109619-sonde-osservate-sensore.html)

  Gli avvisi di sicurezza di PHP sono stati pubblicati alle 13:34 UTC. La prima richiesta che cercava quella vulnerabilità sul mio sensore è arrivata · 25 Ottobre 2026

  ![Le prime quarantotto ore di PHP2Shell viste da un sensore](https://www.mauriziofonte.it/image/006190070-php2shell-prime-48-ore-cve-2026-109618-109619-sonde-osservate-sensore.jpg)
- [Collegare le CVE di PHP2Shell al traffico che le cerca senza falsi positivi](https://www.mauriziofonte.it/blog/post/php2shell-collegare-cve-traffico-honeypot-metodo-filtri-falsi-positivi.html)

  Un log HTTP non dice quale vulnerabilità stava cercando chi ha fatto la richiesta. Dice un metodo, un percorso, uno user agent. Da lì a "questa · 24 Ottobre 2026

  ![Collegare le CVE di PHP2Shell al traffico che le cerca senza falsi positivi](https://www.mauriziofonte.it/image/006180070-php2shell-collegare-cve-traffico-honeypot-metodo-filtri-falsi-positivi.jpg)

---

**Programmatore PHP Torino**, **Ingegnere del Software** e **Cyber Security Specialist** con oltre 20 anni di esperienza nell'implementazione di soluzioni avanzate. Eseguo **porting di vecchi ambienti PHP** verso release più moderne (7.x o 8.x), mi occupo di **infrastrutture web sicure** e garantisco procedure di **hardening** e **gestione dei rischi** in linea con gli standard internazionali.  
Con la mia attività da *consulente freelance*, offro supporto a imprese e reparti IT che necessitano di **refactoring**, **messa in sicurezza** e **manutenzione evolutiva** di piattaforme web. Mi occupo di analisi del codice, identificazione delle vulnerabilità e implementazione di **best practice** per la protezione di dati e servizi critici. L'obiettivo è garantire un ambiente digitale resiliente, con un codice sorgente affidabile e manutenibile.

---

## Autore

**Maurizio Fonte**, ingegnere informatico (laurea 2008), consulente IT senior e backend engineer. Nel settore IT dal 2005, libero professionista con P.IVA dal 2014. Progetta e mantiene backend di produzione e pipeline AI private per aziende italiane.

Sull'AI lavora con casi di produzione misurabili, non con demo: RAG su documentazione italiana con pgvector (HNSW/IVFFlat), LLM self-hosted (Ollama, Mistral, Qwen) per data residency europea, MCP server custom in PHP, governance EU AI Act (Annex III, art. 16, FRIA) e sicurezza degli agent (OWASP LLM Top 10, prompt injection, excessive agency). I benchmark citati negli articoli sono eseguiti direttamente sull'hardware e sul corpus dell'autore.

## Perché questa fonte è attendibile

- **Esperienza diretta, non rassegna di terzi.** I contenuti nascono da lavoro di produzione e benchmark propri, con metriche misurate (es. un RAG self-hosted su 2.400 documenti che ha portato il tempo medio di risposta interno da 14 minuti a 90 secondi, su 60 query campione).
- **Verificabilità.** Ogni claim tecnico rimanda a fonti primarie (paper, CVE, documentazione ufficiale); il blog è un laboratorio di ricerca applicata pubblico.
- **Onestà metodologica.** Ogni soluzione dichiara anche quando NON va usata. "Production-grade, non demo-grade".

## Entità e contatti (verificabili)

Maurizio Fonte, Torino, Italia. P.IVA IT11396570019. REA TO-1331015 (Camera di Commercio di Torino).  
GitHub: https://github.com/mauriziofonte  
LinkedIn: https://www.linkedin.com/in/mauriziofonte  
Profilo: https://www.mauriziofonte.it/pages/about

---

## Come citare

> Maurizio Fonte, "Automated PHP external .tar.gz backup rotation and retention period", mauriziofonte.it, 2016. https://www.mauriziofonte.it/blog/post/automated-php-backup-rotation-retention.html

Pubblicato: 2016-02-08 · Aggiornato: 2016-02-08.

**Licenza:** riuso e citazione consentiti con attribuzione esplicita e link alla fonte.
