The Immense Power of wp-config.php

The thing that motivates me in writing these columns for WPX.net is my desire to help you achieve mastery over WordPress. And mastery implies having knowledge and exerting control.

There is one file within each WordPress installation that allows an inordinate amount of control over the behavior of the entire instance — core, themes and even some plugins: the wp-config.php file. You can use it to unlock hidden functionality and impose limits on users’ behavior and performance that are impossible to override from the WordPress dashboard.

I have already mentioned wp-config.php in passing in a couple of previous columns. In this article, it will be the centerpiece.

Interestingly, the WordPress Codex does not have a separate article about wp-config.php configuration parameters. There is no explanation how to use all of them.

I am aiming to improve that. When published, this column will be the most detailed collection of documented wp-config settings in existence, as far as I know. I also intend to come back to it regularly and update it when I come across some new and helpful settings.

This isn’t the kind of blog post that one is expected to read from beginning to end (but please feel free to skim through it at least the first time you see it). It has been structured like a reference guide. If you find any of this information useful, bookmark it and check back from time to time.

Let us dig into what wp-config.php can offer us.

Table of Contents

Database Access and Configuration

Almost everyone knows that wp-config.php holds database credentials. If you ever see the ‘Error establishing a database connection’ message when opening your WordPress website, the very first thing you need to do is open wp-config.php and check out the settings for DB_NAME, DB_USER, DB_PASSWORD and DB_HOST.

Near these are two other less well understood parameters: DB_CHARSET and DB_COLLATE. The Database Character Set determines the encoding used within the database. Since WordPress 4.2, the default and recommended setting for this is ‘utf8mb4‘ which allows the database to store the full, 32-bit range of Unicode symbols. This should be the default value for any new WordPress instance you create.

The collation setting determines how symbols are sorted within the selected character set. The recommended value for this setting is ‘utf8mb4_unicode_ci‘ but if your WordPress website is designated to use with a specific language, you might select that specific collation. The rule of thumb here is that if you need to change the collation, you know you have to; otherwise you should stick with Unicode, case-insensitive.

Keys and Salts

Below the database settings is a block of 4 keys and 4 salts. WordPress uses the four keys to encrypt certain parts of its data, and salts the data stored in the browser cookies with the other 4 strings to prevent cookie forgery. These values shoud never be shared with anybody.

Regenerating the keys and salts is the fastest way to stop site hijacking via stolen browser cookies. Replacing them has the effect of immediately invalidating all existing sessions (including yours, as the administrator).

Bear in mind that somebody who is finalizing a purchase from your Woocommerce store as you are resetting the keys and salts will lose their cart contents, but there will no long-lasting effects or consequences besides that.

Force Use of HTTPS

These days, it is less frequent to see websites that do not use SSL connections to serve pages to their visitors. Usually, this is handled at server level; and if your website uses HSTS headers, even at domain level.

However if a particular site does not use SSL by default, you might want to make sure that at least the admin area is using an encrypted connection. To do that, you may add this setting to wp-config.php

define('FORCE_SSL_ADMIN', true);

Note: older WordPress tutorials mention another setting, FORCE_SSL_LOGIN, which enforces an encrypted connection for login pages. This one is deprecated since WordPress 4.4; even if you put it in wp-config.php, it will be ignored.

Post and Media Management and Housekeeping

WordPress has a very robust auto save and versioning for blog posts, which allows users to effortlessly restore their content to an earlier state.

By default, WordPress will enable auto-save and store a copy of your post in the database and in the browser’s cache once every 60 seconds. Depending on your preferences, you might want to change that. You do this by adding the following line to wp-config.php:

define( 'AUTOSAVE_INTERVAL', 120 ); // Defaults to 60 seconds

It is not currently possible to completely disable autosave, but providing a large enough value will do the same. Hint: there are 86,400 seconds in a 24-hour day.

Keeping previous posts revisions can be a life-saver, but it also fills the database with many copies of your posts. This could quickly turn into an issue if you have many writers working in parallel. That is why WordPress allows you to limit the number of post revisions it will store, or to disable revisioning altogether:

define( 'WP_POST_REVISIONS', 3 );
define( 'WP_POST_REVISIONS', false ); // Disable feature

Note: changing this setting will not influence the number of post revisions for exinsting posts, unless you edit each one of them afterwards. Plugins like WP-Optimize can help you clean up revisions of old publications.

Media Management

Unlike blog posts, pages, comments and other kinds of data, WordPress treats images very roughly. It allows users to delete them, and offers no means of restoring them once this is done.

The solution to this apparent injustice is the following pair of wp-config.php settings:

define('MEDIA_TRASH', true) ;  // Trash media files instead of delete
define('EMPTY_TRASH_DAYS', 7); // Empty trash after 7 days

The first one enables the trash bin for media images, and the second one determines how long they are kept there before getting deleted for real. Bear in mind that enabling EMPTY_TRASH_DAYS is not obligatory: if you leave it out of the config file, media images will simply stay in trash until manually deleted.

By default, the media library will only accept files of certain formats. While this list is long, you might find yourself in a situation when you need to upload a file and publish it on your WordPress website, except WordPress would have none of it. The easiest way to allow this is to enable the following setting:

define('ALLOW_UNFILTERED_UPLOADS', true);

However bear in mind that this has the potential to significantly degrade the WordPress security model. It allows privileged users to upload unchecked files that might be infected with malware, or — if you are uploading shell scripts and executable files — hackers could make attempts to call them from the outside. This option should be used with extreme caution, and/on only occasionally (for example, you could enable it while you upload a specific file, link to it from a blog post and then disable unfiltered uploads once more).

Memory Allocation

WordPress allows users to set or increase the limit to the usage of system RAM. If you ever get a PHP error that says Allowed memory size of ... bytes exhausted (tried to allocate ... bytes), then it is time to increase your RAM limit. The way to do that is add the following setting to wp-config.php

define('WP_MEMORY_LIMIT', 128M);

Having more RAM at our disposal could be helpful for heavier websites with complicated functionality. You should always remember however that usually there is a memory limit setting imposed globally on your hosting account, and that you can’t rely on WP_MEMORY_LIMIT to get access to indefinitely more memory than is allotted to you by the webhost.

However it is important to know that WP_MEMORY_LIMIT has a less known sister function that allows administrators to control RAM usage in the back-end independently of the front-end.

define('WP_MAX_MEMORY_LIMIT', 256M);

WP_MAX_MEMORY_LIMIT overrides the memory limit for PHP processes executing in the back-end, which could help those who use WordPress as a CRM service or something similar. But once again, you must remember that you are limited by the memory allocation which your hosts provides.

Side note: this limit is very often 128MB, and more rarely 256MB. However WPX — being the awesome host they are — allow their clients to set up max memory limit of 1024MB.

Catch and Process 404 Not Found Requests

I honestly think that allowing users to see a 404 ‘Not Found’ page is a small tragedy. As a webmaster, you should do your utmost to prevent such situation. Thankfully, WordPress makes things much easier for you: it can be configured to intercept all 404 requests and send them to a custom page, and you only need to create that page.

/* Catch 404 pages and redirect to a custom page */
define('NOBLOGREDIRECT', 'https://YOURDOMAIN.COM/are-you-lost');

I have planned a blog post specifically dedicated to creating custom 404 pages. However that one is still many weeks away from completion, and you might not have time to wait for me. In such case, open Google, and make a search for “awesome 404 error pages” to get some ideas.

Enable/Disable Theme and Plugin Editor

The WordPress ships with a quite convenient file editor that allows users to make changes to theme and plugin components. On production websites these functions should of course stay disabled and hidden.

But if you are working on a theme or a plugin and need to study the code, this is the fastest way to view it and make changes if needed.

define( 'DISALLOW_FILE_EDIT',  false );
define( 'DISALLOW_THEME_EDIT', false );

Pasting these two inside wp-config.php will add two new entries to your WordPress admin bar:

Plugins ➡ Plugin File Editor
Appearance ➡ Theme File Editor

Change Site URL and Home Directory

These next two settings are extraordinarily powerful.

When you provision a new WordPress instance, the installer sets up two very important internal parameters: WP_HOME and WP_SITEURL. These were initially meant to be used when somebody wanted to configure WordPress to appear as if it opens from the default domain URL but without cluttering the www root folder with extra files.

Hence, WordPress allows you to provide separate settins for the physical locations of your WordPress folder and the publicly-facing domain. You would set WP_HOME to https://example.com and WP_SITEURL to https://example.com/wordpress/, for example. Combined with some .htaccess trickery, when visitors attempt to load example.com, WordPress would serve them from example.com/wordpress but the URLs in the address bar would still appear as if they originate from example.com.

This was important maybe 10 or 15 years ago when WordPress had little adoption and had to offer anything it could to make it easy for webmasters to deploy it parallel to some other CMS or static website.

Nowadays almost nobody would think about tucking WordPress away in a subfolder (‘Nobody puts Baby in the corner!’), but the WP_HOME and WP_SITEURL settings are still supported, and can be used for something way more powerful, like changing the entire apparent URL of the domain, or making the same WordPress instance serve content under multiple domains!

The values for WP_HOME and WP_SITEURL can be changed from the admin dashboard (Settings ➡ General, WordPress Address and Site Address), and are stored in the wp-options table. Normally, this limits WordPress to responding only to requests that match the domain and URL stored in these settings.

However we can use wp-config.php to override the default WordPress behavior and add the following lines:

define( 'WP_SITEURL', 'http://example.com/wordpress' );
define( 'WP_HOME',    'http://example.com' );

WP_SITEURL and WP_HOME will override the matching values stored within the wp_options table, but will not change them permanently. This makes these settings extremely helpful when you need to promptly migrate the WordPress instance to a different domain, or to make a quick copy of it for testing/staging purposes and let yourself in to make permanent changes.

We can even use PHP environment variables to make the address replacement dynamic. Consider this setting:

define ( 'WP_SITEURL', 'https://' . $_SERVER['HTTP_HOST'] );
define ( 'WP_HOME', 'https://' . $_SERVER['HTTP_HOST'] );

Now imagine that your webserver is configured to serve 5 different domains: example.com, example.net, example.gov, example.edu, example.org. As long as these domains are configured to resolve to the IP address of your web server, and you have configured the www_root for each vhost to point towards the physical folder where your WP instance is located, WordPress will respond to each inquiry as if it has been configured to serve each domain natively!

One final word about site relocation: there might be another way to do it using a similar setting called RELOCATE that I learned about recently, but haven’t used in practice.

define ( 'RELOCATE', true );

It is mentioned in the WordPress codex, under the main article that discusses changing a WordPress websites’s URL. As far as I can tell, the difference between the previous method and this one is that RELOCATE reads the values of HTTP_HOST and PATH_INFO from the server environment and changes the content of WordPress Address URL and Site Address URL in Settings ➡ General.

In a sense, the behavior is similar to that of using WP_SITEURL and WP_HOME with the HTTP_HOST server variable, which makes RELOCATE a convenient shortcut.

It is very important to disable this setting as soon as you’re done moving the website.

Auto-Update Control

WordPress has four types of components that require periodic updates:

  • WordPress core
  • 3rd-party plugins
  • Themes
  • Translations

Under current versions, it is possible to enable auto-updates for all of these component types. However, every update carries a minimal risk to break something. That is why WordPress gives us the ability to control its update patterns and even prohibit updates completely (not that I recommend this).

The good thing about the settings I am about to show you is that they override any settings within the control panel. As long as you prevent your customers to touch wp-config.php, you can be sure you will be in full control of updates.

Here are the possibilities.

1. Disable All Automatic Updates

/* Disable all automated updates                      */
/* Including core, plugins, themes and translations   */
define( 'AUTOMATIC_UPDATER_DISABLED', true );

2. Control Core Automatic Updates

/* Enable ALL core updates, including minor and major: */
define( 'WP_AUTO_UPDATE_CORE', true );
/* Disable ALL core updates, including minor and major: */
define( 'WP_AUTO_UPDATE_CORE', false);
/* Enable minor updates ONLY */
define( 'WP_AUTO_UPDATE_CORE', 'minor' );

3. Disable Automatic Updates for bundled themes (e.g. Twenty-Twenty) and plugins (Hello Dolly, Akismet)

/* Disable updates for bundled themes and plugins */
define( 'CORE_UPGRADE_SKIP_NEW_BUNDLED', true );

4. Disable plugin and theme installations and updates altogether:

define( 'DISALLOW_FILE_MODS', true );

Changing Resource Folder Locations

WordPress allows you to change the default locations for different parts of the CMS like plugins, uploads, themes, etc. This can help you free space on a partition or simplify migration when mounting extra storage. You can specify relocation to a different local folder (i.e. specify a relative path) or a different host (i.e. specify a URL).

Here are the possibilities:

1. Move everything:

define( 'WP_CONTENT_DIR', dirname(__FILE__) . '/blog/wp-content' );
- or -
define( 'WP_CONTENT_URL', 'http://example/blog/wp-content' );

2. Move plugins only:

define( 'WP_PLUGIN_DIR', dirname(__FILE__) . '/blog/wp-content/plugins' );
- or -
define( 'WP_PLUGIN_URL', 'http://example/blog/wp-content/plugins' );

3. Move languages folder only:

There are several ways to do this.

define( 'WP_LANG_DIR', WP_CONTENT_DIR . '/languages' );
define( 'WP_LANG_DIR', ABSPATH . WPINC . '/languages' );
define( 'LANGDIR', WPINC . '/languages' );
define( 'LANGDIR', 'wp-content/languages' );

4. Move user uploaded content only:

define( 'UPLOADS', 'blog/wp-content/uploads' );

This one is very important because the uploads folder holds all user-uploaded media files which could easily grow in size. To prevent disk shortage from hapenning, the system administrator may choose to keep the uploads in a different mounting share.

5. Move themes only:

Weirdly enough, it is not possible to relocate the themes folder alone; the theme URL is hardcoded as a sub-folder to wp-content.

You should be very careful with this functionality and perform thorough tests. While the WordPress core will likely have no issues, badly coded plugins and themes can cause the website to break.

Development Environments

This functionality is not intended to directly influence the behavior of WordPress. Instead, it is supposed to help theme, plugin developers put different kind of behavior based on the the state/intended use for a specific instance.

WordPress hosts and providers of WordPress development/staging environments could, for example, modify the behavior of their back-ends by quering the newly created wp_get_environment_type() function and hook into it if necessary. A query can be used for something as simple as indicating the state of the instance in the GUI dashboard, or for more complicated changes to the control structure like automatically disabling caching/CDN.

When creating a new website copy through some kind of automated procedure, a webhost could add the state of that new instance in wp-config.php

This variable presently supports 4 states: local, development, staging, and production (production is the default one).

define( 'WP_ENVIRONMENT_TYPE', 'development' );
define( 'WP_ENVIRONMENT_TYPE', 'staging' );
define( 'WP_ENVIRONMENT_TYPE', 'local' );
define( 'WP_ENVIRONMENT_TYPE', 'production' );  // Default

As I am writing this, there is no difference in the way the instance behaves except one thing: when development state is selected, the instance will automatically enable WP_DEBUG as well, even if is not specifically activated.

Debugging and Troubleshooting PHP Code

WordPress has a debug mode which can be used to troubleshoot errors by either logging them, displaying them on screen, or both. For most shared hosts that lack shell access, logging errors to a file is impractical, because that file can’t be watched as it updates in real time. That is why it makes sense to enable on-screen bug reporting.

In order to reproduce errors when visiting specific pages, we also need to turn off the automated WordPress function that prevents pages with PHP errors from loading. These are the four settings that you need to activate to enable full debug mode.

// Disable Fatal Error Handle
define( 'WP_DISABLE_FATAL_ERROR_HANDLER', true );

// Enable WP_DEBUG
define( 'WP_DEBUG', true );

// Show errors on screen
define( 'WP_DEBUG_DISPLAY', true );

// Write errors to a log file located at wp-content/debug.log
define( 'WP_DEBUG_LOG', true );

Bear in mind that WP_DEBUG limits the performance of WP_DEBUG_LOG or WP_DEBUG_DISPLAY. WP_DEBUG needs to be enabled for any of the other two to work. Don’t forget to disable them once you are done fixing the website! While WP_DEBUG_DISPLAY is active, an error message or a warning may leak sensitive data about the website.

Debugging and Troubleshooting CSS and JS Code

If you are responsible for day to day maintenance of a WordPress website, knowing how to troubleshoot PHP errors will probably be enough for you. However, if you are also working on theme and/or plugin design, you might need a little more help.

By default, WordPress will serve minified versions of its common CSS and JS libraries, and will also concatenate scripts to improve loading speed a little. Both of these measures make the job of a troubleshooter much more difficult. Thankfully, WordPress is giving us two functions to fix this.

SCRIPT_DEBUG will serve the non-minified versions of all JS and CSS file for the front end (wp-includes/js, wp-includes/css) and back-end (wp-admin/js, wp-admin/css) to make it easier for developers and testers to view the code from the browser console.

/* 
* Uses non-minified versions of wp-includes/js, wp-includes/css,
* wp-admin/js, and wp-admin/css instead of .min.css and .min.js
*/
define( 'SCRIPT_DEBUG', true );
/*
* Does not concatenate scripts to improve
* browsing and back/forth scrolling
*/
define( 'CONCATENATE_SCRIPTS', false );

Sidenote: If you are using a modern web server that supports HTTP/2 or later, it actually makes sense to keep CONCATENATE_SCRIPTS permanently disabled. Resource concatenation was a great way to improve site loading speed in olden times when browsers were allowed to open a limited number of sessions to the web servers, but these days concatenation might actually do more harm than good.

For one thing, concatenating different sets of scripts for different web pages degrades caching performance. It also increases the overall amount of traffic (and wastes bandwidth). And last but not least, variations in concatenation sequences might make some websites inoperable (if a specific bit of JS code gets loaded before a related bit of JS code is forcefully located to the back of the concatenated file).

For these reasons, most modern websites have all but given up concatenating scripts. So you might just as well test to see any improvement.

Cookie Control

WordPress gives you the ability to limit the scope of cookies created by the platform. You can restrict them only to the apex domain, a subdomain, or make them accessible for the apex domain and all subdomain.

Unless you have a very specific use case — like content posted on a sub-domain that is visible to users logged in with a cookie on the main site — it is best to restrict cookies to be valid only on the domain or sub-domain where the WordPress instance is located. Among other things, this prevents the server from sending cookies back when serving requests for static resources fetched via CDN, which saves bandwidth.

define( 'COOKIE_DOMAIN', '.domain.com' ); // Domain and all subdomains
define( 'COOKIE_DOMAIN', 'domain.com' ); // only root domain
define( 'COOKIE_DOMAIN', 'www.domain.com' ); // only subdomain

This is how to store the site name and path inside the cookies.

define( 'COOKIEPATH', $_SERVER['HTTP_HOST'] . '/' ); // You should set this explicitely.
define( 'SITECOOKIEPATH', $_SERVER['HTTP_HOST'] . '/' ); // You should set this explicitely.
define( 'ADMIN_COOKIE_PATH', SITECOOKIEPATH . 'wp-admin' );
define( 'PLUGINS_COOKIE_PATH', preg_replace( '|https?://[^/]+|i', '', WP_PLUGIN_URL ) );

And this is how you can control the naming of your cookies:

define( 'USER_COOKIE', 'wordpressuser_' . COOKIEHASH );
define( 'PASS_COOKIE', 'wordpresspass_' . COOKIEHASH );
define( 'AUTH_COOKIE', 'wordpress_' . COOKIEHASH );
define( 'SECURE_AUTH_COOKIE', 'wordpress_sec_' . COOKIEHASH );
define( 'LOGGED_IN_COOKIE', 'wordpress_logged_in_' . COOKIEHASH );
define( 'RECOVERY_MODE_COOKIE', 'wordpress_rec_' . COOKIEHASH );

Enable Web Access for Local WordPress Instance

Local development is becoming very popular, as it allows the user to work on a WordPress instance that runs on their PC and does not require hosting or even internet access.

However, even during local development it might be necessary to allow that instance to connect to the web to download updates or for testing purposes. WordPress can use a proxy to connect to the Internet, and this is how you can instruct it to access that proxy.

define( 'WP_PROXY_HOST', '192.168.0.1' );
define( 'WP_PROXY_PORT', '8000' );
define( 'WP_PROXY_USERNAME', '' );
define( 'WP_PROXY_PASSWORD', '' );
define( 'WP_PROXY_BYPASS_HOSTS', 'localhost') ;

Database Repair & Maintenance

WordPress has an automated function for repairing and optimizing database tables. You can open it via /wp-admin/maint/repair.php but in order to enable it, you need to add the following setting:

define( 'WP_ALLOW_REPAIR', 'true' );

This is not a forensic tool and you can’t expect miracles from it. It relies entirely on the repair functionality built into MySQL. But unless something really bad has happened, it is very likely it can repair a broken table or two.

Once done, you should either switch WP_ALLOW_REPAIR to false, or delete the statement from the config file altogether.

3rd Party Plugin Functions

You should know that 3rd party plugins might also choose to take advantage of wp-config.php to store their own configuration data.

If you are using Jetpack or Akismet, you might add your WordPress.com API key to wp-config.php:

define( 'WPCOM_API_KEY', 'YourKeyHere' );

Under Apache, the popular object cache plugin Redis uses wp-config to specify things like host/port combination, database number, key prefix and access password.

define( 'WP_REDIS_HOST', '127.0.0.1' );
define( 'WP_REDIS_PORT', 6379 );
define( 'WP_REDIS_PASSWORD', 'secret' );
define( 'WP_REDIS_TIMEOUT', 1 );
define( 'WP_REDIS_READ_TIMEOUT', 1 );

Full list of Redis settings.

Keeping wp-config.php Secure

As a reward for getting to the very end, I will share with you one final hardening trick.

On most webhosts, you can move wp-config.php outside the public_html directory. PHP will still be able to read the file from there, but the web server will be incapable of accessing it.

In the very rare, but not unheard of situation where the PHP handler goes bust due to misconfiguration, the server will push the contents of regular PHP files as text on the screen instead of passing them to PHP for execution.

Moving wp-config.php away from the reach of the web server guarantees that the important information (database name, db user name and db password) stays protected.

At present, WPX does not allow wp-config.php to be moved away from its original location. Instead, they have implemented a server rule that blocks access to it via .htaccess.

Go ahead and try that yourself: you will reach a WPX error page.

In case your own webhost does not offer such protection by default, you can block access to wp-config.php by adding the following lines to the .htaccess file in your root site folder:

<files wp-config.php>
  order allow,deny
  deny from all
</files>

Recap

These are only some of the possible constants you can use, and the ones that I find meaningful (there are many others I’ve left out as too obscure or inessential). Strangely, a full list of WordPress define statements doesn’t seem to exist. I will try to keep this list current and will come back to update the article with new or improved information whenever I learn something new. Bookmark it and share it among your friends and colleagues, and if you know other useful settings, do share them in the comments below. Thanks!

Leave a Reply

Your email address will not be published.