The “famous 5-minute install” is a marketing slogan, not a professional standard. A default WordPress installation is “chatty”, unoptimised and often unsafe.
As developers we do not just “install” WordPress. We prepare it to run in a specific environment. This guide covers the key configuration constants and hardening techniques that belong in the baseline of every client project in 2026.
Configuring wp-config.php after installing WordPress
This is the brain of your installation. Stop leaving it on default settings.
Setting WP_ENVIRONMENT_TYPE in wp-config.php
Since WordPress 5.5, WP_ENVIRONMENT_TYPE is the standard. Use it to keep development errors from leaking into production.
// In wp-config.php
define( 'WP_ENVIRONMENT_TYPE', 'production' ); // 'local', 'development', 'staging', 'production'Then in your code:
if ( wp_get_environment_type() === 'production' ) {
// Enable cache, disable errors
}Disabling the file editor and forcing SSL in wp-config.php
Stop clients (or attackers) from breaking the site through the admin panel.
// Disable the file editor (theme/plugin editor)
define( 'DISALLOW_FILE_EDIT', true );
// Block plugin installs/updates (good for immutable deployments)
define( 'DISALLOW_FILE_MODS', true );
// Force SSL in the admin
define( 'FORCE_SSL_ADMIN', true );How to limit post revisions in WordPress
The database killer. Do you really need 100 versions of the “About us” page?
define( 'WP_POST_REVISIONS', 10 ); // Keep the last 10
// OR
define( 'WP_POST_REVISIONS', false ); // Disable completely (not recommended)How to enable WP_DEBUG_LOG without displaying errors
Never display errors on the frontend. Log them.
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', '/tmp/wp-errors.log' ); // Move the log outside the public directory!
define( 'WP_DEBUG_DISPLAY', false );
// Log SQL queries for performance debugging (disable in production!)
define( 'SAVEQUERIES', false );How to disable emoji and XML-RPC in WordPress
WordPress ships with features that 90% of business sites do not need: emoji, oEmbeds and XML-RPC.
Do not install a plugin to turn them off. Create a must-use plugin (wp-content/mu-plugins/lean-core.php).
<?php
/* Plugin Name: Lean Core */
// 1. Disable emoji (saves an HTTP request)
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
// 2. Disable XML-RPC (security)
add_filter( 'xmlrpc_enabled', '__return_false' );
// 3. Remove the WP version (security by obscurity)
remove_action( 'wp_head', 'wp_generator' );
// 4. Disable RSS feeds (if you are building a brochure site)
// function wppoland_disable_feed() {
// wp_die( 'No feed here, visit our homepage!' );
// }
// add_action('do_feed', 'wppoland_disable_feed', 1);Despite its name, the xmlrpc_enabled filter in point 2 only switches off XML-RPC methods that require authentication, such as publishing. Pingbacks and other unauthenticated methods still answer on xmlrpc.php. Blocking the whole file belongs on the web server, and only if nothing uses it: Jetpack, for example, connects through XML-RPC.
Changing the salt keys in wp-config.php
You know the authentication keys in wp-config.php.
define('AUTH_KEY', 'put your unique phrase here');
// ...Fact: changing them logs every user out immediately. It is the “nuclear option” if the site has been hacked. Pro tip: automate their rotation with a CLI script or Vault if you manage enterprise sites.
What to set in wp-config.php before the site goes live
Before you launch:
- Set
WP_ENVIRONMENT_TYPEto production. - Set
DISALLOW_FILE_EDITto true. - Limit
WP_POST_REVISIONS. - Move
WP_DEBUG_LOGto a private folder. - Disable emoji/XML-RPC in code.
A well-configured WordPress instance is quiet, secure and fast.
Which permalinks to set in WordPress
The optimal permalink structure
Settings > Permalinks > Post nameWhy:
- Shorter, more readable URLs
- Better for SEO (keywords in the URL)
- Easier to remember and share
A fresh install often has pretty permalinks already. When the server supports URL rewriting, the installer sets /%year%/%monthnum%/%day%/%postname%/ (the core function wp_install_maybe_enable_pretty_permalinks()). Switch to Post name before the first post goes out: changing the structure on a live site changes every post URL, and the old addresses then need redirects.
Category and tag structure
- Create the core categories (3-7 main ones)
- Define the hierarchy (parent and child categories)
- Set a default category (not “Uncategorized”)
- Plan a tag strategy (optional, 5-10 tags per post)
WordPress media settings and image sizes
Image sizes
Settings > MediaRecommended settings:
- Thumbnail: 150x150px (tick “Crop thumbnail to exact dimensions”)
- Medium: 300x300px
- Large: 1024x1024px
- Full size: always available
Automatic image optimisation
<?php
/* Plugin Name: Image Sizes */
add_action( 'init', function () {
remove_image_size( '1536x1536' ); // WordPress 5.3+
remove_image_size( '2048x2048' ); // WordPress 5.3+
} );Two lines common in copies of this snippet are left out on purpose. wp_lazy_loading_enabled already returns true by default (native loading="lazy" since WordPress 5.5), and the wp_image_editors filter only sets the order in which WordPress tries Imagick and GD, which is the default order anyway. Neither optimises anything at upload. Images wider or taller than 2560 px are already scaled down on upload through the big_image_size_threshold filter.
How to enable WebP uploads in WordPress
add_filter( 'image_editor_output_format', function ( $formats ) {
$formats['image/jpeg'] = 'image/webp';
$formats['image/png'] = 'image/webp';
return $formats;
} );The snippet does not use wp_upload_image_mime_transforms, a filter that is not in current WordPress core. image_editor_output_format has existed since WordPress 5.8 and maps a source type to the type WordPress saves generated sizes in; since 6.7 its default already maps HEIC and HEIF to JPEG, and the snippet keeps those entries. The server’s Imagick or GD build has to support WebP. Tools > Site Health > Info > Media Handling shows what it supports.
How to secure WordPress after installation
Changing the default table prefix
During installation: Enter a custom prefix instead of wp_ (for example x7k9_, mysite_, proj23_)
After installation (advanced):
// wp-config.php
$table_prefix = 'x7k9_';RENAME TABLE wp_posts TO x7k9_posts;
RENAME TABLE wp_options TO x7k9_options;
-- ... repeat for every table
UPDATE x7k9_options SET option_name = 'x7k9_user_roles' WHERE option_name = 'wp_user_roles';
UPDATE x7k9_usermeta SET meta_key = CONCAT('x7k9_', SUBSTRING(meta_key, 4)) WHERE meta_key LIKE 'wp\_%';WordPress has no DB_PREFIX constant: the prefix lives in the $table_prefix variable. Renaming the tables alone locks administrators out, because roles and capabilities are stored under prefixed keys in options (wp_user_roles) and usermeta (wp_capabilities, wp_user_level). That is what the two UPDATE statements fix. The Hardening WordPress guide treats a custom prefix as obscurity: it can block some automated SQL injection attacks that assume wp_, and it is no substitute for fixing injectable code.
Protecting the wp-config.php file
# .htaccess (root, at the very top)
<Files "wp-config.php">
Require all denied
</Files>Order allow,deny is Apache 2.2 syntax and only works on Apache 2.4 through the compatibility module. The Hardening WordPress guide gives the Require all denied form above and puts it at the very top of the root .htaccess. Nginx ignores .htaccess files, so on Nginx the same rule belongs in the server block.
Blocking directory access
# wp-content/uploads/.htaccess
Options -Indexes
# .htaccess (root, outside # BEGIN WordPress and # END WordPress)
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>WordPress rewrites everything between the # BEGIN WordPress and # END WordPress markers whenever permalinks are saved, so this block goes outside them, in the root .htaccess, not in wp-includes/: the paths in the rules are relative to the site root. On multisite, the ^wp-includes/[^/]+\.php$ line stops ms-files.php from serving images, and the Hardening WordPress guide says to drop that line there, at the cost of some protection.
Limiting login attempts
<?php
/* Plugin Name: Login Security */
function wppoland_login_key() {
return 'login_attempts_' . md5( $_SERVER['REMOTE_ADDR'] );
}
add_filter( 'authenticate', function ( $user ) {
if ( (int) get_transient( wppoland_login_key() ) >= 5 ) {
return new WP_Error( 'too_many_attempts', 'Too many login attempts. Try again in an hour.' );
}
return $user;
}, 30 );
add_action( 'wp_login_failed', function () {
$key = wppoland_login_key();
set_transient( $key, (int) get_transient( $key ) + 1, HOUR_IN_SECONDS );
} );
remove_action( 'wp_head', 'wp_generator' );
add_filter( 'the_generator', '__return_empty_string' );Counting failures in wp_login_failed and calling wp_die() there does not work. That hook runs after the password check, so a correct password still logged in after five failures. The authenticate filter at priority 30 runs after core has checked the username and password, and a WP_Error returned there stops the login. Behind a CDN or reverse proxy, REMOTE_ADDR holds the proxy’s address, so every visitor would share one counter: on such setups, limit logins at the proxy or with fail2ban on the server instead.
Two-factor authentication (2FA)
WordPress core has no second factor. The Two Factor plugin, published on WordPress.org under the WordPress.org account, adds authenticator apps (TOTP), email codes and backup codes, and each user switches it on in their own profile. The Hardening WordPress guide recommends two-step authentication as an extra layer on top of the password. The snippet below does something smaller: it rejects passwords shorter than 12 characters when a profile is saved.
// Enforce strong passwords
add_action('user_profile_update_errors', function($errors) {
$password = $_POST['pass1'];
if (!empty($password) && strlen($password) < 12) {
$errors->add('password_error', 'The password must be at least 12 characters long.');
}
});How to speed up WordPress after installation
Basic database optimisation
// In wp-config.php
// Limit post revisions
define('WP_POST_REVISIONS', 10);
// Empty the trash automatically after 7 days
define('EMPTY_TRASH_DAYS', 7);
// Disable the file editor (security + performance)
define('DISALLOW_FILE_EDIT', true);Database cleanup
wp transient delete --expired
wp post list --post_type=revision --format=count
wp db optimizeThe removed mu-plugin deleted transients by comparing option_value with the current time, but the expiry time sits in separate _transient_timeout_ rows, so it deleted the wrong rows. Core already does this job: wp-admin/admin.php schedules a daily delete_expired_transients event (skipped when a persistent object cache holds transients), and a daily wp_scheduled_auto_draft_delete event removes auto-drafts older than seven days. With WP_POST_REVISIONS set, revisions do not need a deletion cron either. Run the WP-CLI commands above by hand when a database looks bloated.
Optimising the Heartbeat API
add_filter( 'heartbeat_settings', function ( $settings ) {
$settings['interval'] = 120; // default: 60
return $settings;
} );
add_action( 'init', function () {
if ( ! is_admin() ) {
wp_deregister_script( 'heartbeat' );
}
}, 1 );The Heartbeat default is 60 seconds (mainInterval in heartbeat.js), not 15, so the earlier value of 60 changed nothing. Heartbeat also slows itself to 120 seconds when the browser tab is hidden. In the editor it carries post locks, so a longer interval delays the warning that someone else is editing the same post.
Removing RSD, shortlink and REST API links from wp_head
// In a mu-plugin: wp-content/mu-plugins/disable-bloat.php
<?php
/* Plugin Name: Disable Bloat */
// Disable emoji
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('wp_print_styles', 'print_emoji_styles');
// Disable XML-RPC
add_filter('xmlrpc_enabled', '__return_false');
// Disable the RSD link
remove_action('wp_head', 'rsd_link');
// Disable the Windows Live Writer link
remove_action('wp_head', 'wlwmanifest_link');
// Disable the shortlink
remove_action('wp_head', 'wp_shortlink_wp_head');
// Disable the REST API link (if you do not use it)
remove_action('wp_head', 'rest_output_link_wp_head');Basic SEO settings in WordPress
Site title, tagline and search engine visibility
Settings > General:
- Site Title: [Your brand]
- Tagline: one short sentence, or empty
Settings > Reading:
- Search engine visibility: leave "Discourage search engines from indexing this site" UNCHECKEDCore prints no meta description, so the tagline is not one. On current installs the field starts empty, and themes may show it in the header or the title. The meta description comes from an SEO plugin or the theme. The search engine visibility checkbox lives under Reading, not General, and while it is checked WordPress also switches off its own sitemap.
An SEO-friendly permalink structure
Settings > Permalinks > Post nameXML sitemap
add_filter( 'wp_sitemaps_post_types', function ( $post_types ) {
unset( $post_types['portfolio'] ); // remove a post type from the sitemap
return $post_types;
} );The core sitemap at /wp-sitemap.xml has been on by default since WordPress 5.5, as long as the site is visible to search engines. It lists every public post type, custom ones included, so the old $post_types[] = 'portfolio' line was unnecessary and also wrong: the array holds WP_Post_Type objects keyed by name. The filter is useful the other way round, to take a type out of the sitemap.
Basic image optimisation for SEO
A common snippet fills empty ALT attributes with the attachment title and generates titles from filenames. The second step is already core behaviour: media_handle_upload() takes the attachment title from the filename. The first one produces ALT text such as “IMG_2041”, which a screen reader announces and which says nothing about the picture. Write the ALT text per image in the media library and leave it empty for purely decorative images.
How to back up WordPress
Automatic database backups
# crontab -e (site user, not root)
15 3 * * * cd /var/www/example/public && wp db export /home/example/backups/db-$(date +\%F).sql --quiet && find /home/example/backups -name 'db-*.sql' -mtime +7 -deleteThe earlier mu-plugin wrote dumps to wp-content/uploads/backups, a public folder where anyone who guesses the date-based filename can download the database. It also passed the database password on the mysqldump command line, where other users of the server can see it in the process list, and it relied on WP-Cron, which only runs when someone visits the site. A system cron job with wp db export writes outside the web root and runs on time.
Setting up off-site backups
Recommended tools:
- UpdraftPlus: the free version is enough for small sites
- BlogVault: paid, but very reliable
- WPvivid: a good free alternative
Configuration:
- Daily backup (database)
- Weekly backup (full)
- Retention: at least 30 days
- External location: Google Drive, Dropbox, S3
Configuring SMTP in WordPress
SMTP instead of mail()
// In wp-config.php or a mu-plugin
define('SMTP_USER', '[email protected]');
define('SMTP_PASS', 'your-password');
define('SMTP_HOST', 'smtp.gmail.com');
define('SMTP_PORT', '587');
define('SMTP_SECURE', 'tls');
define('SMTP_FROM', '[email protected]');
define('SMTP_NAME', 'Your Site');
// In a mu-plugin
add_action('phpmailer_init', function($phpmailer) {
$phpmailer->isSMTP();
$phpmailer->Host = SMTP_HOST;
$phpmailer->SMTPAuth = true;
$phpmailer->Username = SMTP_USER;
$phpmailer->Password = SMTP_PASS;
$phpmailer->SMTPSecure = SMTP_SECURE;
$phpmailer->Port = SMTP_PORT;
$phpmailer->From = SMTP_FROM;
$phpmailer->FromName = SMTP_NAME;
});How to send a test email from WordPress
add_action( 'admin_init', function () {
if ( isset( $_GET['test_email'] ) && current_user_can( 'manage_options' ) ) {
$sent = wp_mail( get_option( 'admin_email' ), 'Test email', 'This is a test message sent from WordPress.' );
wp_die( $sent ? 'OK' : 'wp_mail() returned false' );
}
} );admin_init also fires on admin-ajax.php requests from visitors who are not logged in, so without the capability check anyone could make the site send mail. With WP-CLI the same test is one line: wp eval 'var_dump( wp_mail( get_option( "admin_email" ), "Test", "Test" ) );'.
WordPress monitoring and error logging
Error logging
// In wp-config.php
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', '/var/log/wp-errors.log'); // Outside the public directory
define('WP_DEBUG_DISPLAY', false);
// SQL query logging (debug mode only)
// define('SAVEQUERIES', true);Uptime monitoring
Recommended tools:
- UptimeRobot
- Pingdom (paid, but more advanced)
- Better Uptime (a modern alternative)
Logging admin logins and post updates
// In a mu-plugin: wp-content/mu-plugins/activity-log.php
<?php
/* Plugin Name: Activity Log */
add_action('wp_login', function($user_login, $user) {
error_log('User logged in: ' . $user_login . ' from IP: ' . $_SERVER['REMOTE_ADDR']);
}, 10, 2);
add_action('save_post', function($post_id, $post, $update) {
if ($update && $post->post_status === 'publish') {
error_log('Post updated: ' . $post->post_title . ' by user: ' . get_current_user_id());
}
}, 10, 3);What to check before launching a WordPress site
Pre-launch checklist
- All SEO settings correct
- Sitemap generated
- Google Search Console set up
- Google Analytics installed
- Privacy policy and terms added
- Contact forms tested
- Email works
- SSL works (HTTPS)
- Redirects between www and non-www configured
- Backup taken
- Caching plugins configured (optional)
- CDN configured (optional)
- Performance tests run (PageSpeed Insights)
- Mobile responsiveness tests
- Cross-browser tests
- 404 page configured
- Favicon added
Testing checksums and file access with WP-CLI and curl
# WP-CLI tests
wp core verify-checksums
wp plugin verify-checksums --all
wp db check
# Security tests
curl -I https://your-site.com/wp-admin/install.php # returns the "Already Installed" page unless the server blocks it
curl -I https://your-site.com/wp-config.php # 403 with the <Files> rule; without it, an empty pageWordPress settings to change after installation
Three screens under Settings hold defaults that matter before the first visitor arrives. The General screen documentation covers each field.
General. Pick the timezone as a city rather than a UTC offset. A city follows daylight saving time, a fixed offset does not, and scheduled posts then publish an hour off for half the year. The site language set here is the language of the dashboard. A new administration email address does not take effect at once: WordPress sends a confirmation link to the new address and keeps the old one active until the link is clicked, so use a mailbox someone reads. Leave Membership (“Anyone can register”) off unless the site needs open registration, and if it does, keep the new user default role at Subscriber.
Reading. This screen holds the search engine visibility checkbox. Staging copies should have it checked and production must not. The setting is stored in the database, so it travels with every copy of the database: check it after each move from staging to production.
Discussion. A new install has comments and pingbacks open by default, manual approval of comments switched off and “Comment author must have a previously approved comment” switched on. On a company site without comments, close them here before publishing. The setting applies to new posts; existing posts keep their own status.
How to read Site Health after installing WordPress
Tools > Site Health runs a set of checks and sorts the results into critical issues and recommended improvements (Site Health screen documentation). On a new site, the useful checks are the ones the settings in this article touch:
- Debugging enabled. With
WP_DEBUG_LOGon, the check turns critical when the PHP error log sits inside the WordPress directory, because the log can then be downloaded. That is why the log path earlier in this article points outside the public folder. - Search engine visibility. Flagged while “Discourage search engines” is checked (a test added in WordPress 6.9).
- Open registration with privileged default role. Flagged when anyone can register with a role above Subscriber (since WordPress 7.0).
- Background updates, plugin and theme auto-updates, scheduled events, loopback request. If these fail, automatic updates and WP-Cron jobs do not run.
- HTTPS status, persistent object cache, page cache. Hosting checks. The two cache checks are recommendations, not errors.
The Info tab lists server details (PHP version and extensions, database version, image editor support, constants such as WP_DEBUG_LOG and WP_ENVIRONMENT_TYPE) and has a button that copies everything to the clipboard. Pasting that into a ticket saves a round of questions with the host.
How automatic updates work on a new WordPress site
There are three layers, set in different places (Configuring automatic background updates):
- Minor core releases (security and maintenance) install automatically, and have done since WordPress 3.7.
- Major core releases. On sites installed with WordPress 5.6 or later, the
auto_update_core_majoroption starts as enabled. Sites upgraded from an older version start with it unset. TheWP_AUTO_UPDATE_COREconstant overrides both:truefor all releases,'minor'for minor releases only,falseto switch background core updates off. - Plugins and themes. Auto-updates are off by default and are switched on per plugin or theme on the Plugins and Themes screens, a feature added in WordPress 5.5.
DISALLOW_FILE_MODS (point 5 of the checklist) does more than hide the install and update buttons. Core checks wp_is_file_mod_allowed( 'automatic_updater' ) before any background update, so the constant also stops automatic security releases. That is fine when every update ships through a deployment. Without such a process, the site stays on the version it was deployed with until someone deploys again.
Should you disable application passwords in WordPress
Application passwords, added in WordPress 5.6, let an external tool authenticate to the REST API with the username and a generated password instead of the account password (Application Passwords documentation). Each user creates and revokes them in their profile. A new password is shown once, stored as a hash, and listed with its last use date and IP address. Core offers the feature only on HTTPS sites or when WP_ENVIRONMENT_TYPE is local.
Leave it on when a mobile app, a deployment tool or an integration talks to the REST API. On a site with none of those, one filter turns it off for every user:
add_filter( 'wp_is_application_passwords_available', '__return_false' );After a suspected break-in, revoke application passwords as well as rotating the salts. New salts end all login sessions, but application passwords are hashed with a fixed key that does not depend on the salts, so they keep working until someone revokes them.
How to set up the privacy policy page in WordPress
The installer creates a “Privacy Policy” page as a draft and assigns it on Settings > Privacy, a screen added in WordPress 4.9.6 (Privacy settings documentation). The draft holds suggested headings and sample text, not a finished policy. The same screen lets you pick another page and links to the privacy policy guide, where core and plugins that register suggested text through wp_add_privacy_policy_content() describe what they store.
Two things follow. The draft is not public until someone edits and publishes it. And themes print the policy link through the_privacy_policy_link(), which outputs nothing while the assigned page is unpublished, so a footer link that “is missing” usually means an unpublished page. Tools > Export Personal Data and Tools > Erase Personal Data handle access and erasure requests for the data WordPress and cooperating plugins keep.
Summary: the 50-point checklist
Security (1-15)
- ✅ Set a custom table prefix (on install, via
$table_prefix) - ✅ Set strong passwords (min. 12 characters)
- ✅ Change the admin username
- ✅ Disable the file editor in the admin
- ✅ Block plugin installation (optional; also stops automatic updates)
- ✅ Force SSL in the admin panel
- ✅ Protect wp-config.php with .htaccess
- ✅ Block directory access (no indexes)
- ✅ Restrict XML-RPC (the filter covers authenticated methods only; keep it for Jetpack)
- ✅ Hide the WordPress version
- ✅ Limit login attempts
- ✅ Set up two-factor authentication (Two Factor plugin)
- ✅ Rotate the authentication keys (salts) regularly
- ✅ Install an SSL certificate
- ✅ Configure server-level protection (firewall, login attempt limits)
Performance (16-25)
- ✅ Limit post revisions (5-10)
- ✅ Disable emoji
- ✅ Disable unused features (XML-RPC, RSD)
- ✅ Optimise image sizes
- ✅ Confirm lazy loading is active (core default since 5.5)
- ✅ Configure caching (object cache, page cache)
- ✅ Optimise the Heartbeat API
- ✅ Enable GZIP/Brotli compression
- ✅ Configure a CDN (optional)
- ✅ Optimise the database (automatic cleanup)
SEO (26-35)
- ✅ Set the “Post name” permalink structure
- ✅ Set the site title and tagline (the tagline is not a meta description)
- ✅ Check the core XML sitemap at /wp-sitemap.xml
- ✅ Add the site to Google Search Console
- ✅ Configure Google Analytics / GA4
- ✅ Write ALT text per image
- ✅ Create the category structure
- ✅ Configure breadcrumbs
- ✅ Add the Organization schema
- ✅ Prepare Open Graph meta tags
Backups (36-42)
- ✅ Set up automatic database backups
- ✅ Set up weekly full backups
- ✅ Store backups in an external location
- ✅ Test restoring from a backup
- ✅ Set backup retention (min. 30 days)
- ✅ Document the restore procedure
- ✅ Verify backup integrity regularly
Content and functionality (43-50)
- ✅ Create the core pages (About us, Contact, Privacy policy)
- ✅ Configure the navigation menus
- ✅ Test every form
- ✅ Configure SMTP for email
- ✅ Create a custom 404 page
- ✅ Add a favicon and PWA icons
- ✅ Run performance tests
- ✅ Document the configuration
See our WordPress security audit if you want to get the site’s risks and protections in order.
wp-config.php differences between staging and production
This guide sets WP_DEBUG twice with different values: true in the debugging section and false in the monitoring section. Both are correct, each for a different environment, which is why the same copy of wp-config.php should not go unchanged to staging and to production. Before you move the file between servers, go through these constants:
WP_ENVIRONMENT_TYPE:stagingon the test copy,productionon the live site. Every condition built onwp_get_environment_type()depends on this value.WP_DEBUG: on in staging, off in production.WP_DEBUG_DISPLAYstaysfalsein both places, and the log goes outside the public directory.SAVEQUERIES: only while you are hunting slow queries, never in production.DISALLOW_FILE_MODS: in production it blocks installing and updating plugins from the admin, so updates go through deployment or WP-CLI. Agree this with the client before they notice the missing update button.- Authentication keys: generated randomly for every installation. A copy of production on staging gets its own set, and you leave the production keys alone, because changing them logs every user out.
After moving the file, run wp core verify-checksums and wp plugin verify-checksums --all from the pre-launch tests section.







