Troubleshooting

Error Establishing a Database Connection: What WordPress Is Actually Telling You

The message means PHP ran, wp-config.php was read, and the connection to MySQL failed. Nothing more. Here is how to tell a wrong password from a downed server from a host that ran out of connections, and what to do about each.

Error Establishing a Database Connection: What WordPress Is Actually Telling You

A site that was fine at midnight is one heading in a bare white box at seven in the morning. No header, no menu, nothing from the theme. Just “Error establishing a database connection” and empty space underneath it.

That page is not a broken site. It is WordPress reporting one specific thing: PHP ran, wp-config.php was read, the four database constants were handed to the MySQL driver, and the driver came back with nothing. The theme never loaded. No plugin ever ran. If wp-config.php were missing you would be looking at the setup screen instead, so the file exists and is valid PHP.

The useful part is how much that rules out. Themes, plugins, .htaccess, file permissions in wp-content, a bad update: all of it is off the table before you start. What is left is a handshake between two processes, and there are about four reasons it fails.

Diagram showing that a WordPress database connection error confirms PHP ran and wp-config.php was parsed, while the theme and all plugins never loaded at all

Where the message comes from

WordPress builds the text in wpdb::db_connect(). When mysqli_real_connect() fails, core checks whether a file exists at wp-content/db-error.php and loads that instead if it does. Otherwise it assembles the default message and stores it on $wpdb->error. A moment later wp_set_wpdb_vars() sees a non-empty error and calls dead_db(), and that function is what actually ends the request.

What dead_db() prints depends on where you are standing. On the front end you get the terse version: the heading and nothing else. Inside /wp-admin/, where the WP_ADMIN constant is defined, you get the verbose version, which names wp-config.php, prints the literal value of DB_HOST in a code tag, and asks three questions: correct username and password, correct hostname, database server running.

That difference is worth using. Load the admin and read the host value core prints back at you, because it is the exact string from your config file rather than an interpretation of it. If it says localhost and your host’s documentation names a separate database server, you have already found the problem.

There is a second, similar message worth telling apart: “Error reconnecting to the database”. That one comes from wpdb::check_connection(), which fires when a connection that already existed goes away in the middle of a request. Core retries five times with a one second pause between attempts before giving up, and its wording asks whether the server is running and whether it is under particularly heavy load. Seeing “reconnecting” rather than “establishing” means the database answered at the start of the request and then stopped answering, which points at a server that restarted or a connection that was killed, not at a wrong password.

Connected, but the database is unusable

A different message, “One or more database tables are unavailable. The database may need to be repaired”, gets lumped in with this error and should not be. It comes from is_blog_installed(), which runs long after the connection succeeded. To reach that code the driver connected, authenticated, and selected the database. Credentials are right. The server is up. What failed is a read: the siteurl option could not be fetched from the options table, while a DESCRIBE on other core tables still returns rows.

That is the message that genuinely points at repair, and core links straight to maint/repair.php from it. If you are seeing the connection error instead, repairing tables will do nothing, because nothing has managed to open a table in the first place.

And if the page is blank rather than carrying any message at all, this is the wrong article. That is a different failure that also shows nothing useful, and the first move there is turning on debug logging so PHP writes the fatal somewhere you can read it.

Comparison of three similar WordPress database errors, the core function that raises each one, what it means and which one actually calls for a table repair

The four causes, ranked

Wrong credentials in wp-config.php

Four constants control the connection, and they sit near the top of wp-config.php.

define( 'DB_NAME', 'example_wp' );
define( 'DB_USER', 'example_wpuser' );
define( 'DB_PASSWORD', 'the_actual_password' );
define( 'DB_HOST', 'localhost' );

Three of these are boring. DB_HOST is the one that catches people, because it is not always localhost. WordPress parses that string itself, in wpdb::parse_db_host(), and accepts several shapes: a plain hostname, a hostname with a port such as db1.example.net:3307, an IPv6 address, or a Unix socket path written as localhost:/var/run/mysqld/mysqld.sock. The parser peels the socket off at the first :/ it finds. Managed hosts frequently hand you a dedicated database hostname, some cPanel and Plesk servers want the socket path, and container setups usually want a service name.

The difference between localhost and 127.0.0.1 is not cosmetic either. With localhost, the MySQL client library connects over a Unix socket. With 127.0.0.1 it opens a TCP connection to the loopback interface. A site that worked on the old server can fail on the new one purely because the socket file lives somewhere else, and swapping one for the other is a legitimate thirty-second test.

Credentials go wrong at three specific moments: a migration to a new host, an account or server rename, and a password reset done in the control panel without the config file being updated afterwards. If the error started within minutes of one of those, look here first. A user can also authenticate correctly and still hold no grant on the database it is trying to open, which produces a different MySQL error number. The numbers further down are worth reading for exactly that reason.

Table of DB_HOST values WordPress accepts in wp-config.php, including a plain hostname, a hostname with a port, a Unix socket path and a container service name

The database server is down or refusing connections

On shared hosting this is usually a watchdog killing MySQL for crossing a memory limit, with something else restarting it a few minutes later. On a VPS you own, it is often the kernel’s out-of-memory killer choosing the largest process on the box, which is nearly always the database. On managed platforms it is occasionally a real, announced incident.

The control panel normally shows the service state directly, and most hosts run a status page. If you have shell access on your own server, the MySQL error log, commonly at /var/log/mysql/error.log on Debian and Ubuntu, records both the shutdown and the reason for it. That is worth reading before you assume it was random.

Too many connections

Every PHP process that serves a WordPress request holds one database connection for the life of that request. When concurrent requests exceed the server’s max_connections, or your account’s max_user_connections, new connections are refused and WordPress prints the same error it prints for a wrong password. Then traffic dips, connections free up, and the site comes back on its own.

This is the most confusing of the four, because the evidence disappears before you go looking for it. It is also the usual explanation behind the version of this problem people describe as “it happens a few times a day”. If you can run queries at all, three statements tell you most of what you need.

SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';
SHOW VARIABLES LIKE 'max_connections';

Max_used_connections is the peak since the server started or since the status counters were last flushed. If it is sitting at or just under max_connections, you are not debugging a config file. You are debugging load.

An actually corrupted table

This is real, and it is much rarer than search results suggest. It follows unclean shutdowns, full disks, and hardware faults. MyISAM tables are the ones that crash this way and stay crashed. InnoDB, the default storage engine on MySQL 5.5 and later and on MariaDB, normally replays its redo log at startup and recovers without anyone noticing.

The signature is distinctive. The connection succeeds, the site half works, and one specific query fails with text like “Table ‘./example_wp/wp_options’ is marked as crashed and should be repaired”. If you are seeing the plain connection error with no table named anywhere, this is not your cause.

Working out which one it is

All four causes produce the same “Error establishing a database connection” page, so work through this in order. Each step eliminates a whole category.

First, load /wp-admin/. If it shows the same connection error, now with the DB_HOST value printed, the failure is at the connection layer and the next two steps apply. If it shows “One or more database tables are unavailable”, the connection is fine and you can skip straight to the repair section. If the admin loads normally while the front end does not, this is not a database problem at all, and the thing to look at is a page cache or a CDN sitting in front of the site.

Second, test the credentials outside WordPress. Copy the four values out of wp-config.php into a temporary file in the site root, give it an unguessable name, load it once, and delete it.

<?php
// db-test-7f2c9a.php - temporary. Delete this file when you are done.

$db_host   = 'localhost';
$db_user   = 'example_wpuser';
$db_pass   = 'the_actual_password';
$db_name   = 'example_wp';
$db_socket = null; // e.g. 'https://cdn.wp-image-editor.com/var/run/mysqld/mysqld.sock' if DB_HOST names a socket

mysqli_report( MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT );

try {
    $link = new mysqli( $db_host, $db_user, $db_pass, $db_name, null, $db_socket );
    echo 'Connected. Server version: ' . $link->server_info;
    $link->close();
} catch ( mysqli_sql_exception $e ) {
    echo 'Failed. MySQL error ' . $e->getCode() . ': ' . $e->getMessage();
}

Two notes on that file. The mysqli_report() call is there because PHP 8.1 changed the default error mode to MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT, so setting it explicitly makes the script behave the same way on older and newer PHP. And if your DB_HOST contains a colon followed by a path, that is a socket. The plain mysqli constructor will not split it the way WordPress does, so put the path in $db_socket and leave the host as localhost.

The error text this prints is the whole point, and the number in front of it tells you which cause you have.

  • 1045 Access denied for user – the username or password is wrong. Reset the password in the control panel and update DB_PASSWORD.
  • 1044 Access denied for user to database – the user is real and the password is right, but it holds no grant on that database. Common after a migration where the user was recreated and never reattached.
  • 1049 Unknown databaseDB_NAME does not exist on this server. Often a naming difference between the old host and the new one.
  • 2002 Can't connect through socket – the socket path is not there, or the server is not running.
  • 2003 Can't connect to MySQL server – nothing is listening at that host and port. Wrong hostname, wrong port, or a downed service.
  • 1040 Too many connections or 1203 max_user_connections – a resource ceiling, not a configuration mistake.

Delete the test file the moment you have the answer. It echoes a username and MySQL internals to anyone who loads the URL.

If you have shell access, skip the file entirely. WP-CLI loads the same wp-config.php WordPress does, so a successful query proves the credentials in that file work right now.

wp config get DB_HOST
wp option get siteurl
wp db check
wp db query "SELECT COUNT(*) FROM wp_options;"

wp option get siteurl goes through WordPress’s own database layer and needs no external binary, which makes it the most portable of the four. wp db check shells out to the mysqlcheck utility with --check, and wp db query runs the mysql client; shared hosts do not always install either binary. Adjust the table prefix in that last line if yours is not wp_, and avoid printing DB_PASSWORD to a terminal you do not control, since the host value is the one you usually need to see.

Third, check the host. Control panel service status, status page, recent incidents. If the credentials test cleanly from the same server and the site still will not connect, you are past the point where anything in your config file is going to help.

Working it out is mostly a matter of reading the second message, the one with the number in it, and knowing what that number means.

Paste it below and the decoder names the cause: 1045 is credentials, 1049 is a database that is not there, 2002 is a socket, 1040 is a server at its connection limit and nothing you did wrong. The second half checks the four values from wp-config.php for the faults that survive a careful read, a trailing space, a password with a dollar sign in double quotes, a host written in a form MySQL does not accept.

Database error decoder

A page that says "Error establishing a database connection" tells you nothing. The line underneath it, in wp-content/debug.log or in the browser, tells you almost everything, once you know the number. Paste that line here, and check the four wp-config lines while you are at it. Everything happens in this browser tab: nothing you type is stored, uploaded or sent anywhere.

1. The message
Nothing decoded yet
2. The four lines in wp-config.php

These five values stay in this tab. They are never stored, never remembered between visits and never sent anywhere, and the fields are exempt from autofill. Reload the page and they are gone. What stands in them now is an example: the first click into any of the five clears all five, so your own values never mix with it.

Nothing checked yet
The block, with the values trimmed and quoted correctly

    
  

Single quotes are used on purpose. In double quotes PHP reads a dollar sign as the start of a variable and a backslash as an escape, which is how a correct password turns into a wrong one on the way into the file.

3. Repairing a crashed table

Only for error 144 or 145, or a table that answers nothing. It does not help with a login or a host that cannot be reached.

wp-config.php, above the line that says that is all, stop editing
define( 'WP_ALLOW_REPAIR', true );

Then open /wp-admin/maint/repair.php on your site, for example https://example.com/wp-admin/maint/repair.php, and choose repair. Remove the line again the moment you are finished: while it stands there, that page is reachable by anyone without logging in.

Or with WP-CLI, no line in wp-config.php needed
wp db check
wp db repair

Check the free disk space before you repair anything. A repair on a full disk fails and can leave the table worse than it was. Take a copy of the database first if you still can reach it.

WunderPaint
The Dynamic Design and Automation Studio
WunderPaint is a layered image editor for your WordPress media library. WunderPaint Studio is the same thing in any browser, free and without an account.

The repair screen, and taking it back out

When the evidence genuinely points at tables, either the “one or more database tables are unavailable” message or a query failing on a table marked as crashed, WordPress ships a repair tool that is switched off by default. Turn it on with one line in wp-config.php, placed above the /* That's all, stop editing! Happy publishing. */ comment near the bottom.

define( 'WP_ALLOW_REPAIR', true );

Then load /wp-admin/maint/repair.php. The screen offers two buttons. The first runs CHECK TABLE against each core table and only issues REPAIR TABLE on the ones that come back with anything other than OK. The second does that and then runs ANALYZE TABLE and OPTIMIZE TABLE as well, which core’s own wording on that page warns will lock the database while it works, so it is not a button for your busiest hour. The table list comes from $wpdb->tables(), so custom plugin tables are not touched unless something adds them through the tables_to_repair filter.

Now the part that matters. That page performs no capability check. It cannot, because the whole reason it exists is that you may be unable to log in. The only thing standing between the public internet and a button that runs repair and optimize operations on your database is the constant you just defined. Core says so itself on the success screen: it prints the line back to you and asks you to remove it “to prevent this page from being used by unauthorized users”. Take it out in the same sitting, not tomorrow. You will be editing over SFTP or SSH rather than in the admin, since the file editor lives behind a working database, so apply the same care you would use editing any live PHP file and keep a copy of the original before you save.

With shell access, wp db repair runs mysqlcheck with --repair and does the equivalent work without ever exposing a URL, which is the better route when it is available.

One limitation to know first: REPAIR TABLE supports only a few storage engines, and InnoDB is not among them. On a modern install you will most likely see “The storage engine for the table doesn’t support repair” for every table. CHECK TABLE still works and still tells you something. Genuine InnoDB corruption is resolved by restoring a backup, or by a server-level recovery mode that belongs in a conversation with your host rather than in a snippet you paste from a blog.

Why intermittent means resources

Configuration errors are deterministic. A wrong password fails at three in the morning with zero visitors exactly the way it fails at noon under load. A missing database is missing on every request. If your site is fine for hours and then throws this error for ninety seconds when a newsletter goes out, nothing in wp-config.php changed during those ninety seconds. The server ran out of something.

Usually that something is connections, and connections run out because requests take too long to finish. A single unindexed query on a large table holds its connection for the duration. Uncached admin-ajax calls hold theirs. WordPress’s own scheduler, which fires on page loads unless you have moved it to a real cron job, adds requests exactly when there are already too many. Each of these is small on its own, and none of them shows up until concurrency is high enough to matter.

Which means the fix is rarely in the database section of anything. It is the same work as the hosting limits and slow queries behind a site that drags under load, and the connection error is simply the point where slow becomes refused.

A holding page instead of a dead page

Since core checks for wp-content/db-error.php before rendering its own message, you can decide in advance what visitors and crawlers see. WordPress’s default page returns a 500 status, and the documentation on dead_db() gives the reason: to keep search engines from caching the error text, and custom messages are told to do the same. A 503 with a Retry-After header describes a temporary outage more honestly.

<?php
// wp-content/db-error.php

http_response_code( 503 );
header( 'Retry-After: 300' );
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Temporarily unavailable</title>
</head>
<body>
<h1>Temporarily unavailable</h1>
<p>We are having a short technical problem. Please try again in a few minutes.</p>
</body>
</html>

This file runs with no database behind it, so it must not call WordPress functions, load the theme, or reference anything from the options table. Plain HTML and headers only. Keep it short enough that it never needs maintenance.

When something looks wrong

The error appeared within an hour of a migration. Credentials, almost certainly. The database and user were probably recreated with different names on the new server, and DB_HOST may no longer be localhost. Check all four constants against what the new host documents rather than assuming only the password changed.

It comes and goes without anyone touching the site. A resource ceiling, not a config problem. Config errors do not heal themselves at 4am and reappear at lunchtime. Look at peak connection counts and at what your site does under concurrency.

The admin shows “One or more database tables are unavailable”. The connection works and the database was selected successfully. This is the one case where the repair screen is the right tool, and where a crashed table is a plausible explanation.

You see “Error reconnecting to the database” rather than “establishing”. The connection existed and then vanished mid-request, and five retries a second apart did not get it back. That is a server that went away, most often restarted or killed for memory, not a credentials problem.

The repair screen reports that the storage engine does not support repair. Your tables are InnoDB, which is expected on any recent install. There is nothing more that screen can do; if a table really is damaged, the path forward is a backup restore.

Every page fails, including wp-login.php, but the host insists MySQL is running. Believe them and test the credentials directly. Errors 1044 and 1045 look identical to an outage from the browser, and both are fixed in the control panel rather than by restarting anything.

Before the next time

Three habits remove most of the panic from this error. Keep the database credentials somewhere other than only wp-config.php, a password manager entry with the name, user, password and host, so that a site you cannot load is not also a site whose credentials you cannot read. Take a database backup immediately before any migration, host change, or major update, and confirm the file downloaded rather than trusting that it exists. And find your host’s status page now, while the site is up, so you are not searching for it from a phone while a client is asking questions.

Where this leaves you

The message is narrower than it looks. It says PHP executed, the config file parsed, and one connection attempt failed. Most of what you would normally suspect when a WordPress site breaks is already excluded by the fact that you are seeing this page at all.

From there, two questions settle it nearly every time. Does the error appear consistently or only sometimes? Consistent means configuration or a downed service, intermittent means resources. And do the four constants in wp-config.php actually work when tested outside WordPress? If they do, the problem is on the server side and no amount of editing that file will change it.

Repair is the last thing to reach for, not the first, and the constant that enables it is a door you open for five minutes and then close. Most of the time the answer is a hostname that changed, a password that was reset in one place and not the other, or a server that briefly had more visitors than connections.

 

Error Establishing a Database Connection: What WordPress Is Actually Telling You

Table of Contents

Learn it by building something

Every week one thing you can make the same afternoon, from dynamic templates to 3D type. Written down step by step.

One mail a week, and then it ends.
Unsubscribe in one click.

Photo Editing

Never Destroy Pixels You Might Want Back: A Working Method

Every edit is either reversible or it is not, and the difference costs nothing at the moment you make it. It shows up later, always at the worst time.

Troubleshooting

Why Is Your WordPress Site Slow? A Diagnostic Order, Not a Checklist

WordPress performance advice is usually handed over as a flat, alphabetized checklist. This walks through the same fixes in the order they actually pay off, starting with the thing most sites get wrong first: images.

Troubleshooting

WordPress Images Not Showing: Five Layers, Five Fixes

A broken image icon is not one problem. It is the same symptom produced by five different failures at five points in the request, and the fix for one does nothing for the other four. Read the server response first, then work down the layers with read-only commands.

CSS & Front-End

CSS Gradient Generator: Gradients That Do Not Band

A gradient that looked smooth in the design tool grows stripes on the page. The cause is arithmetic: 8 bit colour, two similar colours and 1600 pixels to cover. How to count the steps before you ship, the three real fixes, and a builder that measures the banding for you.

WordPress Development

One Design, Two Hundred Names: Designing From a Spreadsheet

Somebody needs two hundred name badges by Thursday. The spreadsheet with all those names already exists, and typing them a second time is pure waste.

Troubleshooting

Allowed Memory Size Exhausted: Where the WordPress Memory Limit Lives

The "Allowed memory size exhausted" fatal error involves three separate ceilings, and the one most people raise is not the one that stopped the request. How PHP's memory_limit, WP_MEMORY_LIMIT and WP_MAX_MEMORY_LIMIT interact, and why images trigger the error more often than anything else.

Download the free WunderPaint Plugin for WordPress

The WunderPaint workspace with the layers panel, adjustment sliders, text style presets and the asset library along the bottom

The Image Editor & Design Studio

Everything described here can be done in the browser, on your own site. The live demo runs the full editor with nothing to install.

Free

Chaos Art

Autonomous painters make one-of-a-kind abstract art in 3D space - gestures, art movements, painterly media, and embeds that paint a new original for every visitor.

Pro

Particle Strokes

Paint with swarms of light: twenty-two movements, a stamp you draw yourself, and curves that give a stroke a shape - the swarm keeps painting for a few seconds after you let go.

Pro

City Diorama

Any place on earth as a miniature you could hold: real streets, water and building footprints raised into a 3D diorama - or wrapped around a sphere as your own tiny planet.

Free

Papercut Art

Layered paper pictures with real depth - a photo sliced along its actual depth into up to twenty layers, parametric landscapes, animals and clouds you can shape, letters with real counters, and a look that runs on three dials.