SQLite in production: patterns that have aged well
Updated: 2026-07-07
SQLite lleva años ganando terreno en servidores reales gracias a WAL, a proyectos como Litestream y libSQL, y a hardware con discos rápidos. Repaso los patrones que siguen funcionando después de varios años de uso, los que no, y por qué el tamaño medio de una aplicación web se come ya sin despeinarse.
SQLite has been the world’s most deployed database for twenty-five years, but for most of that time it was dismissed as a toy option for servers. That perception has shifted in the last three years thanks to three forces: mature WAL, NVMe disks universal on even cheap VPS plans, and projects like Litestream and libSQL that solved the two classic objections, lack of replication and lack of remote access. This post collects what still works and what doesn’t after several years of watching it run in production.
Key takeaways
-
WAL mode changes SQLite so profoundly that talking about SQLite-without-WAL and SQLite-with-WAL as the same database is misleading: the concurrency difference is two orders of magnitude on typical web loads.
-
Litestream streams the WAL to an S3-compatible object store in near real time, enabling point-in-time restore with at most a few seconds of data loss.
-
libSQL, the SQLite fork Turso maintains, adds embedded synchronized replication for geographically distributed architectures.
-
SQLite covers most web applications with fewer than a thousand concurrent users and databases under 100 GB.
-
Doesn’t apply when the load has intensive concurrent writes, multi-server file access is needed, or advanced Postgres extensions are required.
WAL mode changes everything
The gap between SQLite in the default rollback journal mode and SQLite with WAL enabled is so wide that talking about both as the same database is misleading. Rollback mode serializes writes and reads on a single file lock, which caps useful concurrency at a handful of queries per second. WAL mode, a forward-write log kept in a separate file, allows concurrent reads while a write is active, and restricts collisions to simultaneous writers, as documented by the SQLite project itself[1]. For web apps with a 95 to 5 read-to-write ratio, the performance difference is two orders of magnitude.
Enabling WAL is a single statement at database-open time, but it comes with details worth knowing:
-
The WAL file grows while transactions are active and is checkpointed automatically once it reaches a threshold (1,000 pages by default, per the SQLite documentation). On workloads with very frequent writes it is worth raising that threshold.
-
The WAL file and the main file must share a filesystem; trying to put them on different volumes is a classic mistake that breaks atomicity.
The second lever for server performance is tuning page size and cache. SQLite defaults to 4 KB pages, which fits most workloads well, but for tables with small rows and heavy random reads, moving to 8 KB or 16 KB pages at database-creation time can improve filesystem-cache efficiency.
Litestream and replication was the turning point
The historical Achilles’ heel of SQLite on the server was backup. Until Litestream[2] arrived in 2021, the only decent way to back up a live database was to stop writes, copy the file, and resume, which is not viable on any system with real traffic. Litestream solved the problem by streaming the WAL to an S3-compatible object store in near real time (the agent’s default sync interval is one second), enabling point-in-time restore with at most a few seconds of data loss. I cover the full mechanics in Litestream: near-real-time replication for SQLite.
What started as a clever trick has become the piece that makes serious production SQLite viable. In deployed projects, pointing Litestream at an S3-compatible bucket such as Hetzner Object Storage or Cloudflare R2 costs a few cents a month in storage and protects against disk failure or server destruction. Restoring is one command: it downloads the bucket state, rebuilds the database to the chosen instant, and starts the application.
The most recent evolution is libSQL, the SQLite fork Turso maintains and which already powers Turso Cloud in production. It added embedded synchronized replication, allowing a primary server database and local replicas on each client or in regional nodes. For geographically distributed applications, libSQL solves local reads without running multi-region Postgres. Don’t confuse it with Turso Database (formerly known as Limbo), the from-scratch Rust rewrite of SQLite the same company maintains on GitHub[3]: as of mid-2026 it is still in beta on its way to a first stable release, per Turso’s own blog[4], so libSQL, not the rewrite, remains the mature choice for production today.
Loads where SQLite is still the best choice
SQLite on server with WAL and Litestream covers without blinking most web applications with fewer than a thousand concurrent users and databases under 100 GB. That includes many management systems, internal dashboards, sites with moderate dynamic content, mid-size APIs, and practically anything that isn’t a store with massive traffic spikes or a social network with heavy writes. The most visible success story is sqlite.org itself, which, per the project’s own documentation[5], serves its entire site from a SQLite database handling 400,000 to 500,000 HTTP requests a day, 15-20% of them dynamic, running on a single VM that shares a physical server with 23 others.
The second niche where it shines is development and testing. Being able to swap Postgres for SQLite in the continuous-integration environment cuts test startup time from tens of seconds to under two. Most of standard SQL is compatible, and when something diverges it shows up early.
The third space is desktop and mobile applications that sync with the cloud. With libSQL and its bidirectional sync, the pattern of a local client database that replicates with a central server has gotten drastically simpler.
Where SQLite is still a bad idea
There are cases where SQLite on the server doesn’t pay off, and it’s worth being honest about them:
-
Sustained intensive concurrent write workload: SQLite serializes writers by design. A system with hundreds of write transactions per second will hit a ceiling. Postgres, MariaDB, or even MySQL with InnoDB can absorb orders of magnitude more concurrent writes.
-
Multi-process architecture with several application servers accessing the same file: SQLite isn’t built for over-the-network file access. Locking over NFS is unreliable and has been the source of corruptions in real projects. If your architecture needs more than one server reading and writing against the same database, SQLite is not the right tool, period.
-
Needing advanced Postgres features: serious geospatial extensions, production-grade full-text search, or two-phase transactional support. The gap with Postgres in these areas is substantial.
-
Mostly analytical workload over large volumes: if what’s behind it is aggregation and columnar scans rather than row-level transactions, the right engine is usually something else; I cover that crossover in SQLite and DuckDB: when each is the right choice.
The same logic of picking the right tool for the right load applies to the Citus for scaling Postgres horizontally debate: every database has its own range of strengths.
When it pays off
The question is no longer whether SQLite can sustain a web server, because it can. The question is whether your concrete case fits its strength profile. If your application:
-
Reads much more than it writes.
-
Fits on a single server.
-
Doesn’t need synchronous multi-node replication.
-
Values the operational simplicity of a file with no server process.
Then SQLite with WAL and Litestream is probably the lowest total-cost option in engineering and operations.
If instead your system will grow to several servers, will have heavy concurrent writes, or you already have Postgres running without pain, there’s no reason to switch. The beauty of SQLite in 2025 isn’t that it replaced Postgres, but that it stopped being an eccentric option for a certain range of problems. That range is larger than most people assume.
Conclusion
SQLite with WAL and Litestream can save weeks of infrastructure assembly that adds nothing to a service with 200 monthly active users. Taking it seriously before assuming you need a separate database server is part of practical engineering.
Also in Spanish: SQLite en producción: patrones que han envejecido bien.