Mastodon instances that grow beyond a few thousand active users often hit a bottleneck on the primary PostgreSQL database. Read-heavy operations like loading the federated timeline, searching public posts, and fetching user profiles all query the same database server. When that single server cannot keep up, page loads slow down and timeouts increase. This article explains how to set up a PostgreSQL read replica for a Mastodon instance to offload read queries and improve response times.
A read replica is a secondary database that stays synchronized with the primary through streaming replication. Mastodon applications can then route read-only queries to the replica while writes still go to the primary. This setup does not require changing Mastodon source code — only configuration changes in the Rails database adapter and the reverse proxy layer. The steps cover provisioning a replica server, configuring streaming replication, and updating Mastodon to use the replica for read scaling.
Key Takeaways: Postgres Read Replica Setup for Mastodon
- Streaming replication via pg_basebackup: Clones the primary database to a secondary server for read-only query offloading.
- Mastodon environment variable DATABASE_REPLICA_URL: Configures Rails to route SELECT queries to the replica while writes remain on the primary.
- pg_hba.conf and postgresql.conf changes: Enable replication connections and set wal_level to logical or replica on the primary server.
Why Mastodon Needs a Postgres Read Replica
Mastodon uses PostgreSQL as its primary data store. Every page load, API call, and background job queries the database. On a busy instance, the most expensive queries are those that scan large tables — the statuses table, the accounts table, and the timeline materialized views. When the primary database server runs at high CPU or I/O utilization, all operations slow down, including writes that must complete quickly for user posts and interactions.
A read replica solves this by moving read-heavy traffic to a separate server. The replica runs the same PostgreSQL version and applies changes from the primary in near real time. Mastodon applications can connect to the replica for SELECT statements while all INSERT, UPDATE, and DELETE operations still go to the primary. This reduces lock contention and spreads the query load across two servers.
Prerequisites
Before starting the setup, ensure the following conditions are met:
- Two servers running Ubuntu 22.04 or Debian 12 with PostgreSQL 15 or 16 installed.
- The primary server has at least 4 GB of RAM and 2 CPU cores. The replica should match or exceed the primary resources.
- Both servers can communicate over a private network on port 5432. Do not expose the replica to the public internet.
- Mastodon is already running on the primary server with PostgreSQL 15 or 16. The setup assumes Mastodon 4.2 or later.
- You have root or sudo access on both servers.
Steps to Set Up a Postgres Read Replica for Mastodon
The setup has three phases: preparing the primary server, creating the replica, and configuring Mastodon to use the replica for reads.
Phase 1: Prepare the Primary Server for Replication
- Edit postgresql.conf on the primary server
Open the PostgreSQL configuration file, typically located at /etc/postgresql/15/main/postgresql.conf. Set the following parameters:wal_level = replica
max_wal_senders = 3
wal_keep_size = 1024
These settings enable enough WAL (write-ahead log) data to be sent to the replica. Increase max_wal_senders if you plan to add more replicas. - Edit pg_hba.conf to allow replication connections
Add a line to pg_hba.conf that allows the replica server IP to connect for replication. For example:host replication replicator 192.168.1.20/32 md5
Replace 192.168.1.20 with the actual private IP of the replica server. - Create a replication user
Connect to the primary database as the postgres user and run:CREATE USER replicator WITH REPLICATION ENCRYPTED PASSWORD 'strong_password';
Record this password — you will need it on the replica server. - Restart PostgreSQL on the primary
Run sudo systemctl restart postgresql to apply the configuration changes.
Phase 2: Create the Replica Server
- Install PostgreSQL on the replica server
Use the same PostgreSQL version as the primary. On Ubuntu 22.04, run:sudo apt update && sudo apt install postgresql-15 - Stop PostgreSQL on the replica
Run sudo systemctl stop postgresql to ensure no existing data conflicts. - Remove default data directory on the replica
Run sudo rm -rf /var/lib/postgresql/15/main/ to clear the default database cluster. - Clone the primary database using pg_basebackup
Run the following command as the postgres user:pg_basebackup -h PRIMARY_IP -D /var/lib/postgresql/15/main -U replicator -P -v --wal-method=stream
Replace PRIMARY_IP with the private IP of the primary server. Enter the replication password when prompted. - Create a standby.signal file
Run touch /var/lib/postgresql/15/main/standby.signal to tell PostgreSQL to start in standby mode. - Configure primary_conninfo in postgresql.conf on the replica
Add the following line to /etc/postgresql/15/main/postgresql.conf:primary_conninfo = 'host=PRIMARY_IP port=5432 user=replicator password=strong_password sslmode=require'
Replace the host IP and password with your actual values. - Start PostgreSQL on the replica
Run sudo systemctl start postgresql. Check the log file at /var/log/postgresql/postgresql-15-main.log for replication status. You should see a line indicating the replica is streaming WAL from the primary.
Phase 3: Configure Mastodon to Use the Replica for Reads
- Add the DATABASE_REPLICA_URL environment variable
Edit the Mastodon environment file, typically located at /home/mastodon/live/.env.production. Add the following line:DATABASE_REPLICA_URL=postgresql://mastodon_user:password@REPLICA_IP:5432/mastodon_production?sslmode=require
Replace mastodon_user, password, REPLICA_IP, and mastodon_production with your actual credentials and database name. - Restart Mastodon services
Run the following commands to restart the web and streaming processes:systemctl restart mastodon-web
systemctl restart mastodon-streaming
systemctl restart mastodon-sidekiq
Mastodon now routes read queries to the replica automatically. - Verify the replica is receiving queries
Connect to the replica database and run:SELECT count() FROM pg_stat_activity WHERE state = 'active';
You should see connections from the Mastodon application server.
If the Replica Fails to Start or Sync
Replication issues can prevent the replica from starting or cause it to fall behind the primary. Below are the most common failure patterns and their fixes.
Replica Fails to Start with “FATAL: could not connect to the primary server”
This error means the replica cannot reach the primary server on port 5432. Check that the primary server firewall allows incoming connections from the replica IP. On the primary, run sudo ufw status to confirm port 5432 is open. Also verify that the primary_conninfo string in the replica postgresql.conf uses the correct IP address and password.
Replication Lag Increases Over Time
High write volume on the primary can cause the replica to fall behind. Monitor lag with the query SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) FROM pg_stat_replication; on the primary. If lag exceeds 100 MB, increase wal_keep_size on the primary or add more resources to the replica. Also ensure the replica has enough disk I/O bandwidth — use SSD storage if possible.
Mastodon Still Shows Slow Queries After Adding the Replica
If Mastodon continues to experience slow page loads, verify that the DATABASE_REPLICA_URL environment variable is correctly set. Run echo $DATABASE_REPLICA_URL on the Mastodon application server to confirm. Also check that the replica database has the same indexes as the primary. Run vacuumdb and reindex periodically on the replica to maintain query performance.
Primary vs Replica: Configuration Comparison for Mastodon
| Item | Primary Server | Replica Server |
|---|---|---|
| Purpose | Handles all writes and critical reads | Handles read-only queries from Mastodon |
| PostgreSQL mode | Read-write | Standby (read-only) |
| wal_level | replica or logical | hot_standby (default) |
| Connection string in Mastodon | DATABASE_URL | DATABASE_REPLICA_URL |
| Backup method | pg_dump or WAL archiving | pg_basebackup from primary |
| Failover capability | Can be promoted to primary | Not used for failover in this setup |
You now have a working PostgreSQL read replica for your Mastodon instance. The replica handles SELECT queries from the web and streaming processes, reducing load on the primary. Monitor replication lag daily using the pg_stat_replication view. As a next step, consider adding a connection pooler such as PgBouncer in front of both the primary and replica to manage database connections efficiently. For advanced setups, configure pg_rewind to enable fast failback if the primary ever needs to be rebuilt from the replica.