Rollin Logo
Building a Site Health Audit Dashboard in Drupal 10/11: Complete Guide for Proactive Site Monitoring

Building a Site Health Audit Dashboard in Drupal 10/11: Complete Guide for Proactive Site Monitoring

Alex Rollin
Alex Rollin
2025-06-20
When your Drupal site starts slowing down, throwing errors, or getting hacked, you usually find out too late. A site health dashboard changes that by giving you a bird's-eye view of potential problems before they break your site.

Here's how to build a custom health audit dashboard that monitors your Drupal site's security, performance, and configuration—all from one page.

Why Build Your Own Drupal Site Health Monitoring Dashboard?

Drupal sites fail in predictable ways. Cron stops running. Security updates pile up. Files get corrupted. Memory runs out. By the time users complain, you're already in crisis mode.

A health dashboard catches these issues early. Instead of reactive firefighting, you get proactive monitoring. Plus, you can customize it to check the specific things that matter for your site.

What We'll Build: Complete Site Health Monitoring Solution

Our dashboard will check:

  • Security update status
  • Cron job health
  • File system permissions
  • Database integrity
  • Performance bottlenecks
  • Configuration errors

The final result: a single admin page that shows red, yellow, or green status for each check, with details on how to fix problems.

Prerequisites for Building Drupal Health Dashboard

You'll need:

  • Drupal 10 or 11 running locally or on a server
  • Basic PHP knowledge
  • Access to create custom modules
  • Drush installed (optional but helpful)

Step 1: Create the Custom Module Structure

First, create your module directory:

modules/custom/site_health_dashboard/

Create the info file at site_health_dashboard.info.yml:

name: 'Site Health Dashboard'
type: module
description: 'Monitors site health with customizable checks'
core_version_requirement: ^10 || ^11
package: Custom
dependencies:
  - drupal:system
  - drupal:update

This tells Drupal about your module and ensures it works with both Drupal 10 and 11.

Step 2: Set Up Routing and Controller Configuration

Create site_health_dashboard.routing.yml:

site_health_dashboard.dashboard:
  path: '/admin/reports/site-health'
  defaults:
    _controller: '\Drupal\site_health_dashboard\Controller\DashboardController::dashboard'
    _title: 'Site Health Dashboard'
  requirements:
    _permission: 'administer site configuration'

Now create the controller at src/Controller/DashboardController.php:

dateFormatter = $date_formatter;
  }

  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('date.formatter')
    );
  }

  public function dashboard() {
    $checks = $this->runHealthChecks();
    
    return [
      '#theme' => 'site_health_dashboard',
      '#checks' => $checks,
      '#attached' => [
        'library' => ['site_health_dashboard/dashboard'],
      ],
    ];
  }

  private function runHealthChecks() {
    return [
      'cron' => $this->checkCronStatus(),
      'updates' => $this->checkSecurityUpdates(),
      'database' => $this->checkDatabaseHealth(),
      'files' => $this->checkFileSystem(),
    ];
  }

  private function checkCronStatus() {
    $last_run = \Drupal::state()->get('system.cron_last');
    
    if (!$last_run) {
      return [
        'status' => 'error',
        'message' => 'Cron has never run',
        'details' => 'Run cron manually or set up automated cron jobs.',
      ];
    }

    $hours_since = (time() - $last_run) / 3600;
    
    if ($hours_since > 24) {
      return [
        'status' => 'warning',
        'message' => 'Cron last ran ' . round($hours_since) . ' hours ago',
        'details' => 'Cron should run at least daily.',
      ];
    }

    return [
      'status' => 'ok',
      'message' => 'Cron ran ' . $this->dateFormatter->formatTimeDiffSince($last_run) . ' ago',
      'details' => 'Cron is running normally.',
    ];
  }

  private function checkSecurityUpdates() {
    $available = update_get_available(TRUE);
    $project_data = update_calculate_project_data($available);
    
    $security_updates = 0;
    foreach ($project_data as $project) {
      if (isset($project['security updates'])) {
        $security_updates  = count($project['security updates']);
      }
    }

    if ($security_updates > 0) {
      return [
        'status' => 'error',
        'message' => $security_updates . ' security updates available',
        'details' => 'Apply security updates immediately.',
      ];
    }

    return [
      'status' => 'ok',
      'message' => 'No security updates needed',
      'details' => 'All modules are up to date.',
    ];
  }

  private function checkDatabaseHealth() {
    try {
      $database = \Drupal::database();
      $result = $database->query("SELECT COUNT(*) FROM {users}")->fetchField();
      
      return [
        'status' => 'ok',
        'message' => 'Database connection working',
        'details' => 'Found ' . $result . ' user records.',
      ];
    }
    catch (\Exception $e) {
      return [
        'status' => 'error',
        'message' => 'Database connection failed',
        'details' => $e->getMessage(),
      ];
    }
  }

  private function checkFileSystem() {
    $public_path = \Drupal::service('file_system')->realpath('public://');
    $private_path = \Drupal::service('file_system')->realpath('private://');
    
    $issues = [];
    
    if (!is_writable($public_path)) {
      $issues[] = 'Public files directory not writable';
    }
    
    if ($private_path && !is_writable($private_path)) {
      $issues[] = 'Private files directory not writable';
    }

    if (!empty($issues)) {
      return [
        'status' => 'error',
        'message' => 'File system issues detected',
        'details' => implode(', ', $issues),
      ];
    }

    return [
      'status' => 'ok',
      'message' => 'File system healthy',
      'details' => 'All directories writable.',
    ];
  }
}

Step 3: Create the Dashboard Template

Create templates/site-health-dashboard.html.twig:

Site Health Status

{% for check_name, check in checks %}

{{ check_name|title }}

{{ check.status|upper }}

{{ check.message }}

{{ check.details }}

{% endfor %}

Step 4: Add Professional Dashboard Styling

Create site_health_dashboard.libraries.yml:

dashboard:
  css:
    theme:
      css/dashboard.css: {}

Create css/dashboard.css:

.site-health-dashboard {
  max-width: 1200px;
}

.health-checks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1rem;
  margin-top: 1rem;
}

.health-check {
  border: 2px solid #ddd;
  border-radius: 8px;
  padding: 1rem;
  background: #fff;
}

.health-check--ok {
  border-color: #28a745;
  background-color: #f8fff9;
}

.health-check--warning {
  border-color: #ffc107;
  background-color: #fffdf0;
}

.health-check--error {
  border-color: #dc3545;
  background-color: #fff8f8;
}

.health-check__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.5rem;
}

.health-check__status {
  font-weight: bold;
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
  font-size: 0.75rem;
}

.health-check--ok .health-check__status {
  background-color: #28a745;
  color: white;
}

.health-check--warning .health-check__status {
  background-color: #ffc107;
  color: black;
}

.health-check--error .health-check__status {
  background-color: #dc3545;
  color: white;
}

.health-check__message {
  font-weight: bold;
  margin-bottom: 0.5rem;
}

.health-check__details {
  color: #666;
  font-size: 0.9rem;
}

Step 5: Register the Theme Hook

Create or edit site_health_dashboard.module:

 [
      'variables' => ['checks' => NULL],
      'template' => 'site-health-dashboard',
    ],
  ];
}

Step 6: Enable and Test Your Drupal Health Dashboard

Enable your module:

drush en site_health_dashboard

Or through the admin interface at /admin/modules.

Visit /admin/reports/site-health to see your dashboard in action.

Making Your Site Health Dashboard More Powerful

The basic dashboard is just the start. Here are ways to extend it:

Add Advanced Health Monitoring Checks

Create additional health checks by adding methods to your controller:

private function checkMemoryUsage() {
  $memory_limit = ini_get('memory_limit');
  $memory_usage = memory_get_peak_usage(true);
  
  // Convert memory_limit to bytes for comparison
  $limit_bytes = $this->convertToBytes($memory_limit);
  $usage_percent = ($memory_usage / $limit_bytes) * 100;
  
  if ($usage_percent > 80) {
    return [
      'status' => 'warning',
      'message' => 'High memory usage: ' . round($usage_percent) . '%',
      'details' => 'Consider increasing memory_limit or optimizing code.',
    ];
  }
  
  return [
    'status' => 'ok',
    'message' => 'Memory usage: ' . round($usage_percent) . '%',
    'details' => 'Memory usage is within normal limits.',
  ];
}

Cache Results for Better Performance

For expensive checks, add caching:

private function checkSecurityUpdates() {
  $cache = \Drupal::cache()->get('site_health_dashboard.security_updates');
  
  if ($cache && (time() - $cache->created) < 3600) {
    return $cache->data;
  }
  
  // Run the actual check...
  $result = [/* your check results */];
  
  \Drupal::cache()->set('site_health_dashboard.security_updates', $result, time()   3600);
  
  return $result;
}

Add Administrative Configuration Options

Let admins choose which checks to run by creating a config form.

Common Problems and Troubleshooting Solutions

Dashboard shows "Access denied"
Check that your user has the "Administer site configuration" permission.

Checks always show as "OK" even when there are problems
Make sure you're testing the actual conditions. Add debugging with \Drupal::logger('site_health_dashboard')->info() to see what's happening.

Styling looks wrong
Clear your cache after adding CSS changes: drush cr

Module won't enable
Check for syntax errors in your PHP files. Run php -l filename.php to check.

Performance Optimization for Site Health Monitoring

Health checks can slow down your dashboard if they're expensive. Here are ways to keep it fast:

  • Cache results for checks that don't change often
  • Run heavy checks via cron and store results
  • Use AJAX to load individual checks after the page loads
  • Limit check frequency - some things only need checking daily

Next Steps for Advanced Site Health Monitoring

Your health dashboard is now running and catching basic issues. To make it production-ready:

  • Add more specific checks for your site's critical functions
  • Set up email alerts for critical issues
  • Create a mobile-friendly version for monitoring on the go
  • Integrate with external monitoring services
  • Add historical tracking to spot trends

Building this dashboard gives you the foundation for proactive site management. Instead of waiting for problems to find you, you'll catch them early and keep your Drupal site running smoothly.

The code examples here cover the essential structure. Customize the health checks for your specific needs, and you'll have a powerful tool for keeping your site healthy.

Share this article

Ready to start
your project?

Our development team is ready to transform your vision into reality and bring your next innovation to life.