SQLite in Production with Laravel: A Guide for Small Apps (WAL, Backups, and Litestream)
SQLite is the most deployed database engine on earth, and in 2026 — with WAL mode, busy_timeout, and Litestream — it is a legitimate production option for small Laravel apps. This guide shows you how to configure it properly and when it's time to migrate to PostgreSQL.
Why SQLite Is Trending in Production Again
For years SQLite was the database of prototypes: great for development, but nobody recommended it for real production workloads. That perception has changed. The engine that lives inside nearly every phone and browser on the planet has found its place on application servers, thanks to an ecosystem of tools that solved its two historical problems: concurrent writes and reliable backups.
The SQLite Renaissance in 2026
The resurgence of SQLite in production environments is driven by projects like Turso and LibSQL, Cloudflare D1 at the edge, and Litestream for continuous replication. It is no longer unusual to find SQLite behind an early-stage SaaS or a side project with real traffic: it is a deliberate decision to simplify the stack. Fewer pieces to operate, a database that is a single file, and backups that don't depend on a dedicated database server.
When to Choose SQLite and When Not To
The practical rule is simple: SQLite shines when writes are few and reads are many. A small Laravel app — a blog with an admin panel, an internal tool, a SaaS with a few hundred users — fits perfectly. On the other hand, if your application has peaks of concurrent writes, multiple processes writing at the same time, or you need database roles and native multi-node replication, it's time to look at PostgreSQL. SQLite is not an inferior database: it has a different concurrency model, and the trick is to respect it.
Configuring SQLite in Laravel 13
Laravel 13, released on March 17, 2026, still ships with the sqlite driver built in. The Laravel installer creates the database/database.sqlite file automatically and runs the migrations by default, so starting with SQLite requires no extra steps.
The sqlite Connection in config/database.php
The configuration lives in config/database.php, inside the connections array:
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
],In the .env file you only need DB_CONNECTION=sqlite and, if you want the database elsewhere, DB_DATABASE. The rest of the database variables go unused. That simplicity is part of the appeal: no server to spin up, no credentials to rotate, no ports to open.
PRAGMA foreign_keys and Connection Bootstrapping
SQLite does not enable foreign keys by default; Laravel handles this by executing PRAGMA foreign_keys = ON when each connection opens, so Eloquent relations with constraints work out of the box. If you need to run other pragmas per connection, you can do so from a service provider's boot method by listening to the connection's bootstrap event — a pattern that will also come in handy for the production pragmas below.
Preparing the Database for Production
The configuration Laravel ships by default is fine for development, but for production there are three pragmas that make the difference: WAL, busy_timeout, and ANALYZE.
WAL Mode: Concurrent Reads Without Blocking
WAL mode (Write-Ahead Logging) changes how SQLite writes: instead of overwriting the main file, changes are first appended to a separate log file. The practical result is that readers don't block the writer and don't block each other; there is only one write at a time, but concurrent reads flow without waiting. It is enabled with a single line:
PRAGMA journal_mode = WAL;Run it when the database is created and let it stick: the journal mode is stored in the database file itself, so there's no need to repeat it on every connection.
busy_timeout: Living with a Single Writer
SQLite's model is a single writer: if two transactions try to write at the same time, one waits. To make that wait polite instead of an immediate "database is locked" error, you set a maximum wait time in milliseconds:
PRAGMA busy_timeout = 5000;With 5000 ms, a write that finds the lock busy waits up to five seconds before giving up. In a small app, where writes are fast and infrequent, that wait is barely noticeable and eliminates the class of intermittent errors that gave SQLite a bad reputation.
ANALYZE and the Query Optimizer
SQLite's optimizer decides execution plans based on statistics about tables and indexes. The ANALYZE command collects those statistics and should be run periodically, especially after data grows noticeably. In Laravel you can run it on deploy or via a scheduled command; the cost is minimal and complex queries with several JOINs appreciate fresh statistics.
Backups and Disaster Recovery with Litestream
The historical argument against SQLite in production was backups: copying the file while writes are happening can produce a corrupted copy. Litestream solves the problem at its root.
Continuous Replication to S3-Compatible Storage
Litestream watches the WAL file and replicates changes continuously to an S3-compatible bucket, uploading only the modified pages instead of the whole database. If the disk or server fails, you restore the database from the replica with only seconds of data loss. Startup is a single command:
litestream replicate -config litestream.ymlAnd recovery is another:
litestream restore -config litestream.yml /var/www/app/database/database.sqliteIntegrating Litestream into Your Laravel Deployment
The usual pattern is to run litestream replicate as a system service alongside PHP-FPM or Octane. On deploy, before the application starts, you restore the latest replica if the local file doesn't exist, and from then on Litestream keeps replicating live. That gives you disaster recovery without adding a database server to the stack: one less thing to operate, and the peace of mind that your data doesn't live on a single disk.
Ecosystem Tools and Packages
Around production SQLite a small ecosystem of Laravel packages has grown, removing specific frictions.
eznix86/laravel-sqlite: Production SQLite with Litestream
The eznix86/laravel-sqlite package integrates production SQLite with Litestream directly into Laravel. When the sqlite.litestream config flag is enabled, it blocks destructive commands like migrate:fresh so you can't wipe the production database by accident, and it cleans up the -wal, -shm, and -journal sidecar files during schema operations. It's a safety layer that turns costly mistakes into impossible ones.
laravel-sqlite-ffi: The Driver Without pdo_sqlite
If your server doesn't have the pdo_sqlite extension available, laravel-sqlite-ffi offers a drop-in driver that talks to SQLite through PHP FFI, with configurable busy_timeout. Useful in environments where you can't install extensions and need to keep the same application code.
Turso, Cloudflare D1, and LiteFS at the Edge
Outside Laravel, the SQLite edge movement keeps growing: Turso distributes globally replicated SQLite databases, Cloudflare D1 offers SQLite as a service at the edge, and LiteFS enables file-based replication between nodes. If your small app grows in geographic reach, these options let you keep SQLite's simplicity while moving data closer to users.
Signals That It's Time to Migrate to PostgreSQL
A moment will come when SQLite stops being the right tool. The clear signals are: frequent concurrent writes that start causing real busy_timeout waits, a dataset that keeps growing and makes backups and queries heavier, or the need for advanced features like database roles, fine-grained permissions, or native multi-node replication. Migrating to PostgreSQL in Laravel is a configuration change: you adjust the .env, review that columns and types are compatible, and run the migrations. SQLite doesn't punish you for starting with it; it just tells you when it's time to move on.
Conclusion
SQLite in production is no longer heresy: with WAL, busy_timeout, ANALYZE, and continuous replication with Litestream, it's a solid and simple option for small Laravel apps with few concurrent writes. Configure it well, automate the backups, and watch for the growth signals. If you want to keep learning about Laravel, check out the other web development articles on the blog.