Skip to content

WordPress

wp-config.php builder

A whole wp-config.php, not just salts, with the constants people forget until something goes wrong.

Constants
wp-config.php
<?php
/**
 * WordPress configuration.
 *
 * Generated with https://forhadkhan.com/tools/wp-config-builder/
 * Salts were generated in your browser and never transmitted.
 */

// ** Database settings ** //
define( 'DB_NAME', 'database_name_here' );
define( 'DB_USER', 'username_here' );
define( 'DB_PASSWORD', 'password_here' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
define( 'DB_COLLATE', '' );

// ** Authentication keys and salts ** //
define( 'AUTH_KEY', 'put your unique phrase here' );
define( 'SECURE_AUTH_KEY', 'put your unique phrase here' );
define( 'LOGGED_IN_KEY', 'put your unique phrase here' );
define( 'NONCE_KEY', 'put your unique phrase here' );
define( 'AUTH_SALT', 'put your unique phrase here' );
define( 'SECURE_AUTH_SALT', 'put your unique phrase here' );
define( 'LOGGED_IN_SALT', 'put your unique phrase here' );
define( 'NONCE_SALT', 'put your unique phrase here' );

// ** Table prefix ** //
$table_prefix = 'wp_';

// ** Environment ** //
define( 'WP_ENVIRONMENT_TYPE', 'production' );

// ** Debugging ** //
define( 'WP_DEBUG', false );

// ** Security ** //
define( 'DISALLOW_FILE_EDIT', true );
define( 'FORCE_SSL_ADMIN', true );

// ** Content ** //
define( 'WP_POST_REVISIONS', 10 );
define( 'AUTOSAVE_INTERVAL', 300 );
define( 'EMPTY_TRASH_DAYS', 30 );

// ** Performance ** //
define( 'WP_MEMORY_LIMIT', '256M' );
define( 'WP_MAX_MEMORY_LIMIT', '512M' );

// ** Absolute path and bootstrap ** //
if ( ! defined( 'ABSPATH' ) ) {
	define( 'ABSPATH', __DIR__ . '/' );
}

require_once ABSPATH . 'wp-settings.php';

Salts are generated with crypto.getRandomValues() inside your browser. They are never sent anywhere, and a fresh set is created every time you press Regenerate.

WordPress.org’s salt service does one job and does it well. This does the rest of the file: the debug block that doesn’t leak errors to visitors, the security constants that ought to be on by default, and the memory limits you tend to find out about halfway through an import.

About the salts

The eight keys and salts sign your authentication cookies. They want to be 64 random characters, different on every site, and never copied between environments.

These come from crypto.getRandomValues() running in your browser, the same CSPRNG the browser uses for TLS. Nothing is requested from a server, so no third party has ever seen them. Hit Regenerate salts and you get a fresh set immediately.

Please don’t ask an AI assistant for these. A language model isn’t a CSPRNG and has no obligation to produce output free of statistical structure, even though what comes back looks convincingly like a random string. It’s the one part of this file where “close enough” isn’t.

Two details in the implementation that matter:

Mapping a random byte with byte % 93 would quietly favour the first few characters of the alphabet, because 256 doesn’t divide evenly by 93. This uses rejection sampling instead, so every character is equally likely.

The character set also excludes ' and \. Both need escaping inside a single-quoted PHP string, and an unescaped one truncates the salt silently, with no error anywhere. Leaving them out costs about two bits of entropy across 64 characters and removes a whole category of copy-paste breakage.

One warning: changing the salts logs everyone out, you included. That’s the intended way to force a global logout after a compromise, but it will surprise a client if nobody told them.

The constants worth knowing

DISALLOW_FILE_EDIT

Takes the plugin and theme code editors out of wp-admin. If someone gets hold of an administrator session, that editor is a direct path from “logged in” to “running arbitrary PHP”. Turning it off costs you nothing, because editing production files through a browser textarea was never a good idea in the first place.

DISALLOW_FILE_MODS

The stronger version: no plugin or theme installation and no updates from the dashboard at all. Right when deploys are the only route code takes to the server. Wrong when the site owner is expected to run their own updates, and it will confuse them badly, because the buttons simply aren’t there any more and nothing explains why.

WP_DEBUG and friends

PHP
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );     // wp-content/debug.log
define( 'WP_DEBUG_DISPLAY', false ); // never render errors to visitors
@ini_set( 'display_errors', 0 );

The pairing is the important bit. WP_DEBUG on its own prints warnings straight into the page, which on a live site leaks file paths and occasionally fragments of queries. Log them instead. That @ini_set() line is belt and braces against a host that overrides display_errors after WordPress has set it, which some do.

SCRIPT_DEBUG loads unminified core JS and CSS. Useful when you’re debugging the block editor and pointless the rest of the time.

WP_ENVIRONMENT_TYPE

More than a label. Core reads it and so do plugins: it gates the admin environment notice, changes some update behaviour, and gives your own code something dependable to branch on through wp_get_environment_type(). Set it everywhere, production included.

FORCE_SSL_ADMIN

Forces login and wp-admin over HTTPS. Harmless once you have a certificate, and the certificate is free. Sites behind a proxy or load balancer that terminates TLS may also need the forwarded header trusted:

PHP
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
	$_SERVER['HTTPS'] = 'on';
}

Only add that when you control the proxy. A client can otherwise spoof the header and you’ve handed them a way around your own rule.

Memory limits

WP_MEMORY_LIMIT covers the front end, WP_MAX_MEMORY_LIMIT applies to admin and cron, which need more. Neither can go above what PHP itself allows, so if raising them changes nothing at all then the ceiling is in php.ini and your host controls it.

DISABLE_WP_CRON

WP-Cron runs on page visits, which means a quiet site runs scheduled jobs late and a busy one runs the check constantly. Disabling it is correct only once you’ve replaced it:

*/5 * * * * curl -s https://example.com/wp-cron.php?doing_wp_cron > /dev/null

Set the constant without the cron entry and scheduled posts, plugin updates and email queues all stop, silently, with nothing in the log to tell you.

Where the file goes

wp-config.php lives in the WordPress root, though it can also sit one directory above, which puts it outside the web root entirely. WordPress checks the parent automatically and that’s the better option wherever your host allows it.

Permissions of 440 or 400 where the setup permits. The file has your database password in it and doesn’t need to be world-readable.

A few smaller things: you can change salts on a live site whenever you like, as long as you warn whoever is logged in. Leave DB_COLLATE empty and let MySQL take the collation from the database, because setting it wrong here causes subtle sorting bugs that are miserable to track down. And the database password has to be in this file somewhere, since WordPress has nowhere else to read it from, so keep the file out of git and use a gitignored wp-config-local.php that you require conditionally if you need the rest of it version-controlled.

Published