When a site drops to a bare page reading Error establishing a database connection, WordPress has already given up: it never reached the point where it could load a theme, so the screen you see is the last thing PHP could print. The quickest way to fix a WordPress database connection error is to stop reading that screen and start reading the four constants that produced it. Below are the three realistic routes to those constants, when each one fits, and a full build of the one that hands you the real MySQL error instead of a generic failure.
Three routes to the same four constants, and only one of them tells you why
Every fix ends in the same place: a working DB_NAME, DB_USER, DB_PASSWORD and DB_HOST in wp-config.php. What separates the approaches is how much information you get on the way there.
Route 1: editing wp-config.php through the hosting file manager
This is the default on shared hosting. You open the file manager, download wp-config.php, compare the four constants against the database section of the control panel, and upload again. It fits when you have no SSH access and no WP-CLI, and it is genuinely the right call when you already know the password was rotated and the config file was never updated.
The weakness is that it is guesswork. A wrong password, a wrong host, a missing database and a crashed MySQL server all produce the same screen, so you end up editing four values hoping one of them was the culprit. Every save is also a chance to introduce a PHP syntax error on top of the original fault.
Route 2: WP-CLI and mysqladmin from a shell
With SSH access you can read the constants without touching the file, ask the MySQL client whether the host answers at all, and repair tables from the same prompt. It fits any host that gives you a terminal, and it is the only route that produces output you can paste into a support ticket.
Its weakness is that it tests the credentials you type, not the ones WordPress is using. Mistype the password at the prompt and you learn nothing about the site.
Route 3: a diagnostic mu-plugin that prints the raw MySQL error
A must-use plugin loads before regular plugins and can register its own WP-CLI command. That command opens a raw mysqli connection using the exact constants from wp-config.php and prints the error code MySQL returns. It fits when you have file access but no shell, and it is the only route that separates access denied from unknown database from can't connect through socket.
Recommended combination: read the constants with WP-CLI, confirm the host with mysqladmin, then run the probe from route 3 to capture the exact error. The rest of this article builds that in order.
Read the four constants before you change a single character
Open wp-config.php and check the database block character by character. The most common causes of this error are a password with a trailing space pasted from the control panel, a database name missing its host prefix, and a DB_HOST that says localhost when the host requires 127.0.0.1 or a socket path.
<?php /** The database name exactly as the control panel spells it, prefix included. */ define( 'DB_NAME', 'example_wp' ); /** The database user, not the control panel login. */ define( 'DB_USER', 'example_wpuser' ); /** The generated password, copied without a trailing space. */ define( 'DB_PASSWORD', 'kR7p...generated...' ); /** 'localhost' uses the socket; '127.0.0.1' forces TCP. They are not interchangeable. */ define( 'DB_HOST', 'localhost' ); /* Non-standard port or socket: use one of these instead. */ // define( 'DB_HOST', '127.0.0.1:3307' ); // define( 'DB_HOST', 'localhost:/var/run/mysqld/mysqld.sock' ); Reading the file is only half the job, because a host can inject constants from elsewhere or a stray wp-config.php can sit one directory up. The wp config commands parse the file without booting WordPress, so they still answer when the front end is dead.
# Read the values WordPress is actually loading, no editor required. wp config get DB_NAME wp config get DB_USER wp config get DB_HOST # Dump everything the file defines, in case a host injects constants elsewhere. wp config get --json The three database constants read straight from wp-config.php without opening the file (test site: WordPress 7.1.3, PHP 8.2):
wordpress wp localhost:/run/mysqld/mysqld.sock If wp config get returns a value that differs from what the control panel shows, the file is not the one you think it is. If it returns nothing at all, the constant is undefined and WordPress is falling back to a default that will never work.
Test the host with mysqladmin before you blame WordPress
mysqladmin is the decisive test because it needs no WordPress, no PHP and no theme. It answers one question: does this host, port and credential combination reach a live MySQL server? Run it with the same values you just read out of the config file.
# Does MySQL answer on the TCP port? mysqladmin --host=127.0.0.1 --port=3306 --user=example_wpuser --password ping # Same test over the socket that 'localhost' resolves to. mysqladmin --socket=/var/run/mysqld/mysqld.sock --user=example_wpuser --password ping Read the reply literally:
- mysqld is alive — the server is up and the credentials are accepted. The fault is inside WordPress: a stale
wp-config.php, an object cache pointing at a dead Redis instance, or a corrupted table. - Access denied for user — the password or username is wrong, or the account is not granted rights on that database from that host.
- Can't connect to MySQL server … (111) — nothing is listening. The database service is down or a firewall blocks the port.
- Unknown MySQL server host —
DB_HOSTis a typo or a hostname that no longer resolves.
Omitting --password entirely makes the client prompt for it, which keeps the secret out of your shell history. That matters on a shared server where other users can read the process list.
Build the probe that prints the MySQL error WordPress hides
WordPress deliberately replaces the driver error with a friendly page, so the code that would tell you what happened is thrown away. This must-use plugin registers a wp dbdoctor command that opens its own connection with the constants from wp-config.php and prints whatever MySQL says. Because it opens a second connection, it reports the failure even while WordPress itself is loading normally.
<?php /** * Plugin Name: Database Connection Doctor * Description: Probes the credentials in wp-config.php and prints the raw MySQL error. */ class WPTuts_DB_Doctor { /** * Open a raw mysqli connection using the constants from wp-config.php. * * ## OPTIONS * * [--db-host=<host>] * [--db-user=<user>] * [--db-password=<password>] * [--db-name=<name>] * * ## EXAMPLES * * wp dbdoctor probe * wp dbdoctor probe --db-name=missing_db */ public function probe( $args, $assoc_args ) { $host = isset( $assoc_args['db-host'] ) ? $assoc_args['db-host'] : DB_HOST; $user = isset( $assoc_args['db-user'] ) ? $assoc_args['db-user'] : DB_USER; $password = isset( $assoc_args['db-password'] ) ? $assoc_args['db-password'] : DB_PASSWORD; $name = isset( $assoc_args['db-name'] ) ? $assoc_args['db-name'] : DB_NAME; $target = $this->parse_host( $host ); WP_CLI::log( 'DB_HOST : ' . $host ); WP_CLI::log( 'Host : ' . $target['host'] ); WP_CLI::log( 'Port/socket : ' . $target['label'] ); WP_CLI::log( 'DB_USER : ' . $user ); WP_CLI::log( 'DB_NAME : ' . $name ); WP_CLI::log( '' ); mysqli_report( MYSQLI_REPORT_OFF ); try { $db = new mysqli( $target['host'], $user, $password, $name, $target['port'], $target['socket'] ); } catch ( Throwable $e ) { WP_CLI::error( 'Connection failed: ' . $e->getMessage(), false ); return; } if ( $db->connect_errno ) { WP_CLI::error( sprintf( 'Connection failed (%d): %s', $db->connect_errno, $db->connect_error ), false ); return; } WP_CLI::success( 'Connected to ' . $name . ' via ' . $db->host_info . ' (server ' . $db->server_info . ')' ); $db->close(); } private function parse_host( $host ) { $port = null; $socket = null; $label = 'default'; if ( false !== strpos( $host, ':' ) ) { list( $host, $suffix ) = explode( ':', $host, 2 ); if ( ctype_digit( $suffix ) ) { $port = (int) $suffix; $label = 'port ' . $port; } else { $socket = $suffix; $label = 'socket ' . $socket; } } if ( '' === $host ) { $host = 'localhost'; } return array( 'host' => $host, 'port' => $port, 'socket' => $socket, 'label' => $label, ); } } if ( defined( 'WP_CLI' ) && WP_CLI ) { WP_CLI::add_command( 'dbdoctor', 'WPTuts_DB_Doctor' ); } Save it as wp-content/mu-plugins/db-connection-doctor.php. The parse_host() helper matters more than it looks: WordPress accepts host:port and host:/path/to/socket in DB_HOST, and a probe that passes the whole string to mysqli would fail for the wrong reason.
# Happy path: confirms the constants, the socket and the server version. wp dbdoctor probe # Force a specific failure to see the code WordPress hides. wp dbdoctor probe --db-name=definitely_not_a_database wp dbdoctor probe --db-user=example_wpuser --db-password=wrong The probe connects with the constants WordPress is using and reports the socket, host and server version (test site: WordPress 7.1.3, PHP 8.2):
DB_HOST : localhost:/run/mysqld/mysqld.sock Host : localhost Port/socket : socket /run/mysqld/mysqld.sock DB_USER : wp DB_NAME : wordpress Success: Connected to wordpress via Localhost via UNIX socket (server 11.8.6-MariaDB-0+deb13u1 from Debian) Forcing a bad database name surfaces the raw MySQL error code that the front-end screen hides (test site: WordPress 7.1.3, PHP 8.2):
DB_HOST : localhost:/run/mysqld/mysqld.sock Host : localhost Port/socket : socket /run/mysqld/mysqld.sock DB_USER : wp DB_NAME : definitely_not_a_database [11-Nov-2026 01:28:12 UTC] PHP Warning: mysqli::__construct(): (HY000/1044): Access denied for user 'wp'@'localhost' to database 'definitely_not_a_database' in /var/www/html/wp-content/mu-plugins/db-connection-doctor.php on line 42 Warning: mysqli::__construct(): (HY000/1044): Access denied for user 'wp'@'localhost' to database 'definitely_not_a_database' in /var/www/html/wp-content/mu-plugins/db-connection-doctor.php on line 42 Error: Connection failed (1044): Access denied for user 'wp'@'localhost' to database 'definitely_not_a_database' An error 1045 means the credentials are wrong. An error 1049 means the credentials are fine but the database name does not exist. An error 2002 means the socket path is wrong. Each one points at a different line of wp-config.php, which is exactly the distinction the front-end screen withholds.
If
wpitself refuses to start, the probe cannot run either — WordPress has to boot before a mu-plugin command exists. That is the moment to fall back tomysqladmin, which needs nothing but a shell.
Turn on WP_DEBUG_LOG so the failure leaves a written record
Once you can reach the dashboard again, switch on logging before you start changing anything. A written record turns a one-off glitch into a pattern you can act on, and it catches the intermittent cases where the site loads fine every time you look at it.
define( 'WP_DEBUG', true ); define( 'WP_DEBUG_LOG', true ); // writes to wp-content/debug.log define( 'WP_DEBUG_DISPLAY', false ); // keeps errors out of the page @ini_set( 'display_errors', 0 ); Add those lines above the /* That's all, stop editing! */ marker, reload the broken page, then read wp-content/debug.log. The empty result is as informative as a full one: if the log stays at zero bytes after a failed request, the request died before WordPress could log anything, which points squarely at the connection layer rather than at a plugin. If the log fills with a fatal from a single plugin, you have a different problem wearing the same costume.
Turn WP_DEBUG_DISPLAY back off when you are finished. Leaving it on in production prints file paths and query fragments onto public pages.
Repair a corrupted table with wp db check and wp db repair
If mysqladmin ping answers and the constants are correct, the remaining suspect is storage. A table marked as crashed makes the connection handshake fail in ways that look identical to bad credentials. Both commands need a working connection, which is why they come last.
# Report the status of every table in the database. wp db check # Ask MySQL to repair anything it can. wp db repair # Narrow it down to one table when the list is long. wp db query "CHECK TABLE wp_posts" Expect a caveat on modern installs: wp db repair only does real work on MyISAM and Aria tables. InnoDB reports The storage engine for the table doesn't support repair, which is not a failure message — it means the engine has no repair path and you should restore the affected table from a backup, or export and re-import the database with wp db export and wp db import. Running the repair anyway is still worth ten seconds, because it confirms which engine each table uses.
When the credentials are right and the connection still drops
A site that connects and then fails under load is a different fault. Check three things in order. First, whether the host caps concurrent connections: SHOW STATUS LIKE 'Threads_connected' against max_connections shows whether you are hitting a ceiling. Second, whether DB_HOST should be 127.0.0.1 rather than localhost — the first forces TCP, the second uses a socket, and hosts that run MySQL in a separate container only expose one of them. Third, whether a persistent object cache is still pointing at a Redis or Memcached instance that has been removed; the drop-in in wp-content/object-cache.php fails before WordPress ever reaches MySQL.
Once the site is stable, the follow-up worth doing is adding mysqladmin --host=127.0.0.1 --user=... --password ping to whatever already watches your uptime. It answers in under a second, needs no WordPress, and tells you whether the next outage is a database problem or an application one before you open a ticket.
Originally published on WP Tuts Hub.

