• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Disaster Recovery 101: Architecting Auto-Failovers for PostgreSQL and PHP Deployments on DigitalOcean

Disaster Recovery 101: Architecting Auto-Failovers for PostgreSQL and PHP Deployments on DigitalOcean

Designing for High Availability: PostgreSQL and PHP Auto-Failover on DigitalOcean

This document outlines a robust, automated failover strategy for a typical PostgreSQL and PHP application stack deployed on DigitalOcean. The core objective is to minimize downtime by automatically redirecting application traffic to a standby database instance in the event of a primary database failure. We will leverage DigitalOcean’s managed PostgreSQL, a simple yet effective health check mechanism, and a lightweight orchestration script.

Prerequisites and Setup

Before diving into the failover logic, ensure the following are in place:

  • Two DigitalOcean Managed PostgreSQL databases: one designated as primary, the other as standby. Both should be in the same region for minimal latency.
  • A DigitalOcean Droplet (or multiple for application redundancy) running your PHP application. This Droplet will host the failover orchestration script.
  • SSH access to the Droplet designated for orchestration.
  • Basic familiarity with PostgreSQL replication and SQL commands.
  • The psql command-line utility installed on the orchestration Droplet.

Configuring PostgreSQL Replication

DigitalOcean’s Managed PostgreSQL handles the underlying replication setup. When you create a second database cluster and designate it as a read-only replica of your primary, the streaming replication is automatically configured. The key is to ensure the standby database is indeed replicating from the primary. You can verify this via the DigitalOcean control panel or programmatically.

Health Check Mechanism

A reliable health check is paramount. We’ll implement a simple check that attempts to connect to the primary PostgreSQL instance and execute a trivial query. If this fails, we assume the primary is unhealthy.

PHP Application Configuration

Your PHP application’s database connection configuration should be dynamic. Instead of hardcoding the primary database endpoint, use environment variables or a configuration file that can be updated by our failover script. For this example, we’ll assume a configuration file config.php that is included by your application.

 PDO::ERRMODE_EXCEPTION]);
    // Optional: Perform a simple query to ensure connection is active
    $stmt = $pdo->query("SELECT 1");
    $stmt->execute();
    if ($stmt->fetchColumn() !== '1') {
        throw new PDOException("Database check query failed.");
    }
    // echo "Successfully connected to database: " . DB_HOST . "\n";
} catch (PDOException $e) {
    // In a real application, you'd log this and potentially trigger a notification
    // For failover, this exception is the signal to initiate the process.
    error_log("Database connection failed: " . $e->getMessage());
    // In a production scenario, you might want to redirect to a maintenance page
    // or attempt to connect to a known standby. For this script, we'll let it fail
    // and rely on the external script to fix it.
    throw $e; // Re-throw to indicate application failure
}
?>

The key here is the fallback to environment variables. This allows our failover script to dynamically set DB_HOST and potentially other connection parameters.

The Failover Orchestration Script (Bash)

We’ll create a Bash script that runs periodically (e.g., via cron) on the application server. This script will:

  • Attempt to connect to the primary PostgreSQL instance.
  • If the connection fails, initiate the failover process.
  • Promote the standby database to primary.
  • Update the application’s database configuration (via environment variables or file modification).
  • Perform a quick verification of the new primary.
  • Optionally, notify administrators.

Script Implementation

Save the following script as /opt/scripts/pgsql_failover.sh on your application server. Ensure it’s executable (`chmod +x /opt/scripts/pgsql_failover.sh`).

#!/bin/bash

# --- Configuration ---
PRIMARY_DB_HOST="your_primary_db_host.oregon.db.ondigitalocean.com"
STANDBY_DB_HOST="your_standby_db_host.oregon.db.ondigitalocean.com"
DB_PORT="25060"
DB_USER="doadmin"
DB_PASSWORD="your_db_password"
DB_NAME="your_database_name"
APP_CONFIG_FILE="/var/www/your_app/config.php" # Path to your PHP config file
ENV_VAR_FILE="/etc/your_app/.env" # Path to a .env file if you use one
LOG_FILE="/var/log/pgsql_failover.log"
NOTIFICATION_EMAIL="[email protected]"
HEALTH_CHECK_QUERY="SELECT 1"

# --- Functions ---
log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

send_notification() {
    SUBJECT="PostgreSQL Failover Alert: $1"
    BODY="$2"
    echo "$BODY" | mail -s "$SUBJECT" "$NOTIFICATION_EMAIL"
    log_message "Notification sent: $SUBJECT"
}

check_primary_db() {
    PGPASSWORD="$DB_PASSWORD" psql -h "$PRIMARY_DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "$HEALTH_CHECK_QUERY" &>/dev/null
    return $?
}

update_app_config() {
    # Option 1: Update a PHP config file directly (requires careful parsing/writing)
    # This is more complex and prone to errors if the file format changes.
    # For simplicity, we'll focus on updating environment variables.

    # Option 2: Update a .env file (common with frameworks like Laravel)
    if [ -f "$ENV_VAR_FILE" ]; then
        log_message "Updating environment variable file: $ENV_VAR_FILE"
        # Use sed to replace the DB_HOST line. Assumes DB_HOST is on its own line.
        # Be cautious with special characters in your hostnames.
        sed -i "s/^DB_HOST=.*/DB_HOST=$STANDBY_DB_HOST/" "$ENV_VAR_FILE"
        # If your framework requires a cache clear or config reload, add it here.
        # Example for Laravel: php artisan config:cache
        # Example for Symfony: php bin/console cache:clear
        log_message "Successfully updated DB_HOST to $STANDBY_DB_HOST in $ENV_VAR_FILE"
        return 0
    else
        log_message "Error: Environment variable file not found at $ENV_VAR_FILE"
        return 1
    fi
}

promote_standby() {
    log_message "Attempting to promote standby database: $STANDBY_DB_HOST"
    PGPASSWORD="$DB_PASSWORD" psql -h "$STANDBY_DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT pg_promote();" &>/dev/null
    if [ $? -eq 0 ]; then
        log_message "Standby database $STANDBY_DB_HOST successfully promoted."
        return 0
    else
        log_message "Error: Failed to promote standby database $STANDBY_DB_HOST."
        return 1
    fi
}

verify_new_primary() {
    log_message "Verifying connection to new primary: $STANDBY_DB_HOST"
    PGPASSWORD="$DB_PASSWORD" psql -h "$STANDBY_DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "$HEALTH_CHECK_QUERY" &>/dev/null
    return $?
}

# --- Main Logic ---
log_message "Starting PostgreSQL health check..."

if check_primary_db; then
    log_message "Primary database $PRIMARY_DB_HOST is healthy."
    exit 0
else
    log_message "Primary database $PRIMARY_DB_HOST is UNHEALTHY. Initiating failover."

    # 1. Promote the standby
    if promote_standby; then
        # 2. Update application configuration
        if update_app_config; then
            # 3. Verify the new primary
            if verify_new_primary; then
                log_message "Failover successful. New primary is $STANDBY_DB_HOST."
                send_notification "SUCCESSFUL" "PostgreSQL failover completed. New primary is $STANDBY_DB_HOST. Application configuration updated."
                exit 0
            else
                log_message "Error: Failed to verify connection to the new primary database $STANDBY_DB_HOST after promotion."
                send_notification "FAILED - Verification Error" "PostgreSQL failover initiated, standby promoted, but verification of new primary $STANDBY_DB_HOST failed. Manual intervention required."
                exit 1
            fi
        else
            log_message "Error: Failed to update application configuration after promoting standby."
            send_notification "FAILED - Config Update Error" "PostgreSQL failover initiated, standby promoted, but application configuration update failed. Manual intervention required."
            exit 1
        fi
    else
        log_message "Error: Failed to promote standby database $STANDBY_DB_HOST."
        send_notification "FAILED - Promotion Error" "PostgreSQL failover initiated, but promotion of standby $STANDBY_DB_HOST failed. Manual intervention required."
        exit 1
    fi
fi

Script Configuration Details

Crucially, update the placeholder values in the --- Configuration --- section of the script with your actual database credentials, hostnames, database name, and the correct paths to your application’s configuration files. The script prioritizes updating a .env file, which is a common pattern for managing environment-specific configurations in PHP applications and frameworks.

Automating the Health Check with Cron

To make this process automatic, schedule the script to run at regular intervals using cron. A check every 1-5 minutes is usually sufficient for most applications.

Cron Job Setup

Edit the crontab for the user that has permissions to write to the log file and potentially the .env file (often the web server user, e.g., www-data, or a dedicated deployment user).

# Edit crontab for the www-data user (or your application user)
sudo crontab -u www-data -e

# Add the following line to run the script every 2 minutes:
*/2 * * * * /opt/scripts/pgsql_failover.sh >> /var/log/pgsql_failover.log 2>&1

Note: Ensure the cron user has the necessary permissions to execute the script, write to the log file, and modify the .env file. The >> /var/log/pgsql_failover.log 2>&1 part redirects both standard output and standard error to the log file, which is essential for debugging.

Important Considerations and Enhancements

Replication Lag and Stale Reads

Streaming replication is not instantaneous. There can be a small lag between the primary and standby. During a failover, if your application performs reads immediately after the switch, it might read slightly stale data. For critical applications, consider:

  • Implementing a read-after-write consistency check in your application logic.
  • Using a more sophisticated monitoring tool that can detect replication lag and only trigger failover if the lag is within acceptable bounds.
  • For very high-consistency requirements, explore synchronous replication (though this impacts write performance).

Idempotency and Race Conditions

The script is designed to be idempotent: running it multiple times when the primary is down should not cause harm. However, in highly distributed systems or with very frequent cron jobs, race conditions are possible. For instance, two instances of the script could theoretically run simultaneously. Ensure your cron jobs are configured to prevent overlapping execution if this is a concern (e.g., using flock).

Database Credentials Management

Storing database passwords directly in scripts or configuration files is a security risk. For production environments, consider:

  • Using DigitalOcean’s secrets management or a dedicated secrets manager (like HashiCorp Vault).
  • Granting the orchestration script minimal necessary privileges.
  • Ensuring strict file permissions on sensitive configuration files.

Application-Level Failover Logic

For even more resilience, your PHP application itself could be aware of the standby database. When a connection error occurs, instead of just failing, it could attempt to connect to the standby. This provides an immediate, application-driven failover before the external script even kicks in. The external script then acts as a fallback and ensures the standby is promoted and configuration is permanently updated.

Monitoring and Alerting

While the script sends email notifications, integrate with a more comprehensive monitoring system (e.g., Prometheus, Datadog, New Relic) to:

  • Monitor the health check script’s execution.
  • Track replication lag.
  • Alert on failed failover attempts.
  • Monitor the overall health of both database instances.

Promoting the Old Primary

After a successful failover, the original primary database is now out of sync. It should be treated as a potential new standby. You will need to manually reconfigure it to replicate from the *new* primary and then potentially switch back during a planned maintenance window if desired, or keep the new primary as is.

Conclusion

This automated failover strategy provides a solid foundation for ensuring the high availability of your PostgreSQL and PHP deployments on DigitalOcean. By combining DigitalOcean’s managed services with a well-crafted orchestration script and robust monitoring, you can significantly reduce Mean Time To Recovery (MTTR) and protect your application from unexpected database outages.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability
  • Scala Pekko vs. Go Goroutines: Actor Model vs. CSP for Event-Driven Reactive Systems
  • Java Loom Virtual Threads vs. Go Goroutines: Under-the-Hood Scheduler and Thread Overhead Comparison

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (584)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (806)
  • PHP (5)
  • PHP Development (21)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (19)
  • Ruby on Rails (1)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (7)
  • WordPress Theme Development (357)

Recent Posts

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (806)
  • Debugging & Troubleshooting (584)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala