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

Vengala Vinay

Having 9+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Server Monitoring Best Practices: Keeping Your WordPress App and PostgreSQL Clusters Alive on AWS

Server Monitoring Best Practices: Keeping Your WordPress App and PostgreSQL Clusters Alive on AWS

Establishing a Robust Monitoring Foundation with AWS CloudWatch

For any production WordPress application hosted on AWS, particularly those leveraging PostgreSQL for their database layer, a comprehensive monitoring strategy is paramount. This isn’t about basic uptime checks; it’s about deep visibility into application performance, resource utilization, and potential failure points across your entire stack. AWS CloudWatch serves as the foundational service for this, providing metrics, logs, and alarms that are essential for proactive management and rapid incident response.

Our monitoring strategy will focus on three key areas: EC2 instance health (for WordPress web servers), RDS PostgreSQL cluster health, and application-level performance. We’ll configure CloudWatch agents, custom metrics, and alarms to ensure we have actionable insights.

Monitoring WordPress EC2 Instances

The CloudWatch Agent is indispensable for collecting detailed system-level metrics beyond the default EC2 metrics. This includes disk I/O, memory utilization, and custom log file monitoring. For a typical WordPress setup, we’ll want to monitor Apache/Nginx access and error logs, PHP-FPM logs, and potentially application-specific logs.

First, ensure the CloudWatch Agent is installed and configured on your WordPress EC2 instances. The configuration is typically managed via a JSON file. Here’s an example of a `amazon-cloudwatch-agent.json` configuration that collects system metrics and specific log files:

{
  "agent": {
    "metrics_collection_interval": 60
  },
  "metrics": {
    "namespace": "WordPress/App",
    "metrics_collected": {
      "cpu": {
        "resources": [
          "*"
        ],
        "measurement": [
          "cpu_usage_idle",
          "cpu_usage_iowait",
          "cpu_usage_user",
          "cpu_usage_system"
        ],
        "totalcpu_time_metrics": true
      },
      "disk": {
        "measurement": [
          "used_percent",
          "inodes_free"
        ],
        "resources": [
          "/"
        ]
      },
      "mem": {
        "measurement": [
          "mem_used_percent"
        ]
      },
      "statsd": {
        "service_address": "udp:localhost:8125"
      },
      "collectd": {
        "data_source": [
          {
            "type": "cpu",
            "values": [
              "user",
              "system"
            ]
          },
          {
            "type": "memory",
            "values": [
              "used",
              "free"
            ]
          },
          {
            "type": "disk",
            "values": [
              "read",
              "write"
            ]
          },
          {
            "type": "interface",
            "values": [
              "rx_bytes",
              "tx_bytes"
            ]
          }
        ]
      }
    }
  },
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          {
            "file_path": "/var/log/httpd/access_log",
            "log_group_name": "WordPress/App/Apache/Access",
            "log_stream_name": "{instance_id}/access"
          },
          {
            "file_path": "/var/log/httpd/error_log",
            "log_group_name": "WordPress/App/Apache/Error",
            "log_stream_name": "{instance_id}/error"
          },
          {
            "file_path": "/var/log/php-fpm/www-error.log",
            "log_group_name": "WordPress/App/PHP-FPM/Error",
            "log_stream_name": "{instance_id}/php-fpm-error"
          }
        ]
      }
    }
  }
}

After saving this configuration (e.g., to /opt/aws/amazon-cloudwatch-agent/bin/config.json), you can start the agent using:

sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json -s

This command fetches the configuration, applies it, and starts the agent. You can verify its status with sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a status.

Monitoring RDS PostgreSQL Clusters

RDS provides a rich set of performance metrics out-of-the-box. However, for deep PostgreSQL diagnostics, we need to enable enhanced monitoring and potentially custom metrics. Enhanced Monitoring provides more granular OS-level metrics for the underlying instance, and Performance Insights offers a powerful, visual way to analyze database load.

Enabling Enhanced Monitoring:

  • Navigate to your RDS PostgreSQL instance in the AWS Console.
  • Under the “Configuration” tab, edit the instance.
  • Scroll down to “Enhanced Monitoring” and set the “Monitoring Interval” (e.g., 15 seconds for more granular data).
  • Ensure it’s enabled.

This will push detailed OS-level metrics to CloudWatch, including CPU utilization by process, disk I/O, network traffic, and memory usage. Key metrics to monitor include:

  • CPUUtilization (from RDS Enhanced Monitoring)
  • DBLoad (from Performance Insights)
  • DatabaseConnections
  • ReadIOPS and WriteIOPS
  • ReadLatency and WriteLatency
  • FreeableMemory
  • DiskQueueDepth

Custom PostgreSQL Metrics:

While RDS provides many metrics, custom metrics can offer insights into specific application behaviors or database configurations. For instance, monitoring the number of active transactions, long-running queries, or cache hit ratios can be invaluable. You can achieve this by running a script on a bastion host or an EC2 instance that connects to your RDS instance and pushes custom metrics to CloudWatch using the AWS SDK.

import boto3
import psycopg2
import time
from datetime import datetime

# AWS Configuration
REGION_NAME = "us-east-1"
RDS_ENDPOINT = "your-rds-instance.xxxxxxxxxxxx.us-east-1.rds.amazonaws.com"
DB_USER = "your_db_user"
DB_PASSWORD = "your_db_password"
DB_NAME = "your_database_name"
NAMESPACE = "WordPress/RDS/Custom"

cloudwatch = boto3.client('cloudwatch', region_name=REGION_NAME)

def get_custom_metrics():
    metrics = []
    try:
        conn = psycopg2.connect(
            host=RDS_ENDPOINT,
            database=DB_NAME,
            user=DB_USER,
            password=DB_PASSWORD
        )
        cur = conn.cursor()

        # Example: Count active transactions
        cur.execute("SELECT count(*) FROM pg_stat_activity WHERE state = 'active';")
        active_transactions = cur.fetchone()[0]
        metrics.append({
            'MetricName': 'ActiveTransactions',
            'Value': active_transactions,
            'Unit': 'Count'
        })

        # Example: Count long-running queries (e.g., > 5 minutes)
        cur.execute("""
            SELECT count(*)
            FROM pg_stat_activity
            WHERE state = 'active' AND now() - query_start > interval '5 minutes';
        """)
        long_running_queries = cur.fetchone()[0]
        metrics.append({
            'MetricName': 'LongRunningQueries_5min',
            'Value': long_running_queries,
            'Unit': 'Count'
        })

        cur.close()
    except (Exception, psycopg2.DatabaseError) as error:
        print(f"Database error: {error}")
    finally:
        if conn is not None:
            conn.close()
    return metrics

def put_metrics(metrics_data):
    if not metrics_data:
        print("No metrics to put.")
        return

    try:
        response = cloudwatch.put_metric_data(
            Namespace=NAMESPACE,
            MetricData=metrics_data
        )
        print(f"Successfully put metrics: {response}")
    except Exception as e:
        print(f"Error putting metrics: {e}")

if __name__ == "__main__":
    while True:
        custom_metrics = get_custom_metrics()
        if custom_metrics:
            put_metrics(custom_metrics)
        time.sleep(60) # Push metrics every minute

This Python script connects to your PostgreSQL database, queries for specific metrics, and pushes them to CloudWatch under a custom namespace. Schedule this script to run periodically (e.g., via cron or a systemd timer).

Setting Up Actionable CloudWatch Alarms

Metrics are only useful if they trigger alerts when thresholds are breached. CloudWatch Alarms are critical for proactive intervention. We’ll set up alarms for key metrics across both EC2 and RDS.

EC2 Alarms (WordPress Instances):

  • High CPU Utilization: Trigger if CPUUtilization (standard EC2 metric) is above 85% for 5 minutes.
  • Low Disk Space: Trigger if disk_used_percent (from CloudWatch Agent) is above 90% for 10 minutes.
  • High Memory Usage: Trigger if mem_used_percent (from CloudWatch Agent) is above 90% for 10 minutes.
  • Apache/Nginx Error Log Spike: Monitor the count of specific error patterns (e.g., “PHP Fatal error”, “500 Internal Server Error”) in the error logs. This requires configuring CloudWatch Logs metric filters.

RDS Alarms (PostgreSQL Clusters):

  • High CPU Utilization: Trigger if CPUUtilization (RDS metric) is above 80% for 10 minutes.
  • Low Freeable Memory: Trigger if FreeableMemory is below 10% of total memory for 15 minutes.
  • High Disk Queue Depth: Trigger if DiskQueueDepth is above 5 for 5 minutes.
  • High Database Connections: Trigger if DatabaseConnections exceeds a predefined threshold (e.g., 80% of max_connections) for 5 minutes.
  • Replication Lag (if using read replicas): Monitor ReplicaLag metric. Trigger if lag exceeds 60 seconds for 2 minutes.
  • Custom Metric Alarms: Set alarms on your custom metrics, e.g., trigger if ActiveTransactions exceeds 200 for 5 minutes.

Configuring Log Metric Filters (Example for Apache Errors):

In CloudWatch Logs, navigate to your log group (e.g., “WordPress/App/Apache/Error”). Click “Create metric filter”.

Filter Pattern: "error" OR "crit" OR "alert" OR "emerg" OR "fatal" OR "500 Internal Server Error"
Metric Namespace: WordPress/App/Apache/Error
Metric Name: HttpdErrorCount
Default Value: 0

Once the metric filter is created, you can create a CloudWatch Alarm based on the “HttpdErrorCount” metric.

Application Performance Monitoring (APM) with AWS X-Ray

Beyond infrastructure and database metrics, understanding application performance bottlenecks is crucial. AWS X-Ray provides distributed tracing capabilities, allowing you to visualize requests as they travel through your application stack, from the user’s browser to your WordPress application, and down to the PostgreSQL database.

Setup Steps:

  • Install X-Ray Daemon: On your WordPress EC2 instances, install the AWS X-Ray daemon. This daemon listens for traces sent by instrumented applications and forwards them to the X-Ray service.
  • Instrument WordPress: Integrate an X-Ray SDK for PHP into your WordPress application. This involves adding a plugin or modifying your theme/plugin code to capture traces. Libraries like the official AWS SDK for PHP can be used, or dedicated APM plugins for WordPress.
  • Configure X-Ray SDK: Ensure the SDK is configured to send traces to the local X-Ray daemon.
  • Enable X-Ray for RDS (Optional but Recommended): For PostgreSQL, X-Ray can trace database calls if your application is instrumented to do so. The X-Ray SDK for PHP can often capture these automatically if configured correctly.

Example X-Ray SDK for PHP Integration (Conceptual):

You would typically use Composer to install the AWS SDK and its X-Ray component:

composer require aws/aws-sdk-php
composer require aws/aws-xray-sdk-php

Then, in your application’s bootstrap process (e.g., wp-config.php or a custom plugin’s main file), you’d initialize the X-Ray segment manager:

<?php
require 'vendor/autoload.php';

use Aws\XRay\XRayClient;
use Aws\XRay\Exception\XRayException;
use Aws\Credentials\CredentialProvider;
use Aws\Exception\AwsException;

// Configure X-Ray Client
$provider = CredentialProvider::defaultProvider();
$xrayClient = new XRayClient([
    'region'  => 'us-east-1', // Your AWS region
    'version' => 'latest',
    'credentials' => $provider
]);

// Initialize X-Ray Segment Manager
$segmentManager = new \Aws\XRay\SegmentManager($xrayClient);

// Start a segment for the incoming request
try {
    $segment = $segmentManager->beginSegment('WordPressRequest');
    $segment->putAnnotation('RequestURI', $_SERVER['REQUEST_URI']);
    $segment->putAnnotation('HTTPMethod', $_SERVER['REQUEST_METHOD']);

    // ... Your WordPress application logic here ...

    // Example: Tracing a database query
    $dbStartTime = microtime(true);
    // Execute your PostgreSQL query
    // $result = $pdo->query("SELECT * FROM wp_posts LIMIT 1");
    $dbEndTime = microtime(true);
    $dbDuration = ($dbEndTime - $dbStartTime) * 1000; // Duration in ms

    $subsegment = $segmentManager->beginSubSegment('DatabaseQuery');
    $subsegment->putAnnotation('QueryType', 'SELECT');
    $subsegment->putAnnotation('QueryTable', 'wp_posts');
    $subsegment->addDuration($dbDuration);
    $segmentManager->endSubSegment();

    // ... rest of your application logic ...

} catch (XRayException $e) {
    // Log X-Ray specific errors
    error_log("X-Ray Error: " . $e->getMessage());
} catch (AwsException $e) {
    // Log AWS SDK errors
    error_log("AWS SDK Error: " . $e->getMessage());
} catch (Exception $e) {
    // Log general application errors
    error_log("Application Error: " . $e->getMessage());
    // Record the exception in X-Ray
    if (isset($segment)) {
        $segmentManager->getCurrentSegment()->addException($e);
    }
} finally {
    // End the main segment for the request
    if (isset($segment)) {
        $segmentManager->endSegment();
    }
}
?>

With X-Ray, you can identify slow database queries, inefficient PHP code execution, or network latency issues impacting your WordPress site. Set up alarms based on X-Ray trace data, such as traces exceeding a certain duration or traces with errors.

Centralized Logging and Analysis with Elasticsearch/OpenSearch

While CloudWatch Logs is excellent for real-time monitoring and basic alerting, for deep log analysis, searching across large volumes of data, and complex pattern detection, a dedicated log aggregation and analysis platform is often necessary. AWS OpenSearch Service (or Elasticsearch) provides a powerful solution.

Architecture:

  • Log Shippers: Use Fluentd or Filebeat on your WordPress EC2 instances to collect logs (Apache, PHP-FPM, application logs) and forward them to OpenSearch.
  • AWS OpenSearch Domain: A managed OpenSearch cluster in AWS.
  • Kibana/OpenSearch Dashboards: For visualizing and querying your logs.

Configuration Example (Fluentd):

Install Fluentd and the necessary plugins (e.g., fluent-plugin-aws-elasticsearch-kibana or fluent-plugin-opensearch).

# Install Fluentd
sudo apt-get update
sudo apt-get install -y fluentd
sudo apt-get install -y fluentd-plugins-core

# Install OpenSearch plugin
sudo fluent-gem install fluent-plugin-opensearch

Configure Fluentd (e.g., in /etc/fluentd/fluent.conf) to tail log files and send them to your OpenSearch domain:

<source>
  @type tail
  path /var/log/httpd/access_log
  pos_file /var/log/fluentd/httpd-access.pos
  tag apache.access
  <parse>
    @type apache2
  </parse>
</source>

<source>
  @type tail
  path /var/log/httpd/error_log
  pos_file /var/log/fluentd/httpd-error.pos
  tag apache.error
  <parse>
    @type regexp
    expression /^(?<time>\d{2}\/\w{3}\/\d{4}:\d{2}:\d{2}:\d{2}\s[+\-]\d{4})\s+(?<level>\w+):\s+(?<message>.*)$/
  </parse>
</source>

<match apache.**>
  @type opensearch
  host YOUR_OPENSEARCH_DOMAIN_ENDPOINT.region.es.amazonaws.com
  port 443
  logstash_format true
  logstash_prefix wordpress-logs
  scheme https
  ssl_verify false # Set to true in production with proper certs
  include_tag_key true
  tag_key @log_name
  flush_interval 5s
  <buffer>
    @type file
    path /var/log/fluentd/buffer/apache
    flush_interval 5s
  </buffer>
</match>

This configuration tails Apache access and error logs, parses them, and sends them to your OpenSearch domain. You can then use OpenSearch Dashboards to create dashboards for visualizing traffic patterns, error rates, and performance metrics derived from your logs.

High Availability and Disaster Recovery Considerations

Monitoring is intrinsically linked to high availability (HA) and disaster recovery (DR). Ensure your monitoring setup itself is resilient. For RDS, leverage Multi-AZ deployments for automatic failover and consider cross-region read replicas for DR. For EC2 instances, use Auto Scaling Groups with health checks that integrate with CloudWatch Alarms. If an instance fails a health check, the ASG can terminate it and launch a replacement.

Auto Scaling Group Health Check Example:

# In your Auto Scaling Group configuration:
HealthCheckType: EC2 # Or ELB if using a Load Balancer
HealthCheckGracePeriod: 300 # Seconds

# CloudWatch Alarm to trigger scaling actions (e.g., scale-out on high CPU)
# CloudWatch Alarm to trigger instance termination (if an instance is unhealthy)
# This alarm would monitor a metric like CPUUtilization or a custom application health metric.
# If the alarm state changes to ALARM, it can trigger a "Terminate" policy for the ASG.

Regularly test your failover procedures and DR plans. Your monitoring system should alert you immediately when failover events occur and confirm that critical services are restored. This includes verifying that new instances or database replicas are reporting metrics and logs correctly.

Primary Sidebar

A little about the Author

Having 9+ Years of Experience in Software Development.
Expertised in Php Development, WordPress Custom Theme Development (From scratch using underscores or Genesis Framework or using any blank theme or Premium Theme), Custom Plugin Development. Hands on Experience on 3rd Party Php Extension like Chilkat, nSoftware.

Recent Posts

  • Step-by-Step: Diagnosing thread pools deadlock during concurrent ActiveRecord transaction processing on Linode Servers
  • Securing Your E-commerce APIs: Preventing SQL Injection (SQLi) in customized checkout queries in WooCommerce Implementations
  • Disaster Recovery 101: Architecting Auto-Failovers for MySQL and Ruby Deployments on Linode
  • High-Throughput Caching Strategies: Scaling MySQL for Perl Application APIs
  • Disaster Recovery 101: Architecting Auto-Failovers for DynamoDB and Laravel Deployments on DigitalOcean

Copyright © 2026 · Vinay Vengala