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:
The maintenance mode system consists of the following components:
Neuron\Cms\Maintenance\MaintenanceManager): Core maintenance mode orchestration and state managementNeuron\Cms\Maintenance\MaintenanceFilter): HTTP request interceptor that displays maintenance pageNeuron\Cms\Maintenance\MaintenanceConfig): Configuration handler for maintenance settings.maintenance.json file at application rootCreate 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
| 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 |
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
The CLI command provides an interactive way to enable maintenance mode:
./vendor/bin/neuron cms:maintenance:enable
This will:
config/neuron.yaml# 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 | 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 |
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
);
When enabled without parameters, maintenance mode:
127.0.0.1 and ::1 (localhost) onlyretry_after value./vendor/bin/neuron cms:maintenance:disable
This immediately disables maintenance mode and removes the .maintenance.json file.
# Skip confirmation
./vendor/bin/neuron cms:maintenance:disable --force
# Custom config directory
./vendor/bin/neuron cms:maintenance:disable --config=/path/to/config
use Neuron\Cms\Maintenance\MaintenanceManager;
$manager = new MaintenanceManager( '/var/www/app' );
$manager->disable( '[email protected]' );
./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
$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' );
When maintenance mode is enabled, each HTTP request's client IP is checked against the whitelist:
/, CIDR range matching is performedWhile 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'] );
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
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:
HTTP_X_FORWARDED_FOR (first IP if comma-separated)HTTP_X_REAL_IPHTTP_CLIENT_IPREMOTE_ADDRCreate 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.
Custom templates have access to:
| Variable | Type | Description |
|---|---|---|
$message |
string | Maintenance message |
$retryAfter |
int|null | Seconds until expected availability |
Custom view paths are validated to prevent directory traversal attacks:
.. componentsresources/ directoryRuntimeExceptionRegister 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' );
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
The maintenance system emits events for state changes.
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;
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
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
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
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
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
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"
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
Always include admin IPs in whitelist:
--allow-ip="127.0.0.1,203.0.113.5,198.51.100.0/24"
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");
}
}
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
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
Problem: Enabled maintenance mode without whitelisting your IP
Solutions:
Access via SSH and disable maintenance mode:
ssh user@server
cd /var/www/app
./vendor/bin/neuron cms:maintenance:disable --force
Delete maintenance file directly:
rm /var/www/app/.maintenance.json
Add your IP via SSH:
# Get your IP
curl ifconfig.me
# Edit maintenance file
nano .maintenance.json
# Add your IP to allowed_ips array
Problem: Site still accessible after enabling
Solutions:
Verify filter is registered:
// In application bootstrap
$router->addFilter( 'maintenance' );
Check .maintenance.json exists:
ls -la /var/www/app/.maintenance.json
cat /var/www/app/.maintenance.json
Verify file permissions:
# .maintenance.json should be readable by web server
chmod 644 .maintenance.json
Check for caching:
Problem: Default maintenance page shown instead of custom template
Solutions:
Verify path in configuration:
maintenance:
custom_view: resources/views/maintenance/custom.php
Check file exists:
ls -la resources/views/maintenance/custom.php
Verify file permissions:
chmod 644 resources/views/maintenance/custom.php
Check for PHP syntax errors:
php -l resources/views/maintenance/custom.php
Problem: Whitelisted IPs still seeing maintenance page
Solutions:
Verify client IP:
// Add to custom template temporarily
<?php echo $_SERVER['REMOTE_ADDR']; ?>
Check proxy headers:
// Debug script
print_r( $_SERVER );
// Look for X-Forwarded-For, X-Real-IP
Verify CIDR notation:
# Test CIDR range
# 192.168.1.100 should match 192.168.1.0/24
Review .maintenance.json:
cat .maintenance.json
# Verify allowed_ips array contains correct IPs
Problem: Search engines crawling during maintenance
Solutions:
Verify retry_after is set:
./vendor/bin/neuron cms:maintenance:status
# Check "Retry After" value
Check HTTP headers:
curl -I https://yoursite.com
# Look for: Retry-After: 3600
Set explicitly when enabling:
./vendor/bin/neuron cms:maintenance:enable --retry-after=3600
Problem: .maintenance.json exists after running disable command
Solutions:
Check command output:
./vendor/bin/neuron cms:maintenance:disable --force
# Should show "Maintenance mode disabled successfully"
Verify file permissions:
ls -la .maintenance.json
# Web server user should have write access
Delete manually if needed:
rm .maintenance.json
Check disk space:
df -h
# Ensure filesystem isn't full
Enable maintenance mode for the CMS.
Usage:
./vendor/bin/neuron cms:maintenance:enable [options]
Options:
--message, -m: Custom maintenance message--allow-ip, -a: Comma-separated list of allowed IPs--retry-after, -r: Retry-After header value in seconds--config, -c: Path to configuration directory--force, -f: Skip confirmation promptExamples:
# 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
Disable maintenance mode.
Usage:
./vendor/bin/neuron cms:maintenance:disable [options]
Options:
--config, -c: Path to configuration directory--force, -f: Skip confirmation promptExamples:
# Interactive disable
./vendor/bin/neuron cms:maintenance:disable
# Force disable (no confirmation)
./vendor/bin/neuron cms:maintenance:disable -f
Display current maintenance mode status.
Usage:
./vendor/bin/neuron cms:maintenance:status [options]
Options:
--config, -c: Path to configuration directoryExamples:
# Check status
./vendor/bin/neuron cms:maintenance:status
# Check with custom config path
./vendor/bin/neuron cms:maintenance:status -c /var/www/config