{"id":4891,"date":"2026-10-01T09:02:05","date_gmt":"2026-10-01T03:32:05","guid":{"rendered":"https:\/\/cpanelfree.com\/blog\/how-to-setup-postgresql-primary-replica-replication-on-linux\/"},"modified":"2026-10-01T09:02:05","modified_gmt":"2026-10-01T03:32:05","slug":"how-to-setup-postgresql-primary-replica-replication-on-linux","status":"publish","type":"post","link":"https:\/\/cpanelfree.com\/blog\/how-to-setup-postgresql-primary-replica-replication-on-linux\/","title":{"rendered":"How to Setup PostgreSQL Primary-Replica Replication on Linux"},"content":{"rendered":"<p>Scaling database infrastructure beyond a single compute node is a milestone every systems engineer faces when read concurrency saturates system I\/O and CPU queues. In mission-critical Linux deployments, relying on a standalone database instance creates a critical single point of failure (SPOF) where unplanned hardware failure, kernel panics, or maintenance windows lead directly to catastrophic downtime. While staging environments on platforms like <a href=\"https:\/\/cpanelfree.com\">CpanelFree<\/a> allow developers to safely prototype distributed applications, setting up enterprise-grade PostgreSQL primary-replica streaming replication is the gold standard for high availability, zero-downtime read scaling, and disaster resilience.<\/p>\n<p><!-- more --><\/p>\n<h2>How PostgreSQL Streaming Replication Works<\/h2>\n<div class=\"wp-block-group\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:20px 0;border-radius:4px\">\n<p style=\"margin:0;font-size:15px;line-height:1.6;color:#333\"><strong style=\"color:#001b41\">Direct Answer:<\/strong> PostgreSQL primary-replica streaming replication functions by continuously streaming Write-Ahead Log (WAL) records from a primary read-write node to one or more read-only standby replicas over TCP port 5432. The replica&#8217;s WAL receiver process buffers these transactions and applies them immediately to disk, providing sub-millisecond replication lag, horizontal read scaling, and near-instant failover capability.<\/p>\n<\/div>\n<p>PostgreSQL streaming replication relies on physical block-level replication using the database engine&#8217;s Write-Ahead Log (WAL). Whenever a client executes an <code>INSERT<\/code>, <code>UPDATE<\/code>, or <code>DELETE<\/code> transaction on the primary node, PostgreSQL writes the change to WAL buffers before committing the data to table heaps on disk. In streaming replication, a background process on the primary called the <strong>WAL Sender (walsender)<\/strong> transmits these WAL bytes over an authenticated TCP connection directly to the standby node&#8217;s <strong>WAL Receiver (walreceiver)<\/strong> process.<\/p>\n<p>Once received by the standby replica, the <strong>Startup process<\/strong> reads the incoming WAL stream and applies the exact disk page modifications in memory and to storage. Because physical streaming replication operates below the SQL parser and execution layer, it synchronizes the entire database cluster byte-for-byte, including system catalogs, indices, sequences, and table structures. When configured with <strong>Hot Standby<\/strong> mode enabled, the replica accepts concurrent read-only queries while continuously replaying changes streamed from the primary.<\/p>\n<blockquote class=\"wp-block-quote\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:24px 0\">\n<p><strong style=\"color:#001b41\">Architecture Note:<\/strong> Physical streaming replication synchronizes the entire PostgreSQL cluster (all databases within the instance). If your workload demands selective table filtering or bidirectional writes, logical replication is required instead. However, for high availability, disaster recovery, and generic read-replica pools, physical streaming replication provides superior raw throughput, minimal CPU overhead, and automated binary integrity.<\/p>\n<\/blockquote>\n<h2>Architectural Matrix: Default vs. Tuned Production Replication<\/h2>\n<p>Default PostgreSQL installations out-of-the-box are deliberately tuned for minimal memory footprints and conservative system settings. Operating a high-throughput production replication cluster requires modifying Linux kernel limits, network socket buffers, and PostgreSQL concurrency handlers. The comparative matrix below outlines key differences between standard default parameters and enterprise-tuned replication architectures.<\/p>\n<figure class=\"wp-block-table is-style-regular\">\n<table style=\"width:100%;border-collapse:collapse;margin:24px 0;font-size:15px;text-align:left\">\n<thead style=\"background:#001b41;color:#ffffff\">\n<tr>\n<th style=\"padding:12px 16px;border-bottom:2px solid #001b41\">Feature \/ Metric<\/th>\n<th style=\"padding:12px 16px;border-bottom:2px solid #001b41\">Standard \/ Default<\/th>\n<th style=\"padding:12px 16px;border-bottom:2px solid #001b41\">Tuned \/ Production<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Latency \/ Overhead<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Baseline<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">Optimal<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Replication Mechanism<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">File-based WAL Archiving (Delayed)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">Physical Streaming Replication + Slot<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Replication Lag (P99)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">16MB file boundaries (seconds to minutes)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">&lt; 5 ms (continuous byte stream)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">WAL Retention Risk<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">WAL recycled if standby disconnects<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">Guaranteed retention via Replication Slots<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Read Query Conflict Handling<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Queries canceled on WAL replay conflict<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">Managed via max_standby_streaming_delay<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Network TCP Keepalives<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">OS Default (7200s silent disconnect)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">Aggressive (idle=15s, cnt=5, intvl=5s)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Failover RTO (Recovery Time Objective)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">15\u201345 minutes (manual restore)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">&lt; 10 seconds (pg_ctl promote \/ automated)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<h2>Step 1: Linux Kernel and OS-Level Performance Tuning<\/h2>\n<p>Before launching database processes, the underlying Linux operating system must be tuned to prevent network buffer bottlenecks, out-of-memory kernel kills, and file descriptor starvation. On both the primary and replica servers (assumed Ubuntu 22.04\/24.04 or Debian 12 running PostgreSQL 16), create a dedicated kernel parameters file at <code>\/etc\/sysctl.d\/99-postgresql.conf<\/code>.<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/sysctl.d\/99-postgresql.conf - Production Kernel Tuning for PostgreSQL\n# Virtual Memory &amp; Memory Management\nvm.swappiness = 10\nvm.overcommit_memory = 2\nvm.overcommit_ratio = 80\nvm.dirty_background_ratio = 5\nvm.dirty_ratio = 10\n\n# Network TCP Socket Buffers for High-Throughput WAL Streaming\nnet.core.somaxconn = 4096\nnet.core.rmem_max = 16777216\nnet.core.wmem_max = 16777216\nnet.ipv4.tcp_rmem = 4096 87380 16777216\nnet.ipv4.tcp_wmem = 4096 65536 16777216\nnet.ipv4.tcp_max_syn_backlog = 8192\n\n# Fast TCP Connection Liveness &amp; Dead Peer Detection\nnet.ipv4.tcp_keepalive_time = 60\nnet.ipv4.tcp_keepalive_intvl = 10\nnet.ipv4.tcp_keepalive_probes = 5\n\n# Shared Memory and IPC\nkernel.sched_migration_cost_ns = 5000000\nkernel.sched_autogroup_enabled = 0<\/code><\/pre>\n<p>Apply the kernel configuration immediately without rebooting by executing:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo sysctl --system<\/code><\/pre>\n<p>Next, configure systemd resource limits to ensure PostgreSQL is never throttled by default system limits. Create a systemd drop-in override directory and configuration file at <code>\/etc\/systemd\/system\/postgresql.service.d\/override.conf<\/code>:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/systemd\/system\/postgresql.service.d\/override.conf\n[Service]\nLimitNOFILE=1048576\nLimitNPROC=524288\nLimitMEMLOCK=infinity\nTasksMax=infinity\nTimeoutSec=300<\/code><\/pre>\n<p>Reload the systemd daemon to register the unit changes:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo systemctl daemon-reload<\/code><\/pre>\n<h2>Step 2: Configuring the Primary PostgreSQL Node<\/h2>\n<p>Assume our production environment has two dedicated nodes on an isolated private VPC network:<\/p>\n<ul style=\"color:#444;line-height:1.8\">\n<li><strong>Primary Server IP:<\/strong> <code>10.0.10.50<\/code> (Hostname: <code>pg-primary.internal<\/code>)<\/li>\n<li><strong>Replica Server IP:<\/strong> <code>10.0.10.51<\/code> (Hostname: <code>pg-replica-01.internal<\/code>)<\/li>\n<\/ul>\n<h3>1. Create the Dedicated Replication User<\/h3>\n<p>Log in to the primary database server as the <code>postgres<\/code> administrative user and open the <code>psql<\/code> console. Create a dedicated role granted the <code>REPLICATION<\/code> attribute with a secure, hardened password using modern <code>scram-sha-256<\/code> authentication:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo -u postgres psql\n\n-- Within PostgreSQL interactive shell:\nCREATE ROLE replicator WITH REPLICATION LOGIN ENCRYPTED PASSWORD 'StrongAuthPassphraseSecure2026!';\n\\q<\/code><\/pre>\n<h3>2. Configure Client Authentication (pg_hba.conf)<\/h3>\n<p>PostgreSQL strictly controls network access using <code>pg_hba.conf<\/code>. Open <code>\/etc\/postgresql\/16\/main\/pg_hba.conf<\/code> on the primary server and add an explicit rule authorizing the <code>replicator<\/code> user to stream WAL transactions from the replica&#8217;s private IP subnet:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/postgresql\/16\/main\/pg_hba.conf - Replication ACL Rule\n# TYPE  DATABASE        USER            ADDRESS                 METHOD\nhost    replication     replicator      10.0.10.51\/32           scram-sha-256<\/code><\/pre>\n<h3>3. Tune Primary Engine Parameters (postgresql.conf)<\/h3>\n<p>Open <code>\/etc\/postgresql\/16\/main\/postgresql.conf<\/code> and verify or append the following streaming replication parameters. These settings instruct PostgreSQL to retain sufficient WAL logs, bind to internal network adapters, and allocate dedicated sender worker processes:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/postgresql\/16\/main\/postgresql.conf - Primary Replication Settings\nlisten_addresses = 'localhost,10.0.10.50'\nport = 5432\n\n# Write-Ahead Log Configuration\nwal_level = replica                  # Minimal level required for physical streaming\nmax_wal_size = 16GB\nmin_wal_size = 2GB\ncheckpoint_completion_target = 0.9\ncheckpoint_timeout = 15min\nwal_buffers = 64MB\n\n# Streaming Replication Allocation\nmax_wal_senders = 10                 # Maximum concurrent walsender processes\nmax_replication_slots = 10           # Maximum active replication slots\nwal_keep_size = 4096MB               # Retain minimum 4GB WAL on disk for lag tolerance\ntrack_commit_timestamp = on          # Helpful for conflict detection and lag auditing\n\n# Standby Read Feedback &amp; Keepalives\nwal_sender_timeout = 60s\ntcp_keepalives_idle = 15\ntcp_keepalives_interval = 5\ntcp_keepalives_count = 5<\/code><\/pre>\n<p>Save the file and restart the PostgreSQL service on the primary node to apply the new networking and WAL allocation directives:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo systemctl restart postgresql\nsudo systemctl status postgresql<\/code><\/pre>\n<blockquote class=\"wp-block-quote\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:24px 0\">\n<p><strong style=\"color:#001b41\">Architecture Note:<\/strong> Always verify your firewall rules before proceeding. Ensure the replica node (<code>10.0.10.51<\/code>) can communicate with the primary on TCP port <code>5432<\/code>. If using UFW, execute: <code>sudo ufw allow from 10.0.10.51 to any port 5432 proto tcp comment 'PostgreSQL Replication'<\/code>.<\/p>\n<\/blockquote>\n<h2>Step 3: Initializing and Provisioning the Standby Replica<\/h2>\n<p>Now switch to the secondary server (<code>10.0.10.51<\/code>). Ensure PostgreSQL 16 is installed with the exact same major and minor release as the primary. Before streaming begins, the standby database cluster directory must be cloned as a clean, consistent snapshot of the primary node.<\/p>\n<h3>1. Stop the PostgreSQL Service and Clear Data Directory<\/h3>\n<p>Stop the local PostgreSQL service on the replica and remove any automatically initialized empty database cluster files in <code>\/var\/lib\/postgresql\/16\/main<\/code>:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># Stop PostgreSQL on Replica\nsudo systemctl stop postgresql\n\n# Purge default initialized data directory\nsudo rm -rf \/var\/lib\/postgresql\/16\/main\/*<\/code><\/pre>\n<h3>2. Perform Base Backup via pg_basebackup with Automatic Slot Creation<\/h3>\n<p>Execute <code>pg_basebackup<\/code> as the <code>postgres<\/code> user. We utilize modern flags: <code>-R<\/code> (write replica configuration and <code>standby.signal<\/code>), <code>-C<\/code> (create replication slot on the primary), and <code>-S<\/code> (assign a designated slot name <code>replica_slot_1<\/code>):<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># Execute base backup stream from Primary node\nsudo -u postgres pg_basebackup \\\n  -h 10.0.10.50 \\\n  -p 5432 \\\n  -U replicator \\\n  -D \/var\/lib\/postgresql\/16\/main \\\n  -Fp \\\n  -Xs \\\n  -R \\\n  -P \\\n  -v \\\n  -C \\\n  -S replica_slot_1<\/code><\/pre>\n<p>When prompted, enter the password configured earlier (<code>StrongAuthPassphraseSecure2026!<\/code>). Alternatively, store the credentials in <code>~postgres\/.pgpass<\/code> with <code>chmod 600<\/code> permissions to allow automated deployment.<\/p>\n<p>The flags passed to <code>pg_basebackup<\/code> provide enterprise guarantees:<\/p>\n<ul style=\"color:#444;line-height:1.8\">\n<li><code>-D \/var\/lib\/postgresql\/16\/main<\/code>: Target destination directory for the base cluster clone.<\/li>\n<li><code>-Fp<\/code>: Plain format (direct file copying without tar compression).<\/li>\n<li><code>-Xs<\/code>: Stream Write-Ahead Logs concurrently with the backup so no separate archive pass is needed.<\/li>\n<li><code>-R<\/code>: Automatically generates <code>standby.signal<\/code> in the data directory and writes connection parameters to <code>postgresql.auto.conf<\/code>.<\/li>\n<li><code>-C -S replica_slot_1<\/code>: Automatically creates a persistent physical replication slot named <code>replica_slot_1<\/code> on the primary node.<\/li>\n<li><code>-P -v<\/code>: Enables real-time progress indicators and verbose logging.<\/li>\n<\/ul>\n<h3>3. Review Generated Standby Configuration<\/h3>\n<p>Inspect the generated <code>\/var\/lib\/postgresql\/16\/main\/postgresql.auto.conf<\/code> on the replica node:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># Automatically generated by pg_basebackup\nprimary_conninfo = 'user=replicator password=StrongAuthPassphraseSecure2026! host=10.0.10.50 port=5432 sslmode=prefer sslcompression=0 gssencmode=prefer krbsrvname=postgres target_session_attrs=any'\nprimary_slot_name = 'replica_slot_1'<\/code><\/pre>\n<p>Verify that the trigger file <code>standby.signal<\/code> exists in the data directory:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>ls -la \/var\/lib\/postgresql\/16\/main\/standby.signal<\/code><\/pre>\n<blockquote class=\"wp-block-quote\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:24px 0\">\n<p><strong style=\"color:#001b41\">Architecture Note:<\/strong> In PostgreSQL 12 and later, <code>recovery.conf<\/code> has been deprecated. Standby status is determined exclusively by the presence of the empty file <code>standby.signal<\/code> in the root data directory. As long as this file is present upon startup, PostgreSQL operates in read-only recovery mode, listening for incoming WAL packets.<\/p>\n<\/blockquote>\n<h3>4. Tune Replica Hot Standby Parameters<\/h3>\n<p>Edit <code>\/etc\/postgresql\/16\/main\/postgresql.conf<\/code> on the replica server to prevent long-running analytical queries from being prematurely killed when WAL changes conflict with existing MVCC snapshots:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/postgresql\/16\/main\/postgresql.conf - Replica Standby Tuning\nhot_standby = on\nmax_standby_archive_delay = 300s\nmax_standby_streaming_delay = 300s\nhot_standby_feedback = on\nwal_receiver_status_interval = 10s\nwal_receiver_timeout = 60s<\/code><\/pre>\n<p>Ensure correct file ownership and start the PostgreSQL service on the replica:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo chown -R postgres:postgres \/var\/lib\/postgresql\/16\/main\nsudo chmod 700 \/var\/lib\/postgresql\/16\/main\nsudo systemctl start postgresql\nsudo systemctl status postgresql<\/code><\/pre>\n<h2>Step 4: Real-Time Replication Verification and Lag Auditing<\/h2>\n<p>With both nodes active, execute telemetry queries to verify that WAL streaming is active and quantify replication lag in both bytes and elapsed time.<\/p>\n<h3>1. Telemetry Query on Primary Node<\/h3>\n<p>Log in to the primary database and inspect <code>pg_stat_replication<\/code>:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo -u postgres psql -x -c \"\nSELECT \n    pid,\n    usename,\n    application_name,\n    client_addr,\n    backend_start,\n    state,\n    sync_state,\n    sync_priority,\n    pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn) AS sent_lag_bytes,\n    pg_wal_lsn_diff(sent_lsn, write_lsn) AS write_lag_bytes,\n    pg_wal_lsn_diff(write_lsn, flush_lsn) AS flush_lag_bytes,\n    pg_wal_lsn_diff(flush_lsn, replay_lsn) AS replay_lag_bytes,\n    reply_time\nFROM pg_stat_replication;\n\"<\/code><\/pre>\n<p>A healthy replication stream reports <code>state: streaming<\/code> and <code>replay_lag_bytes: 0<\/code> (or single-digit kilobytes during intense batch updates).<\/p>\n<h3>2. Telemetry Query on Standby Replica<\/h3>\n<p>On the replica node, verify the status of the WAL receiver process and check recovery mode:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo -u postgres psql -x -c \"\nSELECT \n    pg_is_in_recovery() AS in_recovery,\n    status,\n    receive_start_lsn,\n    received_lsn,\n    latest_end_lsn,\n    sender_host,\n    sender_port,\n    slot_name\nFROM pg_stat_wal_receiver;\n\"<\/code><\/pre>\n<p>The query must return <code>in_recovery: t<\/code> and <code>status: streaming<\/code>, confirming the standby is operating as an active Hot Standby replica.<\/p>\n<h2>Step 5: Failover and Manual Standby Promotion<\/h2>\n<p>In a disaster recovery scenario where the primary node experiences unrecoverable hardware failure or power outage, the standby node can be promoted to become the new read-write primary in seconds.<\/p>\n<p>To promote the replica node immediately, run:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># Method A: Using pg_ctl (preferred for immediate execution)\nsudo -u postgres pg_ctlcluster 16 main promote\n\n# Method B: Using SQL command directly inside psql\nsudo -u postgres psql -c \"SELECT pg_promote();\"<\/code><\/pre>\n<p>Upon promotion, PostgreSQL deletes the <code>standby.signal<\/code> file, completes replaying any buffered WAL segments, switches the timeline from timeline 1 to timeline 2, and opens the database for read-write transactions. Verify the change by checking <code>SELECT pg_is_in_recovery();<\/code>, which will now return <code>f<\/code> (false).<\/p>\n<p>For production databases processing financial transactions, e-commerce storefronts, or high-throughput API endpoints where sub-millisecond NVMe I\/O is non-negotiable, hosting your PostgreSQL cluster on <a href=\"https:\/\/merahost.org\" target=\"_blank\" rel=\"noopener\">MeraHost Enterprise Cloud<\/a> guarantees dedicated NVMe storage tiers, predictable latency, and predictable pricing with zero renewal price hikes.<\/p>\n<h2>Frequently Asked Questions<\/h2>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">What is the difference between physical streaming replication and logical replication?<\/summary>\n<p style=\"margin-top:10px;color:#444\">Physical streaming replication duplicates the entire database cluster byte-for-byte at the storage block level using Write-Ahead Logs (WAL). It replicates all databases, tables, indices, schemas, and catalogs identically with minimal CPU overhead. Logical replication, introduced in PostgreSQL 10, operates at the SQL row level on a per-table publication\/subscription basis, allowing selective replication, data transformation, and cross-major-version migrations, but requires significantly higher CPU and decoding overhead.<\/p>\n<\/details>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">Why are replication slots necessary, and what operational risk do they introduce?<\/summary>\n<p style=\"margin-top:10px;color:#444\">Replication slots guarantee that the primary node will never recycle or purge WAL segments from disk until the connected standby replica has confirmed receipt and application. This prevents the dreaded &#8220;requested WAL segment has already been removed&#8221; failure if a replica goes offline for hours. However, if an offline standby is neglected and a slot remains abandoned, the primary will continue accumulating WAL files until disk space is 100% exhausted. Always configure <code>max_slot_wal_keep_size<\/code> (e.g., 32GB) to protect the primary from out-of-disk crashes.<\/p>\n<\/details>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">Can a PostgreSQL standby replica handle write operations or create temporary tables?<\/summary>\n<p style=\"margin-top:10px;color:#444\">No. A standby replica in Hot Standby mode is strictly read-only. Any attempt to execute <code>INSERT<\/code>, <code>UPDATE<\/code>, <code>DELETE<\/code>, or DDL operations like <code>CREATE TABLE<\/code> will trigger a read-only transaction error (<code>ERROR: cannot execute INSERT in a read-only transaction<\/code>). Furthermore, standbys cannot create temporary tables unless <code>wal_level<\/code> is configured appropriately and read-write session states are prohibited. All writes must target the primary node directly.<\/p>\n<\/details>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">How do I resolve &#8220;canceling statement due to conflict with recovery&#8221; errors on the replica?<\/summary>\n<p style=\"margin-top:10px;color:#444\">When a replica executes a long-running read query on a table that is simultaneously being vacuumed or modified by incoming WAL replay from the primary, PostgreSQL must choose between delaying recovery or canceling the read query. To eliminate query cancellations, enable <code>hot_standby_feedback = on<\/code> on the replica (which signals the primary not to clean up dead tuples still needed by the standby&#8217;s active queries) and increase <code>max_standby_streaming_delay = 300s<\/code>.<\/p>\n<\/details>\n<div class=\"wp-block-group has-background\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:8px;padding:32px;margin:40px 0;text-align:center\">\n<h3 style=\"color:#001b41;margin-top:0;font-size:24px;font-weight:700\">Deploy Enterprise-Grade Production Infrastructure<\/h3>\n<p style=\"color:#444;font-size:16px;line-height:1.6;max-width:680px;margin:12px auto 24px auto\">Need guaranteed performance with zero price hikes? Host mission-critical workloads on <strong style=\"color:#001b41\">MeraHost<\/strong> with pure Enterprise NVMe, LiteSpeed Web Server, and Same Renewal Price, Always (starting at \u20b999\/mo).<\/p>\n<div class=\"wp-block-buttons\" style=\"display:flex;gap:16px;justify-content:center;flex-wrap:wrap\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link\" href=\"https:\/\/merahost.org\" style=\"background:#001b41;color:#ffffff;font-weight:700;padding:12px 28px;border-radius:4px;text-decoration:none;display:inline-block;font-size:15px\" target=\"_blank\" rel=\"noopener\">Explore MeraHost NVMe Cloud &rarr;<\/a><\/div>\n<div class=\"wp-block-button is-style-outline\"><a class=\"wp-block-button__link\" href=\"https:\/\/cpanelfree.com\" style=\"background:transparent;color:#001b41;font-weight:600;padding:12px 24px;border:2px solid #001b41;border-radius:4px;text-decoration:none;display:inline-block;font-size:15px\">Deploy Free Staging on CpanelFree<\/a><\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Deploy high-availability PostgreSQL streaming replication on Linux. Master primary-replica synchronization, replication slots, and failover.<\/p>\n","protected":false},"author":1,"featured_media":4890,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[163],"tags":[57,177,87,101],"class_list":["post-4891","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-databases","tag-almalinux","tag-databases-performance","tag-devops","tag-sysadmin"],"_links":{"self":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/4891","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/comments?post=4891"}],"version-history":[{"count":0,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/4891\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media\/4890"}],"wp:attachment":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media?parent=4891"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/categories?post=4891"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/tags?post=4891"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}