Use when administering WordPress sites — wp-config.php configuration, security hardening, performance optimization (object cache, page cache, CDN), user roles and capabilities, database maintenance and cleanup, backup and migration (WP-CLI search-replace), debugging (WP_DEBUG, error logs, white screen of death), multisite network setup, cron system configuration, auto-updates, or troubleshooting WordPress errors.
WordPress site administration covering wp-config.php, security hardening, performance tuning, user management, database maintenance, backups, migration, debugging, multisite, cron, and updates. For theme/plugin/block development, see wordpress-developer. For WooCommerce store code, see woocommerce-developer.
define('DB_NAME', 'database_name');
define('DB_USER', 'database_user');
define('DB_PASSWORD', 'database_password');
define('DB_HOST', 'localhost'); // Can be IP, socket, or host:port
define('DB_CHARSET', 'utf8mb4');
$table_prefix = 'wp_'; // Change for security (e.g., 'xk9f_')
define('WP_SITEURL', 'https://example.com'); // Overrides DB value
define('WP_HOME', 'https://example.com'); // Overrides DB value
// Production:
define('WP_DEBUG', false);
// Development/Staging:
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true); // Logs to wp-content/debug.log
define('WP_DEBUG_DISPLAY', false); // Don't show errors on screen
define('SCRIPT_DEBUG', true); // Use unminified core JS/CSS
define('SAVEQUERIES', true); // Log queries to $wpdb->queries (NEVER on production)
define('WP_MEMORY_LIMIT', '256M'); // Frontend (default: 40M)
define('WP_MAX_MEMORY_LIMIT', '512M'); // Admin/backend (default: 256M)
Cannot exceed server's php.ini memory_limit.
define('FS_METHOD', 'direct'); // Skip FTP prompt
define('DISALLOW_FILE_EDIT', true); // Disable theme/plugin editor in admin
define('DISALLOW_FILE_MODS', true); // Disable ALL file mods (editor + install/update)
// Generate at: https://api.wordpress.org/secret-key/1.1/salt/
// Or: wp config shuffle-salts
// Changing salts forces all users to re-login
define('AUTH_KEY', 'unique-phrase');
define('SECURE_AUTH_KEY', 'unique-phrase');
define('LOGGED_IN_KEY', 'unique-phrase');
define('NONCE_KEY', 'unique-phrase');
define('AUTH_SALT', 'unique-phrase');
define('SECURE_AUTH_SALT', 'unique-phrase');
define('LOGGED_IN_SALT', 'unique-phrase');
define('NONCE_SALT', 'unique-phrase');
define('AUTOMATIC_UPDATER_DISABLED', true); // Kill ALL auto-updates
define('WP_AUTO_UPDATE_CORE', 'minor'); // true (all), false (none), 'minor' (default)
define('DISABLE_WP_CRON', true); // Disable pseudo-cron (use server cron instead)
define('ALTERNATE_WP_CRON', true); // Alternative cron method for problematic hosts
Server cron replacement:
*/5 * * * * cd /path/to/wordpress && wp cron event run --due-now > /dev/null 2>&1
define('FORCE_SSL_ADMIN', true);
define('WP_POST_REVISIONS', 5); // Limit revisions (false = disable, true = unlimited)
define('AUTOSAVE_INTERVAL', 300); // Seconds (default: 60)
define('EMPTY_TRASH_DAYS', 15); // Default: 30. 0 = disable trash
define('WP_CACHE', true); // Required by most caching plugins
// Redis object cache:
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('WP_REDIS_PREFIX', 'wp_site1_');
define('WP_ALLOW_MULTISITE', true); // Step 1: enables Network Setup screen
// After setup, add:
define('MULTISITE', true);
define('SUBDOMAIN_INSTALL', false); // false = subdirectory, true = subdomain
define('DOMAIN_CURRENT_SITE', 'example.com');
define('PATH_CURRENT_SITE', '/');
| Path | Permission |
|---|---|
| Directories | 755 |
| Files | 644 |
| wp-config.php | 440 or 400 |
find /path/to/wordpress -type d -exec chmod 755 {} \;
find /path/to/wordpress -type f -exec chmod 644 {} \;
chmod 400 /path/to/wordpress/wp-config.php
# Protect wp-config.php
<Files wp-config.php>
Order Allow,Deny
Deny from all
</Files>
# Disable directory listing
Options -Indexes
# Block PHP execution in uploads
<Directory /path/to/wp-content/uploads>
<Files "*.php">
Order Allow,Deny
Deny from all
</Files>
</Directory>
# Block sensitive files
<FilesMatch "^(readme\.html|license\.txt|xmlrpc\.php)$">
Order Allow,Deny
Deny from all
</FilesMatch>
# Block author enumeration
RewriteEngine On
RewriteCond %{QUERY_STRING} ^author=([0-9]+) [NC]
RewriteRule .* - [F,L]
location ~* wp-config\.php { deny all; }
location ~* /wp-content/uploads/.*\.php$ { deny all; }
location = /xmlrpc.php { deny all; access_log off; log_not_found off; }
location ~* (readme\.html|license\.txt) { deny all; }
if ($args ~* "author=\d+") { return 403; }
autoindex off;
add_filter('xmlrpc_enabled', '__return_false');
add_filter('wp_headers', function($h) { unset($h['X-Pingback']); return $h; });
remove_action('wp_head', 'rsd_link');
remove_action('wp_head', 'wp_generator');
add_filter('the_generator', '__return_empty_string');
add_filter('style_loader_src', function($src) { return $src ? esc_url(remove_query_arg('ver', $src)) : false; }, 9999);
add_filter('script_loader_src', function($src) { return $src ? esc_url(remove_query_arg('ver', $src)) : false; }, 9999);
add_action('send_headers', function() {
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('Referrer-Policy: strict-origin-when-cross-origin');
header('Permissions-Policy: camera=(), microphone=(), geolocation=()');
header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
});
add_filter('rest_authentication_errors', function($result) {
if (true === $result || is_wp_error($result)) return $result;
if (!is_user_logged_in()) {
return new WP_Error('rest_not_logged_in', 'Authentication required.', array('status' => 401));
}
return $result;
});
Warning: Breaks Contact Form 7, oEmbed, some themes. More targeted: restrict only /wp/v2/users.
Drop-in file: wp-content/object-cache.php. Use Redis Object Cache plugin.
// Core cache functions:
wp_cache_set($key, $data, $group, $expire);
wp_cache_get($key, $group);
wp_cache_delete($key, $group);
wp_cache_flush();
wp redis status / enable / disable / flush
Full-page caching serves static HTML. Requires define('WP_CACHE', true); in wp-config. Drop-in: wp-content/advanced-cache.php. Options: WP Super Cache, WP Rocket, server-level (Varnish, Nginx FastCGI, LiteSpeed).
-- Orphaned postmeta:
DELETE pm FROM wp_postmeta pm LEFT JOIN wp_posts p ON p.ID = pm.post_id WHERE p.ID IS NULL;
-- All revisions:
DELETE FROM wp_posts WHERE post_type = 'revision';
-- Auto-drafts:
DELETE FROM wp_posts WHERE post_status = 'auto-draft';
-- Spam comments:
DELETE FROM wp_comments WHERE comment_approved = 'spam';
-- Find large autoloaded options (keep total under 1MB):
SELECT option_name, LENGTH(option_value) AS size FROM wp_options WHERE autoload = 'yes' ORDER BY size DESC LIMIT 20;
-- Total autoload size:
SELECT SUM(LENGTH(option_value)) AS autoload_size FROM wp_options WHERE autoload = 'yes';
wp transient delete --expired # Clean expired transients
wp db optimize # OPTIMIZE all tables
loading="lazy") added automatically since WP 5.5add_image_size('custom-thumb', 400, 300, true);wp media regenerate --yesadd_filter('heartbeat_settings', function($s) { $s['interval'] = 60; return $s; }); // 15-120 seconds
// Disable on frontend:
add_action('init', function() { if (!is_admin()) wp_deregister_script('heartbeat'); }, 1);
// Defer/async (WP 6.3+):
wp_enqueue_script('handle', $src, array(), '1.0', array('strategy' => 'defer', 'in_footer' => true));
// Conditionally dequeue unused plugin assets:
add_action('wp_enqueue_scripts', function() {
if (!is_page('contact')) { wp_dequeue_style('contact-form-7'); wp_dequeue_script('contact-form-7'); }
}, 100);
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript
AddOutputFilterByType DEFLATE application/javascript application/json application/xml
</IfModule>
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType text/html "access plus 0 seconds"
</IfModule>
| Role | Key Capabilities |
|---|---|
| Administrator | All capabilities (manage_options, install_plugins, edit_users, etc.) |
| Editor | Manage all content (edit_others_posts, manage_categories, moderate_comments) |
| Author | Publish own posts (publish_posts, upload_files, delete_published_posts) |
| Contributor | Write drafts (edit_posts, delete_posts) — cannot publish or upload |
| Subscriber | Read only (read) |
| Super Admin | Multisite: all caps + manage_network, manage_sites, manage_network_* |
// Add (only call once — on plugin activation):
add_role('content_manager', 'Content Manager', array(
'read' => true, 'edit_posts' => true, 'edit_others_posts' => true,
'publish_posts' => true, 'upload_files' => true, 'manage_categories' => true,
));
// Modify existing role:
$role = get_role('editor');
$role->add_cap('manage_options');
$role->remove_cap('manage_options');
// Per-user:
$user = new WP_User($user_id);
$user->add_cap('manage_options');
wp role create content_manager "Content Manager"
wp cap add content_manager edit_posts publish_posts upload_files
wp user set-role user123 editor
wp user add-role user123 author # Multiple roles supported
wp user list-caps user123
# Database:
wp db export backup-$(date +%Y%m%d).sql
# Files:
tar -czf site-backup-$(date +%Y%m%d).tar.gz /path/to/wordpress/
# Content only:
tar -czf content-backup.tar.gz /path/to/wordpress/wp-content/
ALWAYS use WP-CLI — handles serialized data correctly. NEVER use raw SQL search-replace.
# Dry run first:
wp search-replace 'https://old-domain.com' 'https://new-domain.com' --dry-run --precise --all-tables
# Execute:
wp search-replace 'https://old-domain.com' 'https://new-domain.com' --precise --all-tables
# Export to file instead of modifying in-place:
wp search-replace 'old.com' 'new.com' --export=migrated.sql --precise --all-tables
wp db exportwp db importwp search-replace 'old-url' 'new-url' --precise --all-tableswp rewrite flushwp cache flushDefault location: wp-content/debug.log
error_log('Debug: ' . print_r($data, true)); // Write to debug.log
Protect in .htaccess:
<Files debug.log>
Order Allow,Deny
Deny from all
</Files>
define('WP_MEMORY_LIMIT', '256M');wp plugin deactivate --allwp theme activate twentytwentyfourwp core verify-checksumswp-content/debug.log.htaccess to test rewrite rulesmysql -u dbuser -p -h localhost dbnamesystemctl status mysqldefine('WP_ALLOW_REPAIR', true); then visit /wp-admin/maint/repair.php (remove after!)Delete .maintenance file in WordPress root.
wp core verify-checksums # Check core integrity
wp plugin verify-checksums --all # Check plugin integrity
wp option get siteurl # Verify site URL
wp option get home # Verify home URL
wp cron test # Test cron functionality
wp eval 'phpinfo();' | grep error_log # Find PHP error log path
// Recurring:
if (!wp_next_scheduled('my_daily_hook')) {
wp_schedule_event(time(), 'daily', 'my_daily_hook');
}
add_action('my_daily_hook', 'my_function');
// One-time:
wp_schedule_single_event(time() + 3600, 'my_one_time_hook');
// Unschedule:
wp_clear_scheduled_hook('my_daily_hook');
// Custom interval:
add_filter('cron_schedules', function($s) {
$s['every_5_min'] = array('interval' => 300, 'display' => 'Every 5 Minutes');
return $s;
});
Built-in schedules: hourly (3600s), twicedaily (43200s), daily (86400s), weekly (604800s).
# Disable WP pseudo-cron:
define('DISABLE_WP_CRON', true);
# Add to crontab:
*/5 * * * * cd /path/to/wordpress && /usr/local/bin/wp cron event run --due-now > /dev/null 2>&1
*.example.com) and wildcard SSLCannot switch between types after setup.
wp-content/mu-plugins/): Always active, cannot be deactivatedsunrise.php: Loaded early for domain mapping (requires define('SUNRISE', true);)wp site list # List sites
wp site create --slug=newsite --title="New Site" # Create site
wp --url=site2.example.com plugin list # Run on specific site
wp plugin activate myplugin --network # Network activate
// Granular filter control:
add_filter('allow_major_auto_core_updates', '__return_true'); // Enable major core updates
add_filter('auto_update_plugin', '__return_true'); // Enable all plugin auto-updates
add_filter('auto_update_theme', '__return_true'); // Enable all theme auto-updates
// Selective plugin auto-updates:
add_filter('auto_update_plugin', function($update, $item) {
return in_array($item->slug, array('akismet', 'wordfence'), true);
}, 10, 2);
wp plugin auto-updates enable akismet wordfence
wp plugin auto-updates disable contact-form-7
wp theme auto-updates enable flavor
| Table | Purpose |
|---|---|
wp_posts | All content: posts, pages, CPTs, revisions, nav menus, attachments |
wp_postmeta | Post metadata (custom fields) |
wp_options | Site settings, plugin settings, widgets |
wp_users | User accounts |
wp_usermeta | User metadata (roles, capabilities, preferences) |
wp_terms | Taxonomy terms |
wp_term_taxonomy | Links terms to taxonomies |
wp_term_relationships | Links posts to terms |
wp_termmeta | Term metadata |
wp_comments | Comments |
wp_commentmeta | Comment metadata |
| Don't | Why |
|---|---|
| Leave WP_DEBUG true on production | Exposes errors to visitors |
| Use raw SQL for search-replace | Breaks serialized data |
| Skip backups before updates | No recovery path |
Leave WP_ALLOW_REPAIR enabled | Anyone can access repair page |
| Set 777 permissions | Full access to everyone — major security risk |
| Keep unused plugins installed | Attack surface, even when deactivated |
| Ignore autoloaded options bloat | Slows every page load |
| Use WP pseudo-cron on high-traffic sites | Adds latency to random visitor requests |
| Edit core files directly | Lost on next update |
| Use same DB prefix on multiple installs | Shared DB = shared vulnerabilities |