---
title: "Backup Laravel su VPS che falliscono da tre mesi senza che nessuno se ne accorga: diagnosi e strategia di ripristino"
description: "Backup Laravel su VPS falliti in silenzio per mesi: diagnosi delle cause, ripristino con spatie/laravel-backup, test di restore e monitoring automatico."
canonical: "https://www.mauriziofonte.it/blog/post/backup-fallito-strategia-urgente-laravel-vps.html"
author: "Maurizio Fonte"
date: "2025-07-03T07:01:59+02:00"
modified: "2026-04-14T17:48:55+02:00"
section: "Business Continuity"
tags: ["Business Continuity", "VPS & Hosting"]
license: "Riuso e citazione consentiti con attribuzione e link alla fonte"
---

[Business Continuity](https://www.mauriziofonte.it/blog/cat/business_continuity.html) [VPS & Hosting](https://www.mauriziofonte.it/blog/cat/vps.html)

# [Backup Laravel su VPS che falliscono da tre mesi senza che nessuno se ne accorga: diagnosi e strategia di ripristino](https://www.mauriziofonte.it/blog/post/backup-fallito-strategia-urgente-laravel-vps.html)

Giovedì 3 Luglio 2025, alle 07:01 ![Backup Laravel su VPS che falliscono da tre mesi senza che nessuno se ne accorga: diagnosi e strategia di ripristino](https://www.mauriziofonte.it/image/001950200-backup-fallito-strategia-urgente-laravel-vps.jpg)

A settembre 2025 un cliente mi ha chiesto di ripristinare un database da backup perché un operatore del backoffice aveva cancellato per errore 1.400 record dalla tabella ordini del gestionale - un'operazione di "pulizia" fatta con una query DELETE senza WHERE sufficientemente restrittivo. Il gestionale era un'applicazione Laravel 10 su un VPS Contabo VPS L (10 vCPU, 30 GB RAM, 800 GB SSD), una PMI marchigiana del settore calzaturiero con circa 45 dipendenti e un fatturato di 3 milioni di euro. Il backup era configurato con [spatie/laravel-backup](https://spatie.be/docs/laravel-backup/v10/introduction), il pacchetto standard che la maggior parte dei progetti Laravel usa - e che funziona egregiamente quando è configurato e monitorato correttamente. Il problema: l'ultimo backup riuscito risaliva a tre mesi prima. Per 92 giorni, il cron job che doveva eseguire `php artisan backup:run` ogni notte alle 2 aveva fallito silenziosamente, e nessuno lo sapeva.

I 1.400 record cancellati per errore coprivano ordini degli ultimi due mesi. Il backup più recente disponibile era di tre mesi fa - un mese prima del primo ordine cancellato. Quei dati erano irrecuperabili dal backup. Li abbiamo recuperati parzialmente dal binlog di MySQL (che fortunatamente era abilitato), ma l'operazione è costata due giorni di lavoro e il risultato non era garantito. Il costo totale dell'incidente - tra il mio intervento, il tempo del personale interno per ricostruire i dati mancanti, e gli ordini in ritardo per la confusione generata - è stato stimato dal titolare in circa 12.000 euro. Il costo di un sistema di backup che funziona e che avvisa quando non funziona: circa 15 euro al mese di storage S3 e mezza giornata di configurazione iniziale.

## Perché i backup su VPS unmanaged falliscono in silenzio e cosa fare per scoprirlo prima che sia troppo tardi?

Nella mia esperienza su decine di VPS che ho preso in carico per PMI, i backup falliti sono la norma, non l'eccezione. Il pattern è sempre lo stesso: qualcuno configura il backup il giorno dell'installazione, funziona per qualche mese, poi qualcosa cambia - si riempie il disco, scade un token, viene aggiornato un pacchetto - e il backup smette di funzionare. Ma l'applicazione continua ad andare, nessuno guarda i log, e il problema viene scoperto solo quando serve un restore.

Le cause di fallimento che trovo più frequentemente, in ordine di probabilità:

**Disco pieno.** Il backup locale riempie la partizione prima di essere trasferito su storage remoto. Su un VPS con 800 GB di SSD e un database da 4 GB, il dump SQL compresso pesa circa 600 MB. Se la retention locale è 7 giorni, servono 4,2 GB solo per i dump del database, più lo spazio per i file dell'applicazione. Se lo storage locale si riempie per altre ragioni (log non ruotati, upload degli utenti, file temporanei), il backup fallisce con un errore di scrittura.

**Credenziali di storage remoto scadute.** Su S3, Wasabi, Backblaze B2 o qualunque object storage, le chiavi API possono scadere o essere revocate. Se il team cambia provider, aggiorna le credenziali dell'applicazione ma dimentica quelle del backup, il backup locale viene creato ma non trasferito - e quando il disco locale si riempie, anche quello smette di funzionare.

**Timeout su database grandi.** Il `mysqldump` che funzionava su un database da 500 MB al primo anno, al terzo anno lavora su un database da 8 GB e va in timeout. Il processo PHP che esegue `backup:run` ha un `max_execution_time` che non è stato aggiornato, o il backup viene eseguito nella stessa finestra di un processo batch notturno che blocca le tabelle.

**Notification channel rotto.** Sul gestionale marchigiano, il canale di notifica era un webhook Slack configurato dall'ex sviluppatore - che nel frattempo aveva lasciato l'azienda e il workspace Slack era stato riorganizzato. Il webhook restituiva 404, le notifiche di fallimento non arrivavano a nessuno, e il backup falliva in un silenzio totale.

**Permessi filesystem cambiati.** Dopo un aggiornamento del sistema operativo o un cambio di configurazione di PHP-FPM, l'utente `www-data` non ha più i permessi di scrittura sulla directory di backup. Il cron gira come `www-data` (tramite lo scheduler Laravel), il dump MySQL richiede lettura dal socket e scrittura su disco - se manca uno dei due permessi, il backup fallisce.

**Lock non rilasciati.** `spatie/laravel-backup` usa un lock per evitare esecuzioni concorrenti. Se un backup precedente è andato in timeout o è stato killato, il lock file rimane e tutti i backup successivi vengono saltati con il messaggio "Another backup process is already running." Un file bloccante di 0 byte che impedisce tutti i backup futuri fino a quando qualcuno non lo rimuove manualmente.

Stai cercando un **Consulente Informatico** esperto per mettere in sicurezza i backup della tua applicazione Laravel su VPS? Nel mio [profilo professionale](https://www.mauriziofonte.it/pages/about) trovi l'esperienza concreta su backup, disaster recovery e gestione di VPS Hetzner, Contabo, OVH e Digital Ocean per PMI.

## Diagnosi: capire cosa è andato storto e da quando

Prima di toccare qualunque configurazione, devo capire l'entità del danno - da quanto tempo il backup fallisce e qual è l'ultimo backup utilizzabile. I comandi diagnostici:

```bash
# Ultimo backup riuscito (se spatie/laravel-backup è configurato)
php artisan backup:list

# Log specifici del backup (gli ultimi 50 errori)
grep -i "backup" storage/logs/laravel.log | grep -i "error\|fail\|exception" | tail -50

# Spazio disco disponibile
df -h /

# Dimensione del database attuale
mysql -e "SELECT table_schema AS 'Database',
    ROUND(SUM(data_length + index_length) / 1024 / 1024, 1) AS 'Size_MB'
FROM information_schema.tables
WHERE table_schema = 'gestionale'
GROUP BY table_schema;"

# Stato del cron (il backup è schedulato?)
crontab -l | grep -i artisan
# oppure, se usa lo scheduler Laravel:
php artisan schedule:list | grep backup
```

Sul gestionale marchigiano, `backup:list` mostrava l'ultimo backup di 92 giorni prima. Il log di Laravel conteneva 92 righe con `BackupHasFailed` - una per notte - ognuna con l'eccezione `Could not create backup because: The backup temporary directory could not be created at /tmp/laravel-backup-temp`. La causa: un job batch notturno di importazione dati scriveva file temporanei in `/tmp` senza pulirli, e dopo tre mesi `/tmp` era pieno. Il backup provava a creare la sua directory temporanea, falliva, e loggava l'errore. Nessuno leggeva il log. Il webhook Slack era rotto. L'alert email non era mai stato configurato.

## Ripristino: rimettere in piedi la pipeline di backup

Il fix del problema immediato è stato banale: pulire `/tmp` e aggiungere un cron di pulizia per il job batch. Ma il vero intervento è stato ristrutturare l'intera pipeline di backup per renderla affidabile e verificabile.

La configurazione di `spatie/laravel-backup` che uso come baseline su ogni progetto Laravel in produzione:

```php
// config/backup.php - sezione critica
'backup' => [
    'name' => env('APP_NAME', 'laravel-backup'),
    'source' => [
        'databases' => ['mysql'],
        'files' => [
            'include' => [base_path()],
            'exclude' => [
                base_path('vendor'),
                base_path('node_modules'),
                storage_path('logs'),
                storage_path('framework/cache'),
            ],
        ],
    ],
    'destination' => [
        'disks' => ['s3'],  // MAI solo locale
    ],
    'temporary_directory' => storage_path('app/backup-temp'),
],

'cleanup' => [
    'strategy' => \Spatie\Backup\Tasks\Cleanup\Strategies\DefaultStrategy::class,
    'default_strategy' => [
        'keep_all_backups_for_days' => 7,
        'keep_daily_backups_for_days' => 30,
        'keep_weekly_backups_for_weeks' => 12,
        'keep_monthly_backups_for_months' => 12,
        'keep_yearly_backups_for_years' => 3,
        'delete_oldest_backups_when_using_more_megabytes_than' => 5000,
    ],
],

'monitor_backups' => [
    [
        'name' => env('APP_NAME'),
        'disks' => ['s3'],
        'health_checks' => [
            \Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumAgeInDays::class => 1,
            \Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumStorageInMegabytes::class => 5000,
        ],
    ],
],
```

La notifica è l'aspetto più importante di tutta la configurazione - più del backup stesso. Un backup che fallisce e genera un alert è un problema risolvibile in 10 minuti. Un backup che fallisce in silenzio per tre mesi è una catastrofe in attesa. La configurazione delle notifiche in `config/backup.php` deve coprire sia il successo che il fallimento, su almeno due canali indipendenti (email + Telegram, o email + Slack):

```php
// config/backup.php - sezione notifications
'notifications' => [
    'notifications' => [
        \Spatie\Backup\Notifications\Notifications\BackupHasFailedNotification::class => ['mail', 'telegram'],
        \Spatie\Backup\Notifications\Notifications\UnhealthyBackupWasFoundNotification::class => ['mail', 'telegram'],
        \Spatie\Backup\Notifications\Notifications\BackupWasSuccessfulNotification::class => ['mail'],
    ],
    'mail' => [
        'to' => ['it@azienda.it', 'titolare@azienda.it'],
    ],
],
```

L'invio della notifica di successo via email (senza Telegram, per non generare rumore) serve come heartbeat: se non ricevi la mail di "backup riuscito" alle 2:30 di notte, qualcosa non va - e lo sai la mattina dopo, non tre mesi dopo. L'invio del fallimento su due canali è ridondanza: se la mail finisce in spam (cosa che succede), il Telegram arriva comunque.

Tre decisioni critiche in questa configurazione. Prima: `destination.disks` contiene solo `s3`, mai il disco locale. Un backup sullo stesso server che stai proteggendo non è un backup - è una copia che muore insieme all'originale se il disco si guasta o il server viene compromesso. Seconda: `temporary_directory` è in `storage_path('app/backup-temp')` anziché in `/tmp`, per evitare conflitti con altri processi che scrivono in `/tmp`. Terza: il `monitor_backups.health_checks` con `MaximumAgeInDays => 1` fa scattare un alert se il backup più recente ha più di 24 ore - il che significa che un singolo fallimento notturno genera una notifica la mattina dopo, non tre mesi dopo.

## La regola che nessuno rispetta: testare il restore

Un backup che non è mai stato testato con un restore non è un backup - è una speranza. [La documentazione di Spatie su monitoring e health check dei backup](https://spatie.be/docs/laravel-backup/v10/monitoring-the-health-of-all-backups/overview) copre il lato automatico, ma il test di restore è un'operazione che va fatta manualmente almeno una volta al mese.

Il pacchetto [stefanzweifel/laravel-backup-restore](https://github.com/stefanzweifel/laravel-backup-restore) è il complemento naturale di spatie/laravel-backup per automatizzare il restore in ambiente di staging:

```bash
# Restore dell'ultimo backup in un database di test
php artisan backup:restore --disk=s3 --backup=latest --database=mysql_staging

# Verifica integrità: conteggio record sulle tabelle principali
mysql -u staging_user staging_db -e "
SELECT 'orders' as tbl, COUNT(*) as cnt FROM orders
UNION ALL
SELECT 'customers', COUNT(*) FROM customers
UNION ALL
SELECT 'order_items', COUNT(*) FROM order_items;"
```

Sul gestionale marchigiano, dopo aver ripristinato la pipeline, ho schedulato un test di restore mensile automatico che gira in un container Docker dedicato, ripristina il backup su un database temporaneo, verifica il conteggio delle tabelle principali, e se il risultato è ok cancella tutto. Se il restore fallisce o i conteggi sono anomali, parte un alert. Il costo computazionale è trascurabile - un dump da 4 GB si ripristina in meno di 3 minuti - e la certezza che il backup funziona davvero vale infinitamente di più di qualunque dashboard di monitoring.

Lo script che schedulo per il test mensile automatico:

```bash
#!/bin/bash
set -euo pipefail

# Scarica l'ultimo backup da S3
LATEST=$(aws s3 ls s3://backup-bucket/gestionale/ --recursive | sort | tail -1 | awk '{print $4}')
aws s3 cp "s3://backup-bucket/$LATEST" /tmp/restore-test.zip

# Restore in database temporaneo
unzip -o /tmp/restore-test.zip -d /tmp/restore-test/
DUMP=$(find /tmp/restore-test/ -name "*.sql" | head -1)
mysql -u restore_test restore_test_db < "$DUMP"

# Verifica conteggi
ORDERS=$(mysql -sN restore_test_db -e "SELECT COUNT(*) FROM orders")
CUSTOMERS=$(mysql -sN restore_test_db -e "SELECT COUNT(*) FROM customers")

if [ "$ORDERS" -lt 100 ] || [ "$CUSTOMERS" -lt 50 ]; then
    curl -s "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
        -d chat_id="${CHAT_ID}" \
        -d text="RESTORE TEST FALLITO: orders=$ORDERS customers=$CUSTOMERS"
    exit 1
fi

# Pulizia
mysql -e "DROP DATABASE restore_test_db; CREATE DATABASE restore_test_db;"
rm -rf /tmp/restore-test /tmp/restore-test.zip

echo "Restore test OK: orders=$ORDERS customers=$CUSTOMERS"
```

Le soglie (100 ordini, 50 clienti) sono deliberatamente basse - non verificano che il backup sia completo al record, ma che non sia vuoto o corrotto. Un backup che si decomprime e si importa ma contiene zero record è tecnicamente "riuscito" ma operativamente inutile. Il check sui conteggi minimi cattura questo scenario.

Per chi vuole un quadro completo sulla strategia di backup a livello di VPS - non solo il livello applicativo ma anche filesystem, snapshot del provider e regola 3-2-1-1-0 - ho scritto due guide dedicate: una sulle [strategie avanzate di backup per VPS Hetzner, OVH, Contabo, Digital Ocean e Aruba](https://www.mauriziofonte.it/blog/post/backup-vps-unmanaged-strategie-avanzate.html) e una sul [Disaster Recovery Plan con RPO/RTO verificabili](https://www.mauriziofonte.it/blog/post/disaster-recovery-plan-importanza-azienda.html) che copre l'aspetto di compliance NIS2.

Il risultato sul gestionale marchigiano: dopo la ristrutturazione della pipeline, i backup funzionano ininterrottamente da cinque mesi, il test di restore mensile non ha mai fallito, e l'alert Telegram arriva ogni mattina alle 2:28 con "Backup riuscito - 612 MB su S3." Quando tre settimane dopo il fix un aggiornamento di Composer ha rotto una dipendenza di `spatie/laravel-backup`, l'alert di fallimento è arrivato alle 2:04 della stessa notte - e il fix è stato applicato la mattina dopo, prima che il titolare se ne accorgesse. La differenza tra tre mesi di backup silenziosamente falliti e un fix applicato in 8 ore è tutta nella configurazione del monitoring - non nel backup stesso.

Se la tua applicazione Laravel ha un backup configurato ma non hai mai verificato che funzioni - o se non sai dire con certezza quando è stato l'ultimo backup riuscito - hai un problema che non puoi permetterti di ignorare. Il momento peggiore per scoprire che il backup non funziona è quando ne hai bisogno. [Contattami](https://www.mauriziofonte.it/pages/contattami) se vuoi un audit della tua pipeline di backup: in mezza giornata verifico lo stato dei backup, testo un restore su ambiente isolato, e configuro il monitoring per sapere immediatamente quando qualcosa smette di funzionare.

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: Martedì 14 Aprile 2026, alle 17:48 [« Articolo precedenteRedis esposto senza password su un VPS Hetzner: come un cryptominer ha messo in ginocchio un'applicazione Laravel2 Luglio 2025](https://www.mauriziofonte.it/blog/post/emergenza-redis-laravel-vps-unmanaged.html)[Articolo successivo »PHP-FPM che crasha sotto carico su VPS: come ho diagnosticato un OOM killer silenzioso su un portale Laravel con 200 utenti concorrenti3 Luglio 2025](https://www.mauriziofonte.it/blog/post/ripristino-crash-php-fpm-laravel-vps.html)

### Articoli correlati a Backup Laravel su VPS che falliscono da tre mesi senza che nessuno se ne accorga: diagnosi e strategia di ripristino

- [Monitoring proattivo per Laravel su VPS unmanaged: come evitare di scoprire un downtime dalla telefonata del cliente](https://www.mauriziofonte.it/blog/post/vps-monitoring-laravel-prevenzione-downtime.html)

  Un VPS Contabo con un e commerce B2B Laravel è rimasto fermo 60 ore dal venerdì pomeriggio al lunedì mattina perché il certificato SSL era scaduto · 30 Giugno 2025

  ![Monitoring proattivo per Laravel su VPS unmanaged: come evitare di scoprire un downtime dalla telefonata del cliente](https://www.mauriziofonte.it/image/001890070-vps-monitoring-laravel-prevenzione-downtime.jpg)
- [Subentro su Laravel 10 con Horizon fermo e scheduler silente: come ho recuperato un SaaS torinese di fleet management nei primi cinque giorni dopo la chiusura improvvisa del team di sviluppo esterno](https://www.mauriziofonte.it/blog/post/subentro-progetto-laravel-unmanaged-senza-sviluppatore.html)

  Quando il team di sviluppo esterno chiude senza preavviso, il codice Laravel continua a girare ma i pezzi che non vedi scheduler, Horizon, queue · 18 Giugno 2025

  ![Subentro su Laravel 10 con Horizon fermo e scheduler silente: come ho recuperato un SaaS torinese di fleet management nei primi cinque giorni dopo la chiusura improvvisa del team di sviluppo esterno](https://www.mauriziofonte.it/image/001810070-subentro-progetto-laravel-unmanaged-senza-sviluppatore.jpg)
- [Ottimizzare cron job su VPS unmanaged: tecniche avanzate per prevenire colli di bottiglia e downtime](https://www.mauriziofonte.it/blog/post/ottimizzare-cron-job-vps-unmanaged.html)

  Un VPS Hetzner con Laravel e 23 cron job tutti schedulati alle 02:00: load average a 47, MySQL in lock wait, import listini che si sovrappongono · 10 Luglio 2025

  ![Ottimizzare cron job su VPS unmanaged: tecniche avanzate per prevenire colli di bottiglia e downtime](https://www.mauriziofonte.it/image/002050070-ottimizzare-cron-job-vps-unmanaged.jpg)
- [Migrare da hosting condiviso a VPS per Laravel: perché, quando e come farlo senza perdere dati](https://www.mauriziofonte.it/blog/post/transizione-server-condiviso-vps-laravel.html)

  Quando un'applicazione Laravel cresce oltre le capacità di un hosting condiviso, la migrazione verso un VPS diventa inevitabile. Il problema · 23 Giugno 2025

  ![Migrare da hosting condiviso a VPS per Laravel: perché, quando e come farlo senza perdere dati](https://www.mauriziofonte.it/image/001840070-transizione-server-condiviso-vps-laravel.jpg)
- [Backup incrementale di MySQL con xtrabackup: recovery point granulare senza blocchi](https://www.mauriziofonte.it/blog/post/backup-incrementale-mysql-xtrabackup-recovery-point-senza-blocchi.html)

  mysqldump su database da 200GB richiede 4 ore e blocca le query durante l'esecuzione. Con Percona XtraBackup ho configurato backup incrementali · 26 Marzo 2026

  ![Backup incrementale di MySQL con xtrabackup: recovery point granulare senza blocchi](https://www.mauriziofonte.it/image/003110070-backup-incrementale-mysql-xtrabackup-recovery-point-senza-blocchi.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, "Backup Laravel su VPS che falliscono da tre mesi senza che nessuno se ne accorga: diagnosi e strategia di ripristino", mauriziofonte.it, 2025. https://www.mauriziofonte.it/blog/post/backup-fallito-strategia-urgente-laravel-vps.html

Pubblicato: 2025-07-03 · Aggiornato: 2026-04-14.

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