Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

MySQL Master-Slave Replication Setup

By Kokil Thapa | Last reviewed: August 2026

When your Laravel or PHP application starts hitting read bottlenecks, a properly configured MySQL Master-Slave Replication Setup is often the most cost-effective scaling strategy before sharding or migrating to managed cloud databases. I have implemented this architecture for high-traffic legal-tech portals and eCommerce platforms where write operations must remain consistent while read queries scale horizontally across replicas. This guide covers the exact configuration steps, security hardening, and monitoring practices required for a reliable database-driven website development in Nepal and global production environments.

How do you configure the MySQL Master server for replication?

The master (primary) server must be configured to generate binary logs that capture every data-modifying statement. In MySQL 8.0 and 8.4 LTS, GTID-based replication is the standard approach because it simplifies failover and eliminates fragile file-position tracking. Edit your /etc/mysql/mysql.conf.d/mysqld.cnf or /etc/my.cnf.d/server.cnf file on the master node with these essential parameters:

[mysqld]
server-id               = 1
log_bin                 = /var/log/mysql/mysql-bin
binlog_format           = ROW
gtid_mode               = ON
enforce_gtid_consistency = ON
sync_binlog             = 1
innodb_flush_log_at_trx_commit = 1
binlog_expire_logs_seconds = 604800
max_binlog_size         = 1073741824

Each directive serves a specific purpose in production. The server-id must be a unique positive integer across your entire replication topology; I typically use sequential integers starting from 1 for the master. Setting binlog_format = ROW ensures deterministic replication by logging actual row changes rather than SQL statements, which prevents issues when functions like NOW() or triggers produce different results on replicas. The sync_binlog = 1 and innodb_flush_log_at_trx_commit = 1 combination guarantees zero data loss on crash at the cost of slightly higher I/O — never relax these in production unless you accept potential data inconsistency.

Creating the replication user securely

Create a dedicated MySQL user solely for replication traffic. Never reuse application credentials or root accounts. On MySQL 8.0+, execute:

CREATE USER 'repl_user'@'%' IDENTIFIED WITH caching_sha2_password BY 'StrongP@ssw0rd!2026';
GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'%';
FLUSH PRIVILEGES;

In practice, restrict the host wildcard to your replica's private IP or subnet (e.g., 'repl_user'@'10.0.1.%') rather than allowing connections from any host. For cross-datacenter replication over public networks, wrap the connection in an SSH tunnel or use MySQL's built-in SSL/TLS by adding REQUIRE SSL to the GRANT statement and configuring certificates on both nodes. I have seen too many setups where open replication users became attack vectors during security audits on server hardening projects.

Master (Primary)server-id = 1GTID Mode: ONBinary Log: ROWReplica 1server-id = 2Read-Only: ONReplica 2server-id = 3Read-Only: ONApplication LayerWrites → MasterReads → Replicas
MySQL Master-Slave Replication Setup topology with GTID-based streaming from primary to read-only replicas and application-level read/write splitting

How do you initialize a MySQL replica and start replication?

On each replica server, configure a unique server-id and enable relay logging in mysqld.cnf:

[mysqld]
server-id               = 2
relay_log               = /var/log/mysql/relay-bin
read_only               = ON
super_read_only         = ON
gtid_mode               = ON
enforce_gtid_consistency = ON
replica_parallel_workers = 4

Setting super_read_only = ON prevents even SUPER-privileged users from accidentally writing to the replica — a safeguard I always enable after being burned by a well-meaning DBA who ran a manual UPDATE on what they thought was the master. The replica_parallel_workers = 4 enables multi-threaded applier threads, significantly reducing lag on write-heavy workloads. Adjust this value based on CPU cores; 4–8 workers suit most mid-range servers.

Pointing the replica to the master

After restarting MySQL with the new configuration, connect to the replica and run:

CHANGE REPLICATION SOURCE TO
    SOURCE_HOST = '10.0.1.10',
    SOURCE_USER = 'repl_user',
    SOURCE_PASSWORD = 'StrongP@ssw0rd!2026',
    SOURCE_AUTO_POSITION = 1,
    SOURCE_SSL = 1;

START REPLICA;

The SOURCE_AUTO_POSITION = 1 parameter tells MySQL to use GTID auto-positioning instead of manual binlog file and position coordinates. This is non-negotiable for new deployments in 2026. If you are migrating from legacy file-based replication, complete the current transaction set first, then switch to GTID mode following the official MySQL upgrade path. Verify replication health immediately:

SHOW REPLICA STATUS\G

Confirm that Replica_IO_Running and Replica_SQL_Running both show Yes, and Seconds_Behind_Source is 0 or a low stable number. Any other state requires investigation before serving traffic.

What are common MySQL replication failures and how do you fix them?

Replication breaks in predictable ways. After maintaining replicated topologies for Laravel applications and WooCommerce stores since 2010, these are the failures I encounter most frequently:

  • Non-deterministic statements: Queries using UUID(), RAND(), or user-defined variables without ROW format cause silent divergence. Always use binlog_format = ROW and audit slow query logs for unsafe patterns.
  • Schema drift: Running ALTER TABLE directly on a replica breaks replication. All DDL must originate on the master. Use tools like pt-online-schema-change or gh-ost for zero-downtime schema migrations on large tables.
  • Disk space exhaustion: Binary logs accumulate quickly under heavy write load. Set binlog_expire_logs_seconds = 604800 (7 days) and monitor disk usage with automated alerts. I have recovered multiple production systems where full disks halted replication silently.
  • Network interruptions: Transient network failures between datacenters cause IO thread disconnections. Configure replica_net_timeout = 60 and replica_reconnect_interval = 10 for automatic reconnection. For unstable links, consider compressed replication with COMPRESSION_ALGORITHMS=zstd in MySQL 8.0.34+.
  • GTID inconsistencies: Manual writes to replicas inject GTIDs that don't exist on the master, causing permanent breakage. Enforce super_read_only and audit access controls quarterly.
Replication Broken?Check SHOW REPLICA STATUSIO Thread: NoSQL Thread: NoLag > ThresholdCheck NetworkVerify CredentialsFirewall / SSL CertsCheck Error LogFix Schema / DataSkip or RebuildEnable Parallel ApplierOptimize Slow QueriesScale Replica Hardware
Diagnostic decision tree for MySQL Master-Slave Replication Setup failures covering IO thread, SQL thread, and lag scenarios

How should Laravel applications use MySQL read replicas?

Laravel natively supports read/write splitting through database configuration. In config/database.php, define separate connections and let the framework route queries automatically:

'mysql' => [
    'read' => [
        'host' => env('DB_READ_HOST', '10.0.1.20'),
    ],
    'write' => [
        'host' => env('DB_WRITE_HOST', '10.0.1.10'),
    ],
    'sticky' => true,
    // ... shared credentials, database name, charset
],

The sticky option is critical. When enabled, Laravel routes all reads to the master for the remainder of a request cycle after any write occurs. Without it, a user might create a record and immediately fail to see it because the replica hasn't caught up yet. I enable sticky by default on every Laravel project using replication, disabling it only for specific read-heavy endpoints where eventual consistency is acceptable (analytics dashboards, search results, cached content).

Handling replica lag in application code

Even with sticky connections, background jobs, API responses consumed by SPAs, and webhook callbacks can hit stale replicas. Implement explicit master reads when freshness is mandatory:

$order = DB::connection('mysql')
    ->table('orders')
    ->where('id', $orderId)
    ->useWriteConnection()
    ->first();

For queue workers processing payment confirmations or inventory updates, always configure the worker to use the write connection exclusively. Stale reads in financial workflows cause duplicate charges and overselling — problems I have debugged on live eCommerce systems handling NPR-denominated transactions where timing matters during peak Dashain sales periods.

ScenarioConnectionRationale
User registration / loginWrite (sticky)Immediate session/auth consistency required
Product listing / searchRead replicaEventual consistency acceptable; high volume
Order placementWriteInventory and payment state must be atomic
Admin dashboard reportsRead replicaAggregation tolerates seconds of lag
Webhook / payment callbackWriteExternal system expects immediate confirmation
Email notification contentRead replicaMinor staleness in template data is harmless

How do you monitor MySQL replication health in production?

Replication monitoring cannot be an afterthought. Silent failures are the norm, not the exception. Implement these layers:

  1. Heartbeat table: Create a small table on the master that updates every second via a scheduled event. Query this table on replicas to measure actual end-to-end lag more accurately than Seconds_Behind_Source, which can report 0 even when the replica is stuck replaying old events.
  2. Alerting thresholds: Alert at 30 seconds lag for warning, 120 seconds for critical. For legal-tech portals handling court date notifications or document attestation deadlines, I set tighter thresholds (10s warning, 30s critical) because stale data has real-world consequences for clients.
  3. GTID set comparison: Periodically compare Executed_Gtid_Set between master and replicas. Divergence indicates skipped transactions or manual interference that Seconds_Behind_Source won't catch.
  4. Automated failover readiness: Test promotion procedures quarterly. Document the exact commands, DNS changes, and application config updates required. Unpracticed failovers fail during actual outages.
MasterHeartbeat TableUpdated Every 1sReplicaRead HeartbeatCompare TimestampMonitoring AgentLag CalculationGTID ComparisonWarning> 30s LagSlack / EmailCritical> 120s LagPagerDuty / SMSApplication Health EndpointGET /health/db-replication → 200 OK or 503
Production monitoring pipeline for MySQL Master-Slave Replication Setup with heartbeat-based lag detection and tiered alerting

When should you choose replication over other scaling strategies?

MySQL Master-Slave Replication Setup solves read-scaling and disaster recovery, but it introduces operational complexity. Choose replication when your workload is read-heavy (80%+ reads), when you need geographic distribution for latency reduction, or when budget constraints rule out managed multi-AZ databases. Avoid replication as a sole strategy when your bottleneck is write throughput, when your team lacks database administration experience, or when strong consistency across all reads is non-negotiable.

For Nepal-based businesses running on limited infrastructure budgets, replication on two modest VPS instances (Rs 8,000–15,000/month each, ~USD 60–110) often delivers better ROI than jumping straight to AWS RDS Multi-AZ at USD 300+/month. However, factor in the engineering time for setup, monitoring, and incident response. If your team is small and database ops aren't a core competency, managed solutions may be cheaper overall despite higher monthly costs. I discuss these trade-offs in detail when consulting on website development cost planning for growing businesses.

Implementing MySQL Master-Slave Replication Setup reliably

A production-grade MySQL Master-Slave Replication Setup demands disciplined configuration, continuous monitoring, and application-aware query routing. Start with GTID mode, enforce read-only replicas, implement heartbeat-based lag detection, and configure Laravel's sticky connections to prevent consistency bugs. Test failover procedures before you need them. If you are planning a replication deployment for a Laravel application, eCommerce platform, or legal-tech portal and want to avoid the pitfalls that only surface under real production load, reach out to discuss your architecture. I help teams design, implement, and maintain replicated MySQL topologies that actually stay healthy at 3 AM.

Frequently Asked Questions

A configuration where one primary server accepts writes and replicates data changes to one or more read-only replica servers asynchronously via the binary log.

Freelance setup typically costs Rs 15,000–30,000 (~USD 110–220) for standard two-node configs on existing Ubuntu infrastructure, excluding ongoing monitoring or managed hosting fees.

MySQL 8.4 LTS or 8.0 LTS. Avoid mixing major versions between source and replica during initial setup to prevent binlog format incompatibilities.

Enable gtid_mode=ON and enforce_gtid_consistency=ON on both servers before configuring replication. Use CHANGE REPLICATION SOURCE TO with SOURCE_AUTO_POSITION=1 instead of specifying binary log file and position manually. This ensures automatic transaction tracking across failovers and simplifies recovery when replicas lag or restart unexpectedly.

Common causes include single-threaded SQL execution on replicas, long-running transactions on the source, insufficient replica hardware, or network latency. Check Seconds_Behind_Source in SHOW REPLICA STATUS. Enable parallel replication workers with replica_parallel_workers=4 or higher in MySQL 8.x to distribute transaction application across multiple threads based on database or logical clock.

Not without safeguards. Asynchronous replication can lose committed transactions if the source crashes before shipping binlog events. For payment systems like those I have built for Nepalese gateways, use semi-synchronous replication with rpl_semi_sync_source_wait_for_replica_count=1 to guarantee at least one replica acknowledges each commit before returning success to the application.

Always enable TLS for replication traffic using REQUIRE SSL in the replication user grant. Configure source_ssl_ca, replica_ssl_ca, and certificate paths in my.cnf. Restrict firewall rules to allow only port 3306 from known replica IPs. Never expose MySQL ports publicly without encryption and IP whitelisting, especially on shared EC2 instances hosting multiple client sites.

Yes, using mysqldump --single-transaction --master-data=2 or mysqlpump for InnoDB-heavy databases. Alternatively, clone the source with Percona XtraBackup or MySQL Enterprise Backup while the server runs. Restore the backup on the new replica, then point it to the source using GTID auto-positioning. This avoids FLUSH TABLES WITH READ LOCK downtime entirely.

Promote the most up-to-date replica by running STOP REPLICA and RESET REPLICA ALL, then reconfigure other replicas to use it as their new source. With GTIDs enabled, this process is deterministic. Update DNS or load balancer targets immediately. Test failover procedures quarterly; untested promotion scripts cause extended outages during real incidents.

Query performance_schema.replication_connection_status and replication_applier_status tables instead of relying solely on SHOW REPLICA STATUS. Set up alerts for IO_THREAD or SQL_THREAD stopping, Seconds_Behind_Source exceeding thresholds, or GTID gaps. On production systems I maintain, Nagios or Prometheus exporters check these metrics every thirty seconds and page on-call engineers when replication breaks.

Yes, but expect higher latency affecting semi-synchronous performance. Use row-based binlog format (binlog_format=ROW) to avoid non-deterministic statement issues across time zones or collations. Compress replication streams with replica_compressed_protocol=1 to reduce bandwidth costs. Monitor network jitter closely; cross-region links in South Asia can introduce variable delays that trigger timeout errors.

Run DDL on the source during low-traffic windows and verify completion on all replicas before proceeding. Use pt-online-schema-change or gh-ost for large tables to avoid blocking replication. Never execute ALTER TABLE directly on replicas. Schema drift causes silent data corruption or replication failures. Validate table structures match across nodes using checksums after migrations complete.

Using weak passwords for replication accounts, disabling SSL, granting excessive privileges beyond REPLICATION SLAVE, storing credentials in plaintext config files, and neglecting to restrict network access. Rotate replication credentials regularly. Store passwords in encrypted vaults or environment variables. Audit replication users quarterly. These oversights expose sensitive business data, particularly problematic for legal-tech portals handling client documents.

Yes, for offloading SELECT queries from reporting, search, or analytics plugins. Configure wpdb read replicas via drop-ins or object caching layers routing reads appropriately. However, core WordPress assumes single-writer consistency; never direct write operations to replicas. Test plugin compatibility thoroughly first. Many eCommerce stores I have optimized see improved checkout performance after moving heavy catalog queries to dedicated replicas.

Choose Galera when you need synchronous multi-master writes with zero data loss tolerance and automatic node provisioning. Traditional async replication suits read-scaling, disaster recovery, or geographic distribution where eventual consistency is acceptable. Galera adds write latency overhead and requires identical hardware specs across nodes. For most Nepal-based SMB applications, async replication with proper monitoring delivers better cost-efficiency than full clustering.

Share this article

Quick Contact Options
Choose how you want to connect me: