SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-07

SQLite in produzione su un VPS: quando conviene

SQLite è adatto alla maggior parte delle piccole app su un VPS: scopri WAL, busy_timeout, replica con Litestream e i limiti che richiedono un altro database.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

Quando SQLite è il database di produzione adatto su un VPS

Eseguire SQLite in produzione su un VPS è la scelta corretta per la maggior parte delle applicazioni di piccole dimensioni, e il motivo è semplice: un processo su una macchina che scrive in un unico file non ha bisogno di un server di database. Non c'è alcun demone da supervisionare, nessuna porta da filtrare con il firewall, nessuna password da ruotare e nessuna seconda macchina da mantenere attiva. Una query è una chiamata di funzione, non un round trip di rete, quindi una pagina che esegue quaranta query richiede quaranta chiamate di funzione.

Il limite è specifico e concreto. SQLite consente un solo writer alla volta sull'intero file del database e il file non può essere condiviso tra due macchine. Entrambi i limiti sono accettabili per un singolo VPS che esegue una singola applicazione. Diventano entrambi bloccanti non appena l'applicazione supera questo modello. Questa guida illustra le impostazioni che rendono SQLite sicuro su un server, il backup continuo con Litestream e il punto in cui è necessario fermarsi.

Installare prima lo strumento da riga di comando. Tutti i comandi riportati di seguito sono stati eseguiti su Ubuntu 24.04.

sudo apt update
sudo apt install -y sqlite3
sqlite3 --version

Il comando stampa una versione che inizia con 3., seguita da una data di build e da un hash del codice sorgente. Ubuntu 24.04 include SQLite 3.45.1 a luglio 2026. Probabilmente l'applicazione non usa questo binario: la maggior parte dei runtime dei linguaggi include una propria copia della libreria SQLite, spesso più recente. Verificare quindi la versione restituita dal driver del database prima di fare affidamento su una funzionalità recente.

Perché la modalità WAL è la prima impostazione da modificare

Per impostazione predefinita, SQLite usa un rollback journal. Prima di modificare una pagina, copia la pagina originale in un file -journal, quindi modifica direttamente il database. Per eseguire questa operazione in sicurezza, acquisisce un lock esclusivo sull'intero file. Di conseguenza, ogni lettore attende mentre è in corso una scrittura. Su un laptop questo comportamento passa inosservato. Su un web server, una scrittura lenta rallenta ogni richiesta che accede al database.

La modalità WAL (write-ahead log) inverte l'ordine delle operazioni. Un writer aggiunge le nuove pagine a un file -wal separato e lascia invariato il database principale. I reader continuano a leggere il file principale nello snapshot acquisito all'inizio. In questo modo, i reader non bloccano il writer e il writer non blocca i reader. In seguito, un checkpoint copia le pagine accumulate nel WAL nel database principale. Questa singola modifica è il fattore principale che rende SQLite utilizzabile dietro un'applicazione web.

Attivare la modalità WAL e verificare che sia persistente

mkdir -p ~/app
sqlite3 ~/app/app.db "PRAGMA journal_mode=WAL;"

Il comando stampa wal. Questo output non è ornamentale. PRAGMA journal_mode restituisce la modalità effettiva del database, quindi una risposta delete indica che la modifica non è riuscita e che il database usa ancora il rollback journal.

La modalità WAL è persistente. È un flag nell'header del database, non un'impostazione della connessione. Per questo motivo va configurata una sola volta per ogni file di database e viene ereditata da tutte le connessioni successive, anche dopo un riavvio. Verificatelo usando una nuova connessione.

sqlite3 ~/app/app.db "PRAGMA journal_mode;"

Ora create una tabella e controllate quali file vengono creati su disco.

sqlite3 ~/app/app.db <<'SQL'
CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT NOT NULL);
INSERT INTO notes (body) VALUES ('first row');
SQL
ls -l ~/app/

Ora sono presenti tre file: app.db, app.db-wal e app.db-shm. Il file -wal contiene le pagine con commit che non sono ancora state sottoposte a checkpoint. Il file -shm è un indice di memoria condivisa che ogni connessione mappa, in modo che tutte concordino sul contenuto del WAL. Entrambi fanno parte del database e non sono file temporanei. Se copiate solo app.db mentre l'applicazione è in esecuzione, ottenete un file privo di tutti i commit più recenti. Se eliminate app.db e lasciate gli altri due file al loro posto, SQLite applicherà quelle pagine WAL obsolete al nuovo file creato con quel nome. È così che si corrompe un database vuoto quando si tenta di reimpostarlo.

Impostazioni di connessione necessarie per ogni applicazione in produzione

Solo journal_mode viene salvato nel database. Tutte le altre impostazioni riportate di seguito sono specifiche della connessione. L'applicazione deve quindi eseguirle su ogni connessione che apre, incluse tutte le connessioni create in background da un pool.

PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;

busy_timeout = 5000 indica a SQLite di continuare a ritentare l'accesso a un database bloccato per un massimo di 5000 millisecondi prima di restituire database is locked. Il valore predefinito è 0. Per impostazione predefinita, quindi, SQLite fallisce immediatamente quando due operazioni di scrittura si sovrappongono. Impostare questo singolo valore elimina la maggior parte degli errori di blocco attribuiti a SQLite.

synchronous = NORMAL è l'impostazione corretta in modalità WAL, ma è importante comprenderne il compromesso. Con FULL, SQLite chiama fsync sul WAL a ogni commit. Con NORMAL, esegue la sincronizzazione durante i checkpoint. La documentazione di SQLite è esplicita su ciò a cui si rinuncia: dopo un'interruzione dell'alimentazione o un riavvio forzato, le transazioni non sono più persistenti. La perdita di alimentazione non può corrompere il database; si perdono semplicemente gli ultimi commit che non erano ancora stati scritti sul disco. Su un VPS questo è generalmente il compromesso corretto, perché rimuove una chiamata a fsync dal percorso di ogni singola scrittura.

foreign_keys = ON è disattivato per impostazione predefinita per motivi di compatibilità con le versioni precedenti ed è specifico della connessione. Uno schema pieno di clausole REFERENCES non applica alcun vincolo finché ogni connessione non abilita questa impostazione.

Un'altra impostazione diventa importante solo in seguito. SQLite esegue automaticamente un checkpoint quando il WAL supera 1000 pagine. L'operazione viene eseguita dalla connessione che in quel momento completa una transazione. Questo comportamento è normalmente adeguato. Diventa rilevante quando è in esecuzione Litestream, perché Litestream deve poter controllare quando vengono eseguiti i checkpoint.

Perché database is locked si verifica ancora dopo aver impostato busy_timeout

Questo è l'errore che spinge gli utenti a tornare a Postgres e ha una causa precisa.

Un busy timeout installa un busy handler, ma SQLite non garantisce di chiamarlo.

Se SQLite determina che l'invocazione del busy handler potrebbe causare un deadlock, restituisce SQLITE_BUSY all'applicazione invece di invocare il busy handler.

Il deadlock che SQLite evita si verifica quando una transazione viene convertita. In SQLite, un BEGIN senza altre clausole significa BEGIN DEFERRED. Se la prima istruzione eseguita dopo è un SELECT, ci si trova in una transazione di lettura. Quando un successivo UPDATE nella stessa transazione deve diventare una transazione di scrittura e un'altra connessione ha eseguito una scrittura dall'inizio della lettura, SQLite non può farvi attendere. Lo snapshot non è più aggiornato e l'attesa causerebbe soltanto un deadlock tra le due connessioni. La documentazione descrive direttamente il risultato:

Le istruzioni di scrittura successive aggiorneranno la transazione trasformandola in una transazione di scrittura, se possibile, oppure restituiranno SQLITE_BUSY.

Il timeout di 5000 millisecondi non viene mai consultato. L'errore arriva immediatamente, perciò sembra che l'impostazione non abbia avuto effetto.

La correzione consiste in una parola.

BEGIN IMMEDIATE;
UPDATE notes SET body = 'edited' WHERE id = 1;
COMMIT;

BEGIN IMMEDIATE acquisisce il write lock all'inizio, prima di eseguire qualsiasi lettura. Non è necessaria alcuna conversione, quindi non c'è alcun deadlock da evitare. Il busy handler viene applicato e la connessione attende il proprio turno invece di fallire. Lasciare differite le transazioni di sola lettura. Qualsiasi transazione che contiene una scrittura deve essere immediate.

La seconda causa degli errori di lock è più difficile da individuare: mantenere aperta una transazione di scrittura durante operazioni lente. SQLite serializza le scritture, quindi una transazione che si apre, chiama un'API esterna tramite la rete e poi esegue il commit blocca ogni altro writer per tutta la durata della chiamata. Leggere i dati necessari, chiudere la transazione, eseguire l'operazione lenta, quindi aprire una breve transazione di scrittura per salvare il risultato.

Backup continuo con Litestream

Una copia notturna può perdere fino a un giorno di scritture e l'esecuzione di cp su un database SQLite in uso può produrre una copia che non si apre. Esistono due metodi sicuri. sqlite3 app.db ".backup /path/to/backup.db" usa l'interfaccia di backup online di SQLite e funziona su un database in uso. Litestream va oltre: monitora il WAL e invia continuamente le modifiche all'object storage, riducendo la perdita di dati massima da un giorno a circa un secondo.

Litestream è un unico binario Go che viene eseguito accanto all'applicazione. Non si interpone tra l'applicazione e il database. L'applicazione scrive in SQLite esattamente come prima e Litestream legge il WAL e carica le modifiche.

cd /tmp
curl -fsSL -O https://github.com/benbjohnson/litestream/releases/download/v0.5.14/litestream-0.5.14-linux-x86_64.deb
sudo dpkg -i litestream-0.5.14-linux-x86_64.deb
litestream version

v0.5.14 è la release documentata dalla pagina ufficiale di installazione per Linux a luglio 2026, mentre v0.5.15 è stata rilasciata il 21 luglio 2026. Modifica la versione in entrambe le righe in modo che corrisponda al tag corrente nella pagina delle release e, se il tuo VPS usa arm64, scegli invece il pacchetto arm64 corrispondente.

Il file di configurazione si trova in /etc/litestream.yml. Inizia con una replica locale, perché consente di verificare l'intero flusso senza richiedere credenziali cloud.

dbs:
  - path: /home/appuser/app/app.db
    replica:
      type: file
      path: /var/backups/litestream/app

Nota che il campo è replica, al singolare. Litestream 0.5 ha sostituito l'array replicas della serie 0.3 con un unico blocco di replica; una configurazione con due elementi ora non parte. Molte guide di terze parti mostrano ancora il vecchio array, quindi copia la struttura riportata sopra invece del primo esempio restituito dalla ricerca. La serie 0.5 ha inoltre rinominato il sottocomando litestream wal in litestream ltx, perché il formato del backup su disco è cambiato.

Verifica che la configurazione venga analizzata correttamente prima di abilitare qualsiasi funzione.

sudo litestream databases -config /etc/litestream.yml

Verifica quindi manualmente il percorso completo. Questa forma ignora il file di configurazione e replica un database su un singolo percorso.

mkdir -p /tmp/replica
litestream replicate ~/app/app.db file:///tmp/replica/app

Il comando viene eseguito in primo piano e continua a funzionare. In una seconda shell, scrivi una riga e ripristina la replica in un nuovo file.

sqlite3 ~/app/app.db "INSERT INTO notes (body) VALUES ('written after replication started');"
litestream restore -o /tmp/restored.db file:///tmp/replica/app
sqlite3 /tmp/restored.db "SELECT count(*) FROM notes;"

Il conteggio include la nuova riga. In caso contrario, la modifica non è ancora stata sincronizzata: Litestream invia le modifiche in base a un sync-interval che per impostazione predefinita è di 1 secondo, quindi attendi e ripristina di nuovo. Quel secondo è anche il tuo punto di ripristino. In caso di arresto anomalo, perdi al massimo le scritture dell'ultimo intervallo di sincronizzazione e nessuna configurazione può ridurlo a zero.

Per lo storage reale, sostituisci il blocco di replica con un URL S3. Funziona con Amazon S3 e con object storage compatibili con S3 di altri provider.

dbs:
  - path: /home/appuser/app/app.db
    replica:
      url: s3://your-bucket-name/app
      region: us-east-1

snapshot:
  interval: 24h
  retention: 24h

Non inserire le credenziali in quel file. Litestream legge LITESTREAM_ACCESS_KEY_ID e LITESTREAM_SECRET_ACCESS_KEY dall'ambiente, quindi inseriscile in un drop-in di systemd di proprietà di root con modalità 600.

I valori degli snapshot riportati sopra sono quelli predefiniti e il valore predefinito della conservazione può sorprendere. La conservazione indica per quanto tempo Litestream mantiene gli snapshot e i file che ne fanno parte; determina quindi anche quanto indietro nel tempo è possibile eseguire un ripristino. Ventiquattro ore significa che una migrazione errata rilevata mercoledì mattina è già irrecuperabile dallo stato di lunedì. Imposta retention: 168h su una settimana e sostieni il costo dello storage aggiuntivo.

Verifica il ripristino prima di averne bisogno

litestream restore -o /tmp/check.db /home/appuser/app/app.db
sqlite3 /tmp/check.db "PRAGMA integrity_check;"
sqlite3 /tmp/check.db "SELECT count(*) FROM notes;"

Dato il percorso di un database, litestream restore individua la replica corrispondente in /etc/litestream.yml e la scarica. PRAGMA integrity_check stampa ok per un file integro; qualsiasi altro output indica che la copia ripristinata non è utilizzabile. Esegui questa verifica secondo una pianificazione, usando un servizio e un timer systemd, e controlla l’output. Finché non hai ripristinato almeno un backup, non puoi sapere se funziona.

Eseguire Litestream con systemd

Il pacchetto Debian installa un'unità litestream che legge /etc/litestream.yml.

sudo systemctl enable litestream
sudo systemctl start litestream
sudo journalctl -u litestream -f

Un output corretto elenca ogni database definito nella configurazione e poi rimane silenzioso, salvo le righe periodiche di sincronizzazione. Un errore no such file or directory relativo al percorso del database indica che il percorso nella configurazione è errato oppure che il processo non può leggerlo. Per impostazione predefinita, l'unità viene eseguita come root, con privilegi superiori a quelli necessari per questo servizio. Litestream deve poter leggere e scrivere sia il database sia la directory che lo contiene, perché utilizza i file -wal e -shm accanto al database. Per questo, assegnagli l'account già utilizzato dall'applicazione.

# /etc/systemd/system/litestream.service.d/override.conf
[Service]
User=appuser
Group=appuser

Applica la configurazione con sudo systemctl daemon-reload e sudo systemctl restart litestream. Configurare un account di servizio dedicato con privilegi minimi richiede pochi minuti e fa la differenza tra un agente di backup e un secondo processo root sul server.

Un dettaglio relativo all'ordine di avvio è importante se devi ricreare la macchina da zero. Il database deve essere ripristinato prima dell'avvio dell'applicazione. litestream restore accetta -if-db-not-exists, che restituisce il codice 0 quando il file esiste già, quindi può essere eseguito in sicurezza a ogni avvio. Inseriscilo in una riga ExecStartPre nell'unità dell'applicazione: un VPS nuovo scaricherà il database, mentre un VPS già configurato non eseguirà alcuna operazione. litestream replicate dispone di un flag -restore-if-db-not-exists equivalente, se preferisci mantenere la configurazione in un unico punto.

Dove SQLite non è adatto su un VPS

File system di rete. Questo è il limite che non si può aggirare con la configurazione. La modalità WAL richiede che ogni processo che usa il database condivida una piccola area di memoria, fornita dal file -shm. La documentazione di SQLite enuncia la regola senza eccezioni:

Tutti i processi che usano un database devono essere eseguiti sullo stesso computer host; WAL non funziona su un file system di rete.

Un database su una condivisione NFS (network file system) o SMB montata può quindi corrompersi e nessun pragma può impedirlo. In questo caso è importante distinguere due situazioni che spesso vengono confuse. Un dispositivo a blocchi di rete, cioè il tipo di storage aggiuntivo collegato dalla maggior parte dei provider VPS, viene visto da Linux come un normale disco con un normale file system ed è quindi adatto. Una condivisione di file montata, invece, non lo è.

Un secondo server applicativo. Nessuna impostazione consente di gestire questo scenario. Quando servono due macchine che utilizzano gli stessi dati, è necessario un database che comunichi tramite la rete. Conviene pianificare il passaggio finché c'è ancora tempo per farlo.

Carichi di lavoro con molte scritture. Un solo processo di scrittura alla volta è una caratteristica del formato del file, non un parametro configurabile. Le scritture brevi sono rapide perché ogni commit viene aggiunto al WAL; per questo il throughput dipende più dalla latenza del disco nelle scritture di piccole dimensioni che dalla CPU. Vedere Storage NVMe e SSD SATA su un VPS per un confronto concreto. Il problema reale sono le transazioni lunghe, perché mettono in coda tutti gli altri processi di scrittura.

Query analitiche. SQLite è un database orientato alle righe, progettato per le transazioni. Una dashboard che analizza cento milioni di righe richiede uno strumento diverso per un'attività diversa; DuckDB a confronto con SQLite per l'uso sui server spiega dove si colloca questo limite.

VACUUM durante la replica. Un VACUUM completo riscrive l'intero file del database. Di conseguenza, Litestream deve caricarlo di nuovo per intero e la documentazione di Litestream sconsiglia di eseguire questa operazione mentre la replica è attiva. Arrestare il replicatore, eseguire il vacuum, riavviarlo e prevedere una nuova snapshot completa.

Due replicatori sullo stesso database. Non eseguire mai due processi Litestream sullo stesso database o sulla stessa destinazione di replica. La documentazione specifica chiaramente che impedire questa configurazione è responsabilità dell'amministratore. In caso contrario, si ottiene una replica che non può essere ripristinata.

Cosa non copre Litestream

Litestream protegge il file del database e nient’altro. I file caricati, la configurazione dell’applicazione, i certificati TLS (transport layer security) e i file delle unità restano a tuo carico. Abbinalo a backup cifrati fuori dal server con restic eseguiti secondo una pianificazione, così coprirai entrambi gli aspetti. Se la macchina è nuova, i primi dieci minuti su un nuovo VPS illustrano la configurazione dell’account utente e del firewall che questa guida presuppone già completata.

FAQ

SQLite è sufficiente per un'applicazione in produzione?

Per un'applicazione su un solo server, sì, a condizione di attivare la modalità WAL, impostare un timeout di attesa e creare backup continui. I limiti importanti sono strutturali: un solo writer alla volta e una sola macchina host. Un'applicazione che rientra in questi limiti utilizza un database senza passaggi di rete e senza un processo separato da monitorare. Un'applicazione che non rientra in questi limiti richiede un database client-server; nessuna ottimizzazione può cambiare questo requisito.

Perché continuo a ricevere database is locked dopo aver impostato busy_timeout?

Perché SQLite ignora il busy handler quando l'attesa potrebbe causare un deadlock. Una transazione che inizia con un semplice BEGIN è deferred: un SELECT iniziale la porta in una transazione di lettura e una scrittura successiva deve effettuare un upgrade. Se nel frattempo un'altra connessione ha eseguito una scrittura, SQLite restituisce immediatamente SQLITE_BUSY invece di chiamare il busy handler, perché lo snapshot di lettura è già obsoleto. Avvia con BEGIN IMMEDIATE qualsiasi transazione che eseguirà scritture, in modo da acquisire subito il write lock e applicare il timeout.

Posso mantenere il database SQLite su uno storage di rete?

Non su un filesystem di rete come NFS o SMB. La modalità WAL richiede che tutti i processi condividano la memoria tramite il file -shm e la documentazione di SQLite stabilisce che ogni processo che utilizza il database deve trovarsi sullo stesso computer host. Un network block device collegato dal provider è un caso diverso: Linux lo vede come un normale disco con un normale filesystem e SQLite funziona correttamente.

Mi serve Litestream se eseguo già backup notturni?

Dipende dalla quantità di dati che puoi permetterti di perdere. Un job notturno può comportare la perdita di fino a ventiquattro ore di scritture. Litestream esegue la sincronizzazione circa una volta al secondo, quindi un crash comporta una perdita approssimativa dell'ultimo secondo. È anche più sicuro della copia del file di database con cp, che può acquisire il database durante una scrittura. Litestream protegge solo il database, quindi mantieni attivo anche un backup generale dei file.

#sqlite#wal#litestream#backups#production