{"id":1018,"date":"2025-06-29T15:03:01","date_gmt":"2025-06-29T15:03:01","guid":{"rendered":"https:\/\/james-batchelor.com\/?p=1018"},"modified":"2025-07-05T11:33:46","modified_gmt":"2025-07-05T11:33:46","slug":"mysql-replication","status":"publish","type":"post","link":"https:\/\/james-batchelor.com\/index.php\/2025\/06\/29\/mysql-replication\/","title":{"rendered":"MySQL Replication"},"content":{"rendered":"\n<p>Sure, its not the most original of topics, but it is one I&#8217;ve relied upon from time to time. For years, <a href=\"https:\/\/www.digitalocean.com\/community\/tutorials\/how-to-set-up-replication-in-mysql\">this guide from Digital Ocean<\/a> was my go to choice.<\/p>\n\n\n\n<p>It wasn&#8217;t until my most recent visit for an upcoming project that things looked, well, a little different. It was mostly the same, but subtle differences meant it was no longer compatible with how I&#8217;d been familiar with setting it up.<\/p>\n\n\n\n<p>Therefore this quick post is to capture the old method of setting it up for posterity.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><a href=\"https:\/\/james-batchelor.com\/wp-content\/uploads\/2025\/06\/image.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"410\" src=\"https:\/\/james-batchelor.com\/wp-content\/uploads\/2025\/06\/image-1024x410.png\" alt=\"\" class=\"wp-image-1024\" srcset=\"https:\/\/james-batchelor.com\/wp-content\/uploads\/2025\/06\/image-1024x410.png 1024w, https:\/\/james-batchelor.com\/wp-content\/uploads\/2025\/06\/image-300x120.png 300w, https:\/\/james-batchelor.com\/wp-content\/uploads\/2025\/06\/image-768x307.png 768w, https:\/\/james-batchelor.com\/wp-content\/uploads\/2025\/06\/image.png 1059w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/a><\/figure><\/div>\n\n\n<!--more-->\n\n\n\n<p>The below steps are to replicate all MySQL databases on another server.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prepare MySQL .conf files<\/h2>\n\n\n\n<p>To allow MySQL to replicate, the servers must be arranged in a hirarchy and logging enabled so each system can keep track and update any changes made.<\/p>\n\n\n\n<p>The .conf files can be found in the following locations:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/etc\/my.cnf.d\/server.cnf<\/code><\/pre>\n\n\n\n<p>Or on newer distros:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/etc\/mysql\/mariadb.conf.d\/50-server.cnf<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Source\/Master Server<\/h3>\n\n\n\n<p>On the source server (server you want to copy <em>from<\/em>), add the following lines below and within the [mysqld] section:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>bind-address            = {local ip address},{remote ip address}\nserver-id               = 1\nlog_bin                 = \/var\/log\/mariadb\/mysql-bin.log\n\nbinlog-format = mixed\nsync_binlog=1<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Replica\/Slave Server<\/h3>\n\n\n\n<p>On the replica server (server that databases are copied to), add the following lines below and within the [mysqld] section:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>server-id               = 2\nlog_bin                 = \/var\/log\/mysql\/mysql-bin.log\nrelay-log               = \/var\/log\/mysql\/mysql-relay-bin.log\nbinlog-format           = mixed\nread-only               = 1<\/code><\/pre>\n\n\n\n<p>On both servers, MySQL requires a restart to pick up the config changes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>systemctl restart mariadb<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create a replication user<\/h2>\n\n\n\n<p>A replication needs to be authenticated, rather than using the root user its preferred to create a user that is only able to perform the replication.<\/p>\n\n\n\n<p>On the source server, login to MySQL:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mysql -u root -p<\/code><\/pre>\n\n\n\n<p>Create the user, and give it replication duties with the following commands:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE USER '{replica_user}'@'{replica_ip}' IDENTIFIED BY '{password}';\nGRANT REPLICATION SLAVE ON *.* TO '{replica_user}'@'{replica_ip}';\nFLUSH PRIVILEGES;<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Copy current databases<\/h2>\n\n\n\n<p>Before the replication can begin, we&#8217;ll need a copy of the existing data captured within a known point of time. This allows the replica to know where to pick up from where the imported databases left off.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Source Server<\/h3>\n\n\n\n<p>If not already, log into mysql :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mysql -u root -p<\/code><\/pre>\n\n\n\n<p>Lock the databases from being able to be written to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FLUSH TABLES WITH READ LOCK;<\/code><\/pre>\n\n\n\n<p>Display the current state of the logfiles while the databases are frozen in time:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SHOW MASTER STATUS;<\/code><\/pre>\n\n\n\n<p>This will produce something similar to this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>MariaDB &#91;(none)]&gt; SHOW MASTER STATUS;\n+------------------+----------+--------------+------------------+\n| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |\n+------------------+----------+--------------+------------------+\n| mysql-bin.000016 |      342 |              |                  |\n+------------------+----------+--------------+------------------+\n1 row in set (0.000 sec)<\/code><\/pre>\n\n\n\n<p>Make a note of the File and Position values, we&#8217;ll need this as a reference point for the replica. I&#8217;d recommend saving it as a file for future reference.<\/p>\n\n\n\n<p>Now, open up new SSH session to the source server. Its important that the current MySQL session is kept open and in its locked state.<\/p>\n\n\n\n<p>Take a mysqldump of the entire database:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mysqldump -u root -p --all-databases &gt; mysql_replication.sql<\/code><\/pre>\n\n\n\n<p>When this completes, return to the original session to return the databases to normal:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>UNLOCK TABLES;<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Transfer SQL file to Replica<\/h2>\n\n\n\n<p>Move the generated mysqldump file to the replica server, I usually transfer the master status txt file too for future reference:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>scp mysql_replication.sql mysql_master.txt root@{replica_ip}:\/root\/<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Setup Replication<\/h2>\n\n\n\n<p>Import the newly transferred file to the replica&#8217;s MySQL:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mysql -u root -p &lt; mysql_replication.sql<\/code><\/pre>\n\n\n\n<p>Then log in to MySQL:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mysql -u root -p<\/code><\/pre>\n\n\n\n<p>Time to setup the replication, using the replica user setup previously, and the logfile positions captured during the mysqldump. The SHOW MASTER STATUS; output from above is referenced in this command example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code> CHANGE MASTER TO MASTER_HOST='{source_ip}', MASTER_USER='{replica_user}', MASTER_PASSWORD='{password}', MASTER_LOG_FILE='mysql-bin.000016', MASTER_LOG_POS=342;<\/code><\/pre>\n\n\n\n<p>Reference points now in place, start the replication process:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>START SLAVE;<\/code><\/pre>\n\n\n\n<p>You can check it is running with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SHOW SLAVE STATUS\\G;<\/code><\/pre>\n\n\n\n<p>In the output you&#8217;re looking for Slave_IO_Running and Slave_SQL_Running to both be Yes:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><a href=\"https:\/\/james-batchelor.com\/wp-content\/uploads\/2025\/06\/image-1.png\"><img loading=\"lazy\" decoding=\"async\" width=\"538\" height=\"97\" src=\"https:\/\/james-batchelor.com\/wp-content\/uploads\/2025\/06\/image-1.png\" alt=\"\" class=\"wp-image-1030\" srcset=\"https:\/\/james-batchelor.com\/wp-content\/uploads\/2025\/06\/image-1.png 538w, https:\/\/james-batchelor.com\/wp-content\/uploads\/2025\/06\/image-1-300x54.png 300w\" sizes=\"auto, (max-width: 538px) 85vw, 538px\" \/><\/a><\/figure><\/div>\n\n\n<p>With both reporting Yes, the replication is running. All changes made to the source server is automatically pushed to the replica.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Notes<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Ports<\/h3>\n\n\n\n<p>The two databases communicate over its MySQL port, default is 3306. Ensure that both servers are able to communicate on this port.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Authentication<\/h3>\n\n\n\n<p>Immediately after setting up replication, you&#8217;ll be able to log in to the replica server as normal. However, if the service\/server was to be restarted you may not be able to login as normal.<\/p>\n\n\n\n<p>As this guide replicates everything on the source MySQL, it will also replicate the users and permissions.<\/p>\n\n\n\n<p>Following a replication start, the &#8220;old&#8221; credentials are still usable But following a service restart, the credentials of the source are the new normal on the replica, and should be used for any future login.<\/p>\n\n\n\n<p>This will also apply to any instances of phpMyAdmin.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Sure, its not the most original of topics, but it is one I&#8217;ve relied upon from time to time. For years, this guide from Digital Ocean was my go to choice. It wasn&#8217;t until my most recent visit for an upcoming project that things looked, well, a little different. It was mostly the same, but &hellip; <a href=\"https:\/\/james-batchelor.com\/index.php\/2025\/06\/29\/mysql-replication\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;MySQL Replication&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[212,5],"tags":[50,414],"class_list":["post-1018","post","type-post","status-publish","format-standard","hentry","category-network","category-servers","tag-mysql","tag-replication"],"_links":{"self":[{"href":"https:\/\/james-batchelor.com\/index.php\/wp-json\/wp\/v2\/posts\/1018","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/james-batchelor.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/james-batchelor.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/james-batchelor.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/james-batchelor.com\/index.php\/wp-json\/wp\/v2\/comments?post=1018"}],"version-history":[{"count":16,"href":"https:\/\/james-batchelor.com\/index.php\/wp-json\/wp\/v2\/posts\/1018\/revisions"}],"predecessor-version":[{"id":1038,"href":"https:\/\/james-batchelor.com\/index.php\/wp-json\/wp\/v2\/posts\/1018\/revisions\/1038"}],"wp:attachment":[{"href":"https:\/\/james-batchelor.com\/index.php\/wp-json\/wp\/v2\/media?parent=1018"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/james-batchelor.com\/index.php\/wp-json\/wp\/v2\/categories?post=1018"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/james-batchelor.com\/index.php\/wp-json\/wp\/v2\/tags?post=1018"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}