Self-Hosted Databases in 2026: Postgres vs SQLite vs MariaDB

Picking a self-hosted database in 2026 comes down to one question: when does it force you to migrate? SQLite holds until about one write-heavy app server (~10 GB, single writer). PostgreSQL 18 is the default that almost never makes you move. MariaDB 12.3 LTS earns its spot mainly when you already live in the MySQL world.

Key Takeaways

  • SQLite serializes writes, so one busy app server is its real ceiling.
  • Postgres 18 is the default that almost never makes you migrate later.
  • MariaDB fits best when you already run MySQL tooling.
  • SQLite runs with no daemon and almost no RAM, while Postgres needs tuning.
  • The SQLite to Postgres jump is a planned move, not an emergency.

What are the best self-hosted databases for web apps in 2026?

For a self-hosted web app, three engines cover almost every case: PostgreSQL is the do-everything default, SQLite is the embedded single-file engine, and MariaDB is the MySQL-compatible community fork. All three are open source and free to run on your own box.

The decision rule fits in one sentence. Count your concurrent writers and your dataset size before you count features. A read-heavy app on one server lives happily on SQLite. An app with many writers needs a client-server engine from day one. Many self-hosted apps surface this exact fork: Gitea runs on SQLite for a test box but moves to Postgres for anything past it.

Self-hosting also changes how you compare them. You don’t pay a monthly cloud bill. Instead, you pay in RAM, upkeep time, and backup care. So the work of running it is a first-class axis here, not an afterthought.

Here is the high-level spec matrix for the three engines.

DatabaseConcurrency modelJSON / full-text searchReplicationTypical RAM footprintBackup
PostgreSQL 18MVCC, many concurrent writersjsonb, JSON_TABLE, tsvector + GINLogical + streaming, built in~128 MB base, grows with tuningpg_dump, streaming/physical
SQLite 3.xMany readers, one writerJSON functions, FTS5 in-engineNone nativeNear zero, shares app processCopy the file, VACUUM INTO
MariaDB 12.3 LTSInnoDB, many concurrent writersJSON functions, FULLTEXT indexBinlog replicationComparable to Postgresmariadb-dump, mariabackup

This post leads with the migration ceiling, then covers ops burden per scale, then the few features that actually flip the choice. Managed and distributed variants like Neon, Turso, and Vitess exist, but they sit outside a self-hosting comparison.

The Migration Ceiling: When Each Database Forces You to Move

A feature checklist won’t tell you much. It helps more to frame each engine by the point where it stops fitting. That is the moment you have to migrate. And how painful that move gets varies a lot between the three.

SQLite has the sharpest ceiling, and it is the most famous one. The limit on running SQLite at production scale is concurrency: SQLite allows unlimited readers but only one writer at any instant . WAL mode lets readers and writers stop blocking each other, but it does not hand you concurrent writes. So one busy app process is the real limit.

In practice, three signals tell you SQLite is done: a second app server, frequent database is locked errors in your logs, or a dataset pushing past ~10 GB where complex queries slow down. One migration guide puts it bluntly.

If you see sqlite3.OperationalError: database is locked more than once a week, it’s time to migrate.

Render

The SQLite to Postgres path is well-trodden. You translate the schema (types, autoincrement, booleans), dump and load the data, then rewrite any SQLite-specific SQL. It is a known, planned operation. It costs you a weekend, not your business, and tools and writeups exist because this is the canonical upgrade route.

Postgres has the highest ceiling of the three. Its MVCC concurrency model lets many connections write at once without blocking. And it scales to terabytes. You usually grow a single Postgres box into read replicas, partitions, or pooling. That happens long before you ever “migrate off” Postgres. More on pooling below.

MariaDB sits close to Postgres on this axis. It is client-server with real concurrent writes through InnoDB. The new 12.3 LTS release rewrote binary logging to push write throughput up roughly 4x by moving core binlog components into InnoDB. You scale it the way you scale MySQL: replicas first, then sharding.

The verdict follows directly. Start on SQLite only if you are confident you will stay single-server and read-heavy. Otherwise begin on Postgres so you never pay the migration tax. Choose MariaDB at the start mainly if your team and tooling are MySQL-native.

Decision tree routing a self-hosted web app to SQLite, PostgreSQL 18, or MariaDB 12.3 based on write concurrency, dataset size, and MySQL familiarity
DatabaseSweet-spot scaleMigration ceilingMigrates toOps burdenBest for
SQLite 3.xSingle server, read-heavy, <~10 GBSecond writer, frequent locks, ~10 GBPostgres or MariaDBLowestSolo devs, content sites, edge apps
PostgreSQL 18One box to terabytesReplicas/partitioning, not a rewriteReplicas, pooling, partitionsMediumAlmost any web app default
MariaDB 12.3 LTSOne box to large, MySQL-style scale-outReplicas, then shardingReplicas, Vitess-style shardingMediumTeams already on MySQL

Ops Burden Per Scale: What Your Server Actually Needs

When you self-host, the database fights your app for RAM. It also takes your time every time something needs tuning. So the real cost of each engine shows up here, at small and medium scale, not on a spec sheet.

SQLite ops burden is near zero. There is no daemon, no port, no user or permission setup. The whole library is under 1 MB , and a backup can be copying a single file or running VACUUM INTO backup.db. It shares your app process and barely registers in RAM.

Postgres asks more of you. It is a separate daemon you configure, with shared_buffers defaulting to 128 MB and a common starting guideline of about 25% of RAM on a dedicated box. You also own pg_dump and pg_restore, vacuum tuning, connection limits, and major-version upgrades.

MariaDB ops burden lands close to Postgres. You run and tune a daemon, back up with mariadb-dump for logical dumps or mariabackup for physical ones, and add replication config when you scale out. None of this surprises anyone who has run MySQL.

The RAM math is the part that bites on a small VPS. SQLite leaves nearly all your memory for the app. Postgres and MariaDB each want a baseline carve-out before they perform well. On a 1-2 GB box, that carve-out is the difference between a snappy app and a swapping one.

Running my own web apps on a VPS, the SQLite experience was almost boring in the best way. Backups were “the file lives in the volume, snapshot the volume,” and that was the entire plan. There was no separate process to watch, no port to firewall, no tuning knob that mattered at my scale.

The day I moved one app to Postgres, the ops time reappeared. I wrote a pg_dump cron, sized shared_buffers for a small box, and learned to watch connection counts before they capped out. None of it was hard, but it was real work that SQLite simply never asked of me. For a small, single-server, read-heavy app today, I would reach for SQLite again without hesitating.

Backup ergonomics rank cleanly for self-hosters. SQLite (copy a file) is simplest. Then come logical dumps for Postgres and MariaDB. Then physical or streaming backups and replication, which you only need once you grow.

Features That Actually Change the Decision

Features count for something, but only a handful of them flip the choice for a self-hosted web app. The ones that do are JSON storage, full-text search, replication, and extensibility. So those are the ones worth weighing.

On JSON, Postgres leads. It has mature jsonb, and Postgres 18 adds the SQL-standard JSON_TABLE to project JSON into relational rows. MariaDB 12.3 added IS JSON, removed the old 32-level JSON depth limit, and improved JSON handling generally. SQLite ships solid JSON functions and a JSONB storage format, which covers many apps.

Full-text search is built into all three, which lets most self-hosters defer a separate search engine. SQLite ships FTS5 in-engine. Postgres has tsvector and GIN search, with parallel GIN index builds new in 18 plus BM25 extensions like pg_textsearch . MariaDB offers FULLTEXT indexes.

Replication is where SQLite shows its single-node nature. Postgres 18 improved logical replication, with generated-column support, conflict reporting, and parallel apply by default. MariaDB 12.3’s InnoDB-based binlog aims for faster, steadier replication. SQLite has no native multi-node replication. So you reach for outside layers like Litestream streaming or forks like libSQL. Needing those is itself a sign you have outgrown plain SQLite.

Extensions are where Postgres wins. PostGIS, vector search, time-series, and a deep add-on ecosystem are a big reason Postgres rarely forces a migration. MariaDB has pluggable storage engines and added native vector search in 12.x. SQLite supports loadable extensions, but the set is smaller.

The vector question deserves a brief note, since it comes up for AI apps. All three can store embeddings in 2026: Postgres through pgvector, MariaDB through native vector search, and SQLite through extensions. So “I need embeddings” is no longer an automatic reason to bolt on a separate database for a small workload.

Current Versions and What Changed in 2026

The verdict only holds if it reflects today’s releases, so here is where each engine stands in 2026.

PostgreSQL 18 is the current stable major, released in late 2025. Its headline feature is a new async I/O subsystem with up to 3x gains on some scans. It also adds UUIDv7 and OAuth support. Postgres 19 is in beta , with general release targeted for September 2026.

Bar chart comparing Postgres 18 sequential and bitmap scan throughput across io_method settings, showing the async io_uring path beating the synchronous default
Postgres 18 scan throughput across io_method settings
Image: Xata

SQLite remains the steadily evolving embedded engine. WAL is the standard concurrency mode. Experimental WAL2 and BEGIN CONCURRENT push write concurrency higher in specialized builds, but the single-writer model still defines mainstream SQLite.

MariaDB 12.3 is the current LTS, supported through June 2029. It ships the rewritten InnoDB-based binary log (~4x write throughput) and better native vector search. It also adds compatibility features like TO_DATE(), SET PATH, and caching_sha2_password. Note that the LTS support window moved to 3 years for releases after 11.4 .

Licensing stays simple for self-hosters. Postgres uses the permissive PostgreSQL License, SQLite is public domain, and both carry no copyleft. MariaDB is GPL, governed by the community MariaDB Foundation.

One last piece completes the Postgres ceiling story: connection pooling. Before you migrate off Postgres or add replicas, you usually add PgBouncer to multiplex connections, because raw connections are the resource that runs out first. It is the cheap fix that buys you a lot of headroom.