Maintenance Mode Guide

Overview

The Neuron CMS provides a robust maintenance mode system that allows you to temporarily take your site offline for updates, deployments, or scheduled maintenance while displaying a user-friendly maintenance page. The system includes IP whitelisting, allowing administrators and authorized users to access the site during maintenance.

Key features:

Architecture

Components

The maintenance mode system consists of the following components:

How It Works

  1. State Storage: Maintenance state is stored in .maintenance.json file at application root
  2. Request Interception: MaintenanceFilter checks every incoming HTTP request
  3. IP Validation: Client IP is checked against whitelist (exact match or CIDR range)
  4. Response: Blocked requests receive 503 status with maintenance page
  5. Bypass: Whitelisted IPs access the site normally

Configuration

Default Configuration

Create or update config/neuron.yaml:

maintenance:
  # Default message shown to users
  default_message: "Site is currently under maintenance. We'll be back soon!"

  # IP addresses allowed to access during maintenance
  allowed_ips:
    - 127.0.0.1        # Localhost IPv4
    - ::1              # Localhost IPv6

  # Retry-After header value in seconds
  retry_after: 3600    # 1 hour

  # Custom maintenance view path (optional)
  custom_view: null

  # Show countdown timer
  show_countdown: false

Configuration Options

Option Type Default Description
default_message string "Site is currently under maintenance..." Default message shown to users
allowed_ips array ['127.0.0.1', '::1'] IP addresses that bypass maintenance mode
retry_after int 3600 Seconds until site is expected to be available (HTTP Retry-After header)
custom_view string|null null Path to custom maintenance page template
show_countdown bool false Show countdown timer on maintenance page

IP Whitelist Configuration

The allowed_ips configuration supports:

Individual IP addresses:

allowed_ips:
  - 203.0.113.5
  - 198.51.100.42

CIDR notation for IP ranges:

allowed_ips:
  - 192.168.1.0/24        # Entire local network
  - 10.0.0.0/8            # All 10.x.x.x addresses
  - 172.16.0.0/12         # Private network range

IPv6 addresses:

allowed_ips:
  - ::1                              # IPv6 localhost
  - 2001:db8::/32                    # IPv6 CIDR range
  - fe80::1ff:fe23:4567:890a         # Specific IPv6 address

Enabling Maintenance Mode

Using CLI (Recommended)

The CLI command provides an interactive way to enable maintenance mode:

./vendor/bin/neuron cms:maintenance:enable

This will:

  1. Load configuration from config/neuron.yaml
  2. Display the maintenance message and allowed IPs
  3. Prompt for confirmation
  4. Enable maintenance mode
  5. Display success message with disable instructions

CLI Options

# Custom message
./vendor/bin/neuron cms:maintenance:enable --message="Upgrading to v2.0. Back in 30 minutes."

# Custom allowed IPs
./vendor/bin/neuron cms:maintenance:enable --allow-ip="203.0.113.5,192.168.1.0/24"

# No allowed IPs (lock out everyone)
./vendor/bin/neuron cms:maintenance:enable --allow-ip=""

# Custom retry-after (2 hours)
./vendor/bin/neuron cms:maintenance:enable --retry-after=7200

# Skip confirmation prompt
./vendor/bin/neuron cms:maintenance:enable --force

# Custom config directory
./vendor/bin/neuron cms:maintenance:enable --config=/path/to/config

# Combined options
./vendor/bin/neuron cms:maintenance:enable \
  --message="Database migration in progress" \
  --allow-ip="203.0.113.5" \
  --retry-after=1800 \
  --force

Option Details

Option Short Required Description
--message -m No Custom maintenance message
--allow-ip -a No Comma-separated list of allowed IPs
--retry-after -r No Estimated downtime in seconds
--config -c No Path to configuration directory
--force -f No Skip confirmation prompt

Programmatic Activation

Enable maintenance mode from PHP code:

use Neuron\Cms\Maintenance\MaintenanceManager;

$basePath = '/var/www/app';
$manager = new MaintenanceManager( $basePath );

$manager->enable( 'Performing scheduled maintenance. Back in 1 hour.',
    ['203.0.113.5', '192.168.1.0/24'],  // Allowed IPs
    3600,                                // Retry-after (seconds )
    '[email protected]'                  // Enabled by
);

Default Behavior

When enabled without parameters, maintenance mode:

Disabling Maintenance Mode

Using CLI (Recommended)

./vendor/bin/neuron cms:maintenance:disable

This immediately disables maintenance mode and removes the .maintenance.json file.

CLI Options

# Skip confirmation
./vendor/bin/neuron cms:maintenance:disable --force

# Custom config directory
./vendor/bin/neuron cms:maintenance:disable --config=/path/to/config

Programmatic Deactivation

use Neuron\Cms\Maintenance\MaintenanceManager;

$manager = new MaintenanceManager( '/var/www/app' );
$manager->disable( '[email protected]' );

Checking Maintenance Status

Using CLI

./vendor/bin/neuron cms:maintenance:status

Output when enabled:

Maintenance mode: ENABLED

Message: Site is currently under maintenance
Allowed IPs: 127.0.0.1, ::1, 203.0.113.5
Retry After: 3600 seconds (1h)
Enabled At: 2025-11-27T14:30:00-05:00
Enabled By: admin

Output when disabled:

Maintenance mode: DISABLED

Programmatic Status Check

$manager = new MaintenanceManager( '/var/www/app' );

// Check if enabled
if ($manager->isEnabled()) {
    echo "Maintenance mode is active\n";
}

// Get full status
$status = $manager->getStatus();
/*
Array:
  'enabled' => true
  'message' => 'Site is under maintenance'
  'allowed_ips' => ['127.0.0.1', '::1']
  'retry_after' => 3600
  'enabled_at' => '2025-11-27T14:30:00-05:00'
  'enabled_by' => 'admin'
*/

// Get specific values
$message = $manager->getMessage();
$retryAfter = $manager->getRetryAfter();

// Check if specific IP is allowed
$isAllowed = $manager->isIpAllowed( '203.0.113.5' );

IP Whitelisting

How IP Whitelisting Works

When maintenance mode is enabled, each HTTP request's client IP is checked against the whitelist:

  1. Extract client IP: MaintenanceFilter determines client IP from request headers
  2. Check whitelist: IP is compared against configured allowed IPs
  3. CIDR matching: If entry contains /, CIDR range matching is performed
  4. Access decision: Matching IPs bypass maintenance mode; others see maintenance page

Adding IPs Dynamically

While maintenance mode is active, you can modify allowed IPs:

$manager = new MaintenanceManager( '/var/www/app' );

// Get current status
$status = $manager->getStatus();

// Add new IP to existing whitelist
$currentIps = $status['allowed_ips'];
$currentIps[] = '203.0.113.10';

// Re-enable with updated whitelist
$manager->enable( $status['message'],
    $currentIps,
    $status['retry_after'] );

CIDR Notation Support

The system fully supports CIDR notation for IP ranges:

Common CIDR ranges:

CIDR Range Description
192.168.1.0/24 192.168.1.0 - 192.168.1.255 256 addresses
192.168.0.0/16 192.168.0.0 - 192.168.255.255 65,536 addresses
10.0.0.0/8 10.0.0.0 - 10.255.255.255 16,777,216 addresses
172.16.0.0/12 172.16.0.0 - 172.31.255.255 1,048,576 addresses

Example: Allow entire office network:

allowed_ips:
  - 203.0.113.0/24    # Office network
  - 198.51.100.42     # VPN server

Proxy and Load Balancer Considerations

If your application is behind a proxy or load balancer, ensure it forwards the real client IP:

Nginx configuration:

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Apache configuration:

RequestHeader set X-Forwarded-For expr=%{REMOTE_ADDR}
RequestHeader set X-Real-IP expr=%{REMOTE_ADDR}

MaintenanceFilter checks headers in this order:

  1. HTTP_X_FORWARDED_FOR (first IP if comma-separated)
  2. HTTP_X_REAL_IP
  3. HTTP_CLIENT_IP
  4. REMOTE_ADDR

Custom Maintenance Page

Using Custom Template

Create a custom maintenance page template:

1. Create template at resources/views/maintenance/custom.php:

<?php
/**
 * @var string $message Maintenance message
 * @var int|null $retryAfter Retry-after value in seconds
 */
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Under Maintenance</title>
    <link rel="stylesheet" href="/css/maintenance.css">
</head>
<body>
    <div class="maintenance-container">
        <img src="/images/logo.png" alt="Logo" class="logo">
        <h1>We'll Be Right Back!</h1>
        <p><?= htmlspecialchars( $message ) ?></p>

        <?php if ($retryAfter): ?>
        <div class="countdown" data-retry-after="<?= $retryAfter ?>">
            <div id="countdown-timer"></div>
        </div>
        <?php endif; ?>

        <div class="contact">
            <p>Need immediate assistance?</p>
            <a href="mailto:[email protected]">Contact Support</a>
        </div>
    </div>

    <script src="/js/maintenance.js"></script>
</body>
</html>

2. Configure custom view in config/neuron.yaml:

maintenance:
  custom_view: resources/views/maintenance/custom.php

3. Enable maintenance mode:

./vendor/bin/neuron cms:maintenance:enable

The custom template will automatically be used.

Dynamic Content in Templates

Custom templates have access to:

Variable Type Description
$message string Maintenance message
$retryAfter int|null Seconds until expected availability

Template Security

Custom view paths are validated to prevent directory traversal attacks:

Integration with Application

Setting Up Maintenance Filter

Register the maintenance filter in your application bootstrap:

use Neuron\Cms\Maintenance\MaintenanceManager;
use Neuron\Cms\Maintenance\MaintenanceFilter;
use Neuron\Cms\Maintenance\MaintenanceConfig;
use Neuron\Patterns\Registry;

// Load configuration
$settingManager = Registry::getInstance()->get( 'SettingManager' );
$config = MaintenanceConfig::fromSettings( $settingManager->getSource());

// Create manager
$basePath = Registry::getInstance()->get( 'BasePath' );
$manager = new MaintenanceManager( $basePath );

// Create and register filter
$filter = new MaintenanceFilter( $manager,
    $config->getCustomView()
);

$router = Registry::getInstance()->get( 'Router' );
$router->registerFilter( 'maintenance', $filter );

// Apply to all routes
$router->addFilter( 'maintenance' );

Using Application Initializer

For automatic setup, use the provided initializer:

Create app/Initializers/MaintenanceInitializer.php:

<?php

namespace App\Initializers;

use Neuron\Application\Initializers\IInitializer;
use Neuron\Cms\Maintenance\MaintenanceConfig;
use Neuron\Cms\Maintenance\MaintenanceFilter;
use Neuron\Cms\Maintenance\MaintenanceManager;
use Neuron\Patterns\Registry;

class MaintenanceInitializer implements IInitializer
{
    public function init(): bool
    {
        $settingManager = Registry::getInstance()->get( 'SettingManager' );
        $config = MaintenanceConfig::fromSettings( $settingManager->getSource());

        $basePath = Registry::getInstance()->get( 'BasePath' );
        $manager = new MaintenanceManager( $basePath );

        $filter = new MaintenanceFilter( $manager,
            $config->getCustomView() );

        $router = Registry::getInstance()->get( 'Router' );
        $router->registerFilter( 'maintenance', $filter );
        $router->addFilter( 'maintenance' );

        return true;
    }
}

Register initializer in config/initializers.yaml:

initializers:
  - App\Initializers\MaintenanceInitializer

Event Integration

The maintenance system emits events for state changes.

Available Events

MaintenanceModeEnabledEvent (Neuron\Cms\Events\MaintenanceModeEnabledEvent):

public function __construct( string $enabledBy,
    string $message );

public function getEnabledBy(): string;
public function getMessage(): string;

MaintenanceModeDisabledEvent (Neuron\Cms\Events\MaintenanceModeDisabledEvent):

public function __construct( string $disabledBy );

public function getDisabledBy(): string;

Listening to Events

Create event listeners to react to maintenance state changes:

<?php

namespace App\Listeners;

use Neuron\Events\IListener;
use Neuron\Events\IEvent;
use Neuron\Cms\Events\MaintenanceModeEnabledEvent;
use Neuron\Log\Log;

class NotifyAdminsMaintenanceEnabled implements IListener
{
    public function handle( IEvent $event ): void
    {
        if( !$event instanceof MaintenanceModeEnabledEvent )
{
            return;
        }

        Log::info( "Maintenance mode enabled by: {$event->getEnabledBy()}");

        // Queue notification emails
        dispatch( new SendMaintenanceNotificationJob(), [
            'enabled_by' => $event->getEnabledBy(),
            'message' => $event->getMessage(),
            'recipients' => ['[email protected]', '[email protected]']
        ], 'emails');
    }
}

Register listener in config/events.yaml:

events:
  maintenance.enabled:
    - App\Listeners\NotifyAdminsMaintenanceEnabled
    - App\Listeners\LogMaintenanceChange

  maintenance.disabled:
    - App\Listeners\NotifyAdminsMaintenanceDisabled
    - App\Listeners\ClearMaintenanceCache

Deployment Workflows

Zero-Downtime Deployment

For deployments requiring database migrations or significant changes:

1. Enable maintenance mode:

./vendor/bin/neuron cms:maintenance:enable \
  --message="Deploying new features. Back in 5 minutes." \
  --retry-after=300 \
  --force

2. Perform deployment:

# Pull latest code
git pull origin main

# Install dependencies
composer install --no-dev --optimize-autoloader

# Run migrations
./vendor/bin/neuron db:migrate

# Clear caches
php artisan cache:clear

3. Disable maintenance mode:

./vendor/bin/neuron cms:maintenance:disable --force

Automated Deployment Script

Create deploy.sh:

#!/bin/bash

set -e

echo "Enabling maintenance mode..."
./vendor/bin/neuron cms:maintenance:enable \
  --message="Deploying update. Back shortly." \
  --retry-after=600 \
  --force

echo "Pulling latest code..."
git pull origin main

echo "Installing dependencies..."
composer install --no-dev --optimize-autoloader

echo "Running migrations..."
./vendor/bin/neuron db:migrate --force

echo "Clearing caches..."
rm -rf var/cache/*

echo "Disabling maintenance mode..."
./vendor/bin/neuron cms:maintenance:disable --force

echo "Deployment complete!"

Make executable and run:

chmod +x deploy.sh
./deploy.sh

Rollback on Failure

Add error handling to automatically disable maintenance mode on failure:

#!/bin/bash

set -e

# Trap errors and disable maintenance mode
trap 'echo "Deployment failed! Disabling maintenance mode..."; ./vendor/bin/neuron cms:maintenance:disable --force; exit 1' ERR

./vendor/bin/neuron cms:maintenance:enable --force

# Deployment steps...
git pull origin main
composer install --no-dev
./vendor/bin/neuron db:migrate

./vendor/bin/neuron cms:maintenance:disable --force

Best Practices

1. Test Maintenance Mode

Test maintenance mode before deployments:

# Enable maintenance mode
./vendor/bin/neuron cms:maintenance:enable --force

# Verify in browser (from non-whitelisted IP)
curl -I https://yoursite.com

# Check whitelisted access works
curl -I https://yoursite.com -H "X-Forwarded-For: 127.0.0.1"

# Disable maintenance mode
./vendor/bin/neuron cms:maintenance:disable --force

2. Use Descriptive Messages

Provide clear, helpful messages to users:

# Good
--message="Upgrading database. Back at 3:00 PM EST."

# Better
--message="Performing scheduled maintenance. Expected completion: 3:00 PM EST. Contact [email protected] for urgent issues."

# Avoid
--message="Down for maintenance"

3. Set Realistic Retry-After Times

Match retry_after to actual expected downtime:

# 5 minute deployment
--retry-after=300

# 1 hour maintenance window
--retry-after=3600

# 4 hour migration
--retry-after=14400

4. Whitelist Administrative IPs

Always include admin IPs in whitelist:

--allow-ip="127.0.0.1,203.0.113.5,198.51.100.0/24"

5. Monitor Maintenance State

Set up monitoring to alert if maintenance mode is accidentally left enabled:

// Monitoring script
$manager = new MaintenanceManager( '/var/www/app' );

if ($manager->isEnabled()) {
    $status = $manager->getStatus();
    $enabledAt = new DateTime( $status['enabled_at'] );
    $duration = time() - $enabledAt->getTimestamp();

    // Alert if enabled for more than 2 hours
    if( $duration > 7200 )
{
        // Send alert
        sendAlert( "Maintenance mode enabled for " . ($duration / 3600 ) . " hours");
    }
}

6. Document IP Whitelist

Maintain documentation of which IPs should be whitelisted:

# config/maintenance-ips.md
# Maintenance Mode Whitelist
#
# Office Network: 203.0.113.0/24
# VPN Server: 198.51.100.42
# Admin Home: 203.0.113.100
# CI/CD Server: 203.0.113.200

7. Use Force Flag in Automation

Always use --force in automated scripts to prevent hanging on confirmation prompts:

./vendor/bin/neuron cms:maintenance:enable --force
./vendor/bin/neuron cms:maintenance:disable --force

Troubleshooting

Cannot Access Site (Locked Out)

Problem: Enabled maintenance mode without whitelisting your IP

Solutions:

  1. Access via SSH and disable maintenance mode:

    ssh user@server
    cd /var/www/app
    ./vendor/bin/neuron cms:maintenance:disable --force
    
  2. Delete maintenance file directly:

    rm /var/www/app/.maintenance.json
    
  3. Add your IP via SSH:

    # Get your IP
    curl ifconfig.me
    
    # Edit maintenance file
    nano .maintenance.json
    # Add your IP to allowed_ips array
    

Maintenance Mode Not Activating

Problem: Site still accessible after enabling

Solutions:

  1. Verify filter is registered:

    // In application bootstrap
    $router->addFilter( 'maintenance' );
    
  2. Check .maintenance.json exists:

    ls -la /var/www/app/.maintenance.json
    cat /var/www/app/.maintenance.json
    
  3. Verify file permissions:

    # .maintenance.json should be readable by web server
    chmod 644 .maintenance.json
    
  4. Check for caching:

    • Clear browser cache
    • Clear CDN cache
    • Clear application cache

Custom Template Not Loading

Problem: Default maintenance page shown instead of custom template

Solutions:

  1. Verify path in configuration:

    maintenance:
      custom_view: resources/views/maintenance/custom.php
    
  2. Check file exists:

    ls -la resources/views/maintenance/custom.php
    
  3. Verify file permissions:

    chmod 644 resources/views/maintenance/custom.php
    
  4. Check for PHP syntax errors:

    php -l resources/views/maintenance/custom.php
    

IP Whitelist Not Working

Problem: Whitelisted IPs still seeing maintenance page

Solutions:

  1. Verify client IP:

    // Add to custom template temporarily
    <?php echo $_SERVER['REMOTE_ADDR']; ?>
    
  2. Check proxy headers:

    // Debug script
    print_r( $_SERVER );
    // Look for X-Forwarded-For, X-Real-IP
    
  3. Verify CIDR notation:

    # Test CIDR range
    # 192.168.1.100 should match 192.168.1.0/24
    
  4. Review .maintenance.json:

    cat .maintenance.json
    # Verify allowed_ips array contains correct IPs
    

Retry-After Header Not Set

Problem: Search engines crawling during maintenance

Solutions:

  1. Verify retry_after is set:

    ./vendor/bin/neuron cms:maintenance:status
    # Check "Retry After" value
    
  2. Check HTTP headers:

    curl -I https://yoursite.com
    # Look for: Retry-After: 3600
    
  3. Set explicitly when enabling:

    ./vendor/bin/neuron cms:maintenance:enable --retry-after=3600
    

Maintenance State Persists After Disable

Problem: .maintenance.json exists after running disable command

Solutions:

  1. Check command output:

    ./vendor/bin/neuron cms:maintenance:disable --force
    # Should show "Maintenance mode disabled successfully"
    
  2. Verify file permissions:

    ls -la .maintenance.json
    # Web server user should have write access
    
  3. Delete manually if needed:

    rm .maintenance.json
    
  4. Check disk space:

    df -h
    # Ensure filesystem isn't full
    

CLI Commands Reference

cms:maintenance:enable

Enable maintenance mode for the CMS.

Usage:

./vendor/bin/neuron cms:maintenance:enable [options]

Options:

Examples:

# Interactive enable with defaults
./vendor/bin/neuron cms:maintenance:enable

# With custom message
./vendor/bin/neuron cms:maintenance:enable -m "Deploying v2.0"

# With specific IPs
./vendor/bin/neuron cms:maintenance:enable -a "203.0.113.5,192.168.1.0/24"

# Complete example
./vendor/bin/neuron cms:maintenance:enable \
  -m "Database migration in progress" \
  -a "203.0.113.5" \
  -r 1800 \
  -f

cms:maintenance:disable

Disable maintenance mode.

Usage:

./vendor/bin/neuron cms:maintenance:disable [options]

Options:

Examples:

# Interactive disable
./vendor/bin/neuron cms:maintenance:disable

# Force disable (no confirmation)
./vendor/bin/neuron cms:maintenance:disable -f

cms:maintenance:status

Display current maintenance mode status.

Usage:

./vendor/bin/neuron cms:maintenance:status [options]

Options:

Examples:

# Check status
./vendor/bin/neuron cms:maintenance:status

# Check with custom config path
./vendor/bin/neuron cms:maintenance:status -c /var/www/config

Additional Resources