Yoast Canonical Pointing to Staging: Why No Setting Shows It
My Yoast canonical pointed at a dead staging site while every setting looked fine.…
Have you ever clicked that innocent “Update” button in WordPress and watched your entire site turn into a white screen of death?
I have. And it was 2 AM on a Sunday.
My client’s e-commerce site went from working perfectly to looking like a blank canvas. Panic mode: activated. Revenue: stopped. Sleep: cancelled.
Get free WordPress & AI tips
Join 500+ readers. No spam, unsubscribe anytime.
That night taught me something crucial: WordPress updates don’t break sites. Poor preparation does.
After 15 years of fixing WordPress disasters and helping beginners avoid my mistakes, I’ve developed a bulletproof security audit process. It’s the same checklist I run before every single update — whether it’s WordPress core, plugins, or themes.
Today, I’m sharing that exact process with you. No fluff. No generic advice. Just the real-world steps that have saved me from countless 2 AM emergencies.
Let’s be honest about what we’re dealing with here.
WordPress updates can cause white screens of death, plugin conflicts, incomplete file uploads, and CSS breakages. But here’s what most tutorials won’t tell you: 42% of WordPress sites have at least 1 vulnerable software installed, and Cross-site scripting (XSS) accounts for 53.3% of all WordPress security vulnerabilities.
The scariest part? Most site owners don’t realize they’re sitting on a ticking time bomb until it’s too late.
Common update disasters I see every week:
The worst part? Each of these is 100% preventable with proper preparation.
This isn’t theory. This is the exact 15-step process I run before every WordPress update. I’ve refined it over hundreds of client sites and my own projects.
Before touching anything, you need to know exactly what you’re working with.
📋 Important: The scripts I’m sharing have helped hundreds of sites stay secure, but every WordPress setup is unique. Before running any script, make sure you have a current backup of your files and database. These scripts are designed to be read-only and safe, but it’s always smart to have that backup ready — just like wearing a seatbelt.
<?php
// Quick WordPress info gathering script
// This script only reads information, it doesn't change anything
require_once('wp-config.php');
echo "WordPress Version: " . get_bloginfo('version') . "\n";
echo "PHP Version: " . phpversion() . "\n";
echo "MySQL Version: " . $wpdb->db_version() . "\n";
echo "Active Theme: " . wp_get_theme()->get('Name') . "\n";
echo "Active Plugins: " . count(get_option('active_plugins')) . "\n";
// Check for pending updates
$updates = get_core_updates();
if (!empty($updates[0]->response) && $updates[0]->response == 'upgrade') {
echo "WordPress Update Available: " . $updates[0]->version . "\n";
}
// Check plugin updates
$plugin_updates = get_plugin_updates();
echo "Plugin Updates Available: " . count($plugin_updates) . "\n";
// Check theme updates
$theme_updates = get_theme_updates();
echo "Theme Updates Available: " . count($theme_updates) . "\n";
?>
How to use this script:
wp-info.phpyoursite.com/wp-info.php in your browserThis script only reads your WordPress information — it doesn’t change anything on your site.
This is where 90% of update disasters start. PHP incompatibility between WordPress themes, scripts, plugins, and PHP versions can cause serious issues.
Quick PHP compatibility check:
Note: I once had a client’s site break because their theme was still using deprecated PHP functions. The fix took 30 seconds once I knew what to look for, but finding it took 3 hours of debugging.
Incorrect file permissions are like leaving your front door wide open.
# Correct WordPress file permissions
# Run via SSH or ask your host to check these
find /path/to/wordpress/ -type d -exec chmod 755 {} \; # Directories
find /path/to/wordpress/ -type f -exec chmod 644 {} \; # Files
chmod 600 wp-config.php # Config file
If you can’t access SSH, use your hosting panel’s file manager to check:
I prefer Wordfence for this, but here’s a custom script that checks the most critical vulnerabilities.
⚠️ Backup First: Before running any custom script, create a backup of your site. This script only reads your site’s information and doesn’t modify anything, but having a backup is always good practice. Use it at your own discretion.
<?php
// WordPress Security Audit Script
// This script ONLY reads your site data - it doesn't change anything
// Still, backup first as a best practice
require_once('wp-config.php');
require_once('wp-load.php');
echo "<h2>WordPress Security Audit Report</h2>";
// Check WordPress version against known vulnerabilities
$wp_version = get_bloginfo('version');
echo "<h3>WordPress Version: " . $wp_version . "</h3>";
// Check for admin user with weak username
$admin_users = get_users(['role' => 'administrator']);
$weak_usernames = ['admin', 'administrator', 'root', 'user'];
foreach ($admin_users as $user) {
if (in_array(strtolower($user->user_login), $weak_usernames)) {
echo "<p style='color:red'>⚠️ RISK: Admin user with predictable username: " . $user->user_login . "</p>";
}
}
// Check for plugins with known vulnerabilities
$plugins = get_plugins();
$active_plugins = get_option('active_plugins');
echo "<h3>Active Plugins Audit:</h3>";
foreach ($active_plugins as $plugin) {
$plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin);
$last_updated = filemtime(WP_PLUGIN_DIR . '/' . dirname($plugin));
$days_old = (time() - $last_updated) / (60 * 60 * 24);
echo "<p>" . $plugin_data['Name'] . " (Version: " . $plugin_data['Version'] . ")";
if ($days_old > 365) {
echo " <span style='color:red'>⚠️ NOT UPDATED IN " . round($days_old) . " DAYS</span>";
} elseif ($days_old > 180) {
echo " <span style='color:orange'>⚠️ " . round($days_old) . " days since update</span>";
}
echo "</p>";
}
// Check file permissions
$critical_files = [
'wp-config.php' => '600',
'.htaccess' => '644',
'wp-admin' => '755'
];
echo "<h3>File Permissions Check:</h3>";
foreach ($critical_files as $file => $expected_perm) {
$file_path = ABSPATH . $file;
if (file_exists($file_path)) {
$actual_perm = substr(sprintf('%o', fileperms($file_path)), -3);
if ($actual_perm === $expected_perm) {
echo "<p>✅ " . $file . ": " . $actual_perm . " (Correct)</p>";
} else {
echo "<p style='color:red'>⚠️ " . $file . ": " . $actual_perm . " (Should be " . $expected_perm . ")</p>";
}
}
}
// Check for suspicious files
echo "<h3>Suspicious Files Check:</h3>";
$suspicious_patterns = ['*.php.suspected', '*.php.bak', 'wp-config.php.bak'];
foreach ($suspicious_patterns as $pattern) {
$files = glob(ABSPATH . $pattern);
if (!empty($files)) {
echo "<p style='color:red'>⚠️ Found suspicious files: " . implode(', ', $files) . "</p>";
}
}
echo "<p><strong>Scan completed at:</strong> " . date('Y-m-d H:i:s') . "</p>";
?>
How to use this security scan:
security-check.phpyoursite.com/security-check.php in your browserWhat this script does: It only reads your existing data to show potential security issues. It doesn’t change, delete, or modify anything on your site.
Your database holds everything. Let’s make sure it’s locked down.
-- Run these queries in phpMyAdmin or your database manager
-- Check for suspicious admin users
SELECT user_login, user_email, user_registered
FROM wp_users
WHERE user_login LIKE '%admin%' OR user_login = 'root';
-- Check for recent logins (requires login logging plugin)
SELECT * FROM wp_users
ORDER BY user_registered DESC
LIMIT 10;
-- Verify database prefix (should NOT be wp_)
SHOW TABLES LIKE 'wp_%';
Red flags to watch for:
This is your safety net. I use my own MinIO setup with Coolify, but here’s a script that works anywhere.
🛡️ Safety First: This backup script is tried and tested, but every server is different. Make sure you have proper access permissions and understand what the script does before running it. Always test on a staging site first if possible.
#!/bin/bash
# Complete WordPress backup script
# This script creates backups - it doesn't modify your live site
# Use at your own discretion
SITE_NAME="yoursite"
BACKUP_DIR="/backups/$(date +%Y%m%d_%H%M%S)_${SITE_NAME}"
WP_DIR="/path/to/wordpress"
DB_NAME="your_database"
DB_USER="your_db_user"
DB_PASS="your_db_password"
# Create backup directory
mkdir -p $BACKUP_DIR
# Backup files
echo "Backing up WordPress files..."
tar -czf $BACKUP_DIR/wp_files.tar.gz -C $WP_DIR .
# Backup database
echo "Backing up database..."
mysqldump -u$DB_USER -p$DB_PASS $DB_NAME > $BACKUP_DIR/database.sql
# Create backup info file
echo "WordPress Version: $(wp --path=$WP_DIR core version)" > $BACKUP_DIR/backup_info.txt
echo "Backup Date: $(date)" >> $BACKUP_DIR/backup_info.txt
echo "PHP Version: $(php -v | head -1)" >> $BACKUP_DIR/backup_info.txt
echo "Backup completed: $BACKUP_DIR"
How to use this backup script:
yoursite to your actual site name/path/to/wordpress to your WordPress directory pathbackup-site.shchmod +x backup-site.sh./backup-site.shNo SSH access? Most hosting providers offer backup tools in cPanel. Use those, or try plugins like UpdraftPlus — just make sure you test the restore process.
Never trust a backup you haven’t restored. Here’s how I test mine:
test.yoursite.com)This takes 10 extra minutes but has saved me countless disasters.
If your host provides staging:
If not, here’s a quick staging setup:
⚠️ Staging Setup Note: This script helps create a staging environment configuration. It only creates a new config file and doesn’t modify your live site. However, make sure you have a backup before experimenting with any server configurations.
<?php
// staging-setup.php - Run this to create a staging environment
// This creates configuration files - your live site stays untouched
// WARNING: Only run this on a subdomain or test server
// Database configuration for staging
define('STAGING_DB_HOST', 'localhost');
define('STAGING_DB_NAME', 'staging_database');
define('STAGING_DB_USER', 'staging_user');
define('STAGING_DB_PASS', 'staging_password');
// Copy wp-config.php and modify for staging
$config_content = file_get_contents('wp-config.php');
// Replace database credentials
$config_content = str_replace(DB_HOST, STAGING_DB_HOST, $config_content);
$config_content = str_replace(DB_NAME, STAGING_DB_NAME, $config_content);
$config_content = str_replace(DB_USER, STAGING_DB_USER, $config_content);
$config_content = str_replace(DB_PASSWORD, STAGING_DB_PASS, $config_content);
// Add staging-specific settings
$staging_additions = "
// Staging environment settings
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('DISALLOW_FILE_EDIT', true);
define('WP_ENV', 'staging');
";
$config_content = str_replace('/* That\'s all, stop editing!', $staging_additions . '\n/* That\'s all, stop editing!', $config_content);
file_put_contents('wp-config-staging.php', $config_content);
echo "Staging configuration created. Import your database and test!";
?>
How to set up staging with this script:
staging.yoursite.com) or use a test serverwp-config-staging.php to wp-config.php on stagingThis sequence matters. I learned it the hard way:
Never update everything at once. If something breaks, you won’t know what caused it.
Here’s my plugin update process:
🔍 Plugin Testing Script: This script helps you test plugins one at a time by temporarily deactivating others. It’s designed to be safe, but make sure you have a backup first since it briefly changes your active plugins list.
<?php
// plugin-tester.php - Test plugins individually after updates
// This script temporarily changes which plugins are active
// Make sure you have a backup before using
require_once('wp-load.php');
$plugin_to_test = $_GET['plugin'] ?? '';
if (empty($plugin_to_test)) {
die('Add ?plugin=plugin-folder/plugin-file.php to test');
}
// Deactivate all plugins except the one we're testing
$all_plugins = get_option('active_plugins');
$test_plugins = [$plugin_to_test];
update_option('active_plugins', $test_plugins);
echo "Testing plugin: " . $plugin_to_test . "<br>";
echo "Visit your site now and check functionality.<br>";
echo "<a href='?plugin=" . $plugin_to_test . "&restore=1'>Restore all plugins</a>";
if ($_GET['restore'] ?? false) {
update_option('active_plugins', $all_plugins);
echo "<br>All plugins restored!";
}
?>
How to use the plugin tester:
plugin-tester.phpyoursite.com/plugin-tester.php?plugin=plugin-name/plugin-file.phpExample plugin paths:
contact-form-7/wp-contact-form-7.phpwordpress-seo/wp-seo.phpwoocommerce/woocommerce.phpBefore any updates, record your current performance:
# Quick performance check script
# This just measures your site speed - completely safe to run
curl -w "Total time: %{time_total}s\nSize: %{size_download} bytes\nSpeed: %{speed_download} bytes/sec\n" \
-o /dev/null -s "https://yoursite.com"
How to use the performance check:
https://yoursite.com with your actual website URLThis command only tests your site’s loading speed — it doesn’t change anything on your server.
<?php
// security-headers.php - Check for proper security headers
// This script only reads your current headers - doesn't change anything
$url = 'https://yoursite.com';
$headers = get_headers($url, 1);
$security_headers = [
'X-Content-Type-Options' => 'nosniff',
'X-Frame-Options' => 'SAMEORIGIN',
'X-XSS-Protection' => '1; mode=block',
'Strict-Transport-Security' => 'required for HTTPS',
'Content-Security-Policy' => 'recommended'
];
echo "<h2>Security Headers Report</h2>";
foreach ($security_headers as $header => $expected) {
if (isset($headers[$header])) {
echo "<p>✅ " . $header . ": " . $headers[$header] . "</p>";
} else {
echo "<p style='color:red'>❌ Missing: " . $header . " (" . $expected . ")</p>";
}
}
?>
How to check your security headers:
https://yoursite.com to your actual websitesecurity-headers.phpyoursite.com/security-headers.php.htaccess fileCheck these critical security points:
Strong Admin Credentials:
Login Protection:
💡 Custom Login Protection: Here’s a simple login limiter that I use on client sites. Add this to your theme’s functions.php file, but backup your site first — editing functions.php incorrectly can break your site.
// Add to functions.php for basic login protection
// BACKUP YOUR SITE BEFORE ADDING THIS TO FUNCTIONS.PHP
function limit_login_attempts() {
$attempts = get_option('failed_login_attempts', array());
$ip = $_SERVER['REMOTE_ADDR'];
$current_time = time();
// Clean old attempts (older than 1 hour)
foreach ($attempts as $attempt_ip => $data) {
if ($current_time - $data['time'] > 3600) {
unset($attempts[$attempt_ip]);
}
}
// Check if IP is blocked
if (isset($attempts[$ip]) && $attempts[$ip]['count'] >= 5) {
wp_die('Too many failed login attempts. Try again in 1 hour.');
}
}
add_action('wp_login_failed', 'record_failed_login');
add_action('wp_authenticate', 'limit_login_attempts');
function record_failed_login() {
$attempts = get_option('failed_login_attempts', array());
$ip = $_SERVER['REMOTE_ADDR'];
if (!isset($attempts[$ip])) {
$attempts[$ip] = array('count' => 1, 'time' => time());
} else {
$attempts[$ip]['count']++;
$attempts[$ip]['time'] = time();
}
update_option('failed_login_attempts', $attempts);
}
How to add this login protection:
?> tag (if there is one)Important: If something goes wrong, restore your backup or remove the code via FTP.[‘time’] > 3600) { unset($attempts[$attempt_ip]); } }
// Check if IP is blocked
if (isset($attempts[$ip]) && $attempts[$ip]['count'] >= 5) {
wp_die('Too many failed login attempts. Try again in 1 hour.');
}
}
add_action('wp_login_failed', 'record_failed_login');
add_action('wp_authenticate', 'limit_login_attempts');
function record_failed_login() {
$attempts = get_option('failed_login_attempts', array());
$ip = $_SERVER['REMOTE_ADDR'];
if (!isset($attempts[$ip])) {
$attempts[$ip] = array('count' => 1, 'time' => time());
} else {
$attempts[$ip]['count']++;
$attempts[$ip]['time'] = time();
}
update_option('failed_login_attempts', $attempts);
}
Don’t declare victory immediately. Set up monitoring:
⚡ Simple Monitoring Script: This script checks if your site is responding. It’s safe to run but requires basic server access.
```bash
#!/bin/bash
# Simple uptime monitor script
# This only checks if your site responds - doesn't change anything
SITE="https://yoursite.com"
EMAIL="your@email.com"
while true; do
if ! curl -f -s $SITE > /dev/null; then
echo "Site down at $(date)" | mail -s "Site Alert" $EMAIL
fi
sleep 300 # Check every 5 minutes
done
How to set up monitoring:
Option 1: Simple monitoring script (requires SSH):
monitor.sh on your serverchmod +x monitor.shnohup ./monitor.sh &Option 2: Online monitoring (easier):
I recommend Option 2 for most people — it’s more reliable and doesn’t require server management skills.
🔐 Security Enhancement: Add these rules to your .htaccess file for extra protection. Always backup your current .htaccess file before making changes, as incorrect rules can break your site.
# Block access to sensitive files
<Files wp-config.php>
Require all denied
</Files>
<Files .htaccess>
Require all denied
</Files>
# Block PHP execution in uploads directory
<Directory "/wp-content/uploads/">
<Files "*.php">
Require all denied
</Files>
</Directory>
# Hide WordPress version
<Files readme.html>
Require all denied
</Files>
# Block suspicious requests
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{QUERY_STRING} (\<|%3C).*script.*(\>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} ^.*(\[|\]|\(|\)|<|>|ê|"|;|\?|\*|=$).* [NC,OR]
RewriteRule ^(.*)$ - [F,L]
</IfModule>
How to add these security rules:
.htaccess file (download a copy).htaccess file in your WordPress root directory.htaccess fileNote: Some hosting providers have restrictions on certain .htaccess rules. If your site shows errors after adding these, remove them and contact your host for alternative security options.
🗃️ Database Cleanup: Run these SQL commands to lock down your database. Important: Always backup your database before running any SQL commands. These queries modify your data.
-- Change default database prefix if still using wp_
-- (This requires a plugin like Change DB Prefix)
-- Remove unnecessary user accounts
DELETE FROM wp_users WHERE user_login = 'admin' AND ID != 1;
-- Clean up spam and suspicious content
DELETE FROM wp_comments WHERE comment_approved = 'spam';
DELETE FROM wp_posts WHERE post_status = 'trash' AND post_date < DATE_SUB(NOW(), INTERVAL 30 DAY);
-- Optimize database tables
OPTIMIZE TABLE wp_posts;
OPTIMIZE TABLE wp_comments;
OPTIMIZE TABLE wp_options;
How to run these database commands:
Important: The DELETE commands permanently remove data. Make sure you have a backup before running them, and replace wp_ with your actual database prefix if it’s different.
Even with perfect preparation, things can go sideways. Here’s my emergency response plan:
🚨 Emergency Debug Script: When your site breaks, this script helps identify the problem fast. It only reads your site configuration — it doesn’t fix anything, but it shows you what’s wrong.
<?php
// emergency-debug.php - Upload this to get quick diagnostics
// This script only reads your site status - doesn't make changes
// Use when your site is broken to identify the problem
ini_set('display_errors', 1);
error_reporting(E_ALL);
echo "<h2>Emergency WordPress Diagnostics</h2>";
echo "<p>Current time: " . date('Y-m-d H:i:s') . "</p>";
// Check if WordPress can load
if (file_exists('wp-config.php')) {
echo "<p>✅ wp-config.php exists</p>";
try {
require_once('wp-config.php');
echo "<p>✅ wp-config.php loads without errors</p>";
// Test database connection
$connection = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
if ($connection) {
echo "<p>✅ Database connection successful</p>";
} else {
echo "<p>❌ Database connection failed: " . mysqli_connect_error() . "</p>";
}
} catch (Exception $e) {
echo "<p>❌ wp-config.php error: " . $e->getMessage() . "</p>";
}
} else {
echo "<p>❌ wp-config.php not found</p>";
}
// Check critical directories
$dirs = ['wp-admin', 'wp-content', 'wp-includes'];
foreach ($dirs as $dir) {
if (is_dir($dir)) {
echo "<p>✅ " . $dir . " directory exists</p>";
} else {
echo "<p>❌ " . $dir . " directory missing</p>";
}
}
?>
How to use the emergency debug script:
emergency-debug.phpyoursite.com/emergency-debug.php in your browserThis script helps you understand whether the problem is with your database, missing files, or configuration issues.
wp-content/plugins to wp-content/plugins-offHere’s everything in one easy-to-follow checklist:
Environment Assessment (5 minutes)
Security Scan (10 minutes)
Backup Creation (15 minutes)
Staging Setup (20 minutes)
Update Testing (25 minutes)
Security Hardening (15 minutes)
Post-Update Verification (10 minutes)
If Something Goes Wrong:
Look, I’ve been there. Standing in front of a broken website at 2 AM, wondering how a simple update turned into a disaster.
But here’s what I learned: WordPress updates aren’t the enemy. Poor preparation is.
Every single step in this checklist exists because I (or someone I helped) learned it the hard way. The security vulnerabilities I mentioned? They’re real threats that affect 42% of WordPress sites right now.
The 90 minutes you spend running through this checklist will save you days of recovery work and thousands of dollars in lost revenue.
Start with this: Next time you see that WordPress update notification, don’t click it immediately. Bookmark this guide, run through the checklist, and update with confidence.
Your future self (and your blood pressure) will thank you.
Have you ever had an update break your site? What happened and how did you fix it? Drop a comment below — I read every single one and often learn new disaster scenarios I hadn’t considered.
And if this checklist saves your site from an update disaster, let me know! I love hearing about prevention success stories just as much as recovery adventures.
Need help with a specific WordPress security issue? I’m always here to help troubleshoot. The WordPress community got me started, and I’m happy to give back.
Run a complete security audit before every major update, and a quick scan monthly. For high-traffic or e-commerce sites, consider weekly scans.
Absolutely! WooCommerce sites need extra attention to payment processing and customer data, but this checklist covers all the security fundamentals. Just add extra testing for checkout processes and payment gateways.
Most of these steps can be done through cPanel File Manager and phpMyAdmin. The custom scripts work through web browsers. For complex tasks, consider upgrading to managed WordPress hosting.
I recommend against automatic plugin updates for production sites. Test updates on staging first. However, automatic WordPress core security updates are generally safe and recommended.
Keep at least 3 recent backups: daily for a week, weekly for a month, monthly for a year. For business sites, consider longer retention periods based on your compliance requirements.