If Matomo just stopped talking to its database, you're looking at one of a small set of SQLSTATE errors. They show up on the installer's database step, on a normal page load, or in the system check on a site that was fine yesterday. They read like noise. They aren't.
SQLSTATE[28000] [1045] Access denied for user 'matomo'@'localhost' (using password: YES)
SQLSTATE[HY000] [2002] No connection could be made / Connection refused
System Check: CREATE TEMPORARY TABLES / LOAD DATA INFILE not availableBefore you touch anything, read the bracketed number. [1045] means the database server answered and rejected you: an authorization problem. [2002] means the server never answered at all: a connectivity problem. The privilege warnings split in two: CREATE TEMPORARY TABLES is a required grant, while LOAD DATA INFILE is optional performance plumbing. Get the error into the right bucket and you've done most of the work.
| Error | Meaning | First check | Likely fix |
|---|---|---|---|
[1045] | MySQL rejected the user, host, password, or grant | mysql -h ... -u ... -p dbname from the Matomo runtime | Fix password, user host, or grants |
[2002] | No MySQL connection or handshake happened | mysqladmin status, then host, port, socket, firewall, disk | Start MySQL or fix reachability |
CREATE TEMPORARY TABLES | Required database-level grant is missing | SHOW GRANTS | Add the required grant on the Matomo database |
LOAD DATA INFILE | Optional archiving performance check failed | System Check details | Enable it safely or disable the feature |
[1045] Access denied: the server answered and said no
This error is MySQL's, not Matomo's. The connection succeeded, MySQL looked up the user, host, and password you sent, and turned them down. So the fix lives on the database side every time. Here's the order I'd work through it, roughly sorted by how often each one turns out to be the culprit.
Start with stray whitespace. A trailing space in the username or password, usually pasted in by browser autofill on the installer, is the single most common cause. Re-type the credentials by hand, and check that nothing is hiding inside the quotes in config/config.ini.php:
[database]
host = "127.0.0.1"
username = "matomo"
password = "secret"
dbname = "matomo_db"That's "secret", not " secret". MySQL treats those as two different passwords.
Next, confirm the login on its own, with Matomo mostly out of the picture. Run the exact credentials from the same runtime that executes Matomo's PHP, not from your laptop and not necessarily from the database host. If this fails there, Matomo was never going to succeed:
mysql -h 127.0.0.1 -u matomo -p matomo_dbOn a VM, SSH into the app server. In Docker Compose, use docker compose exec matomo sh or a temporary MySQL client on the same Compose network. In Kubernetes, kubectl exec into the Matomo pod or run a temporary client pod in the same namespace. The point is to test the path MySQL sees from Matomo.
Once you are connected, ask MySQL which account it actually matched:
SELECT CURRENT_USER(), USER();
SHOW GRANTS;Then check the user's host, which is the part that catches containerised and cloud setups. In MySQL, 'matomo'@'localhost', 'matomo'@'10.0.2.%', and 'matomo'@'%' are separate accounts with their own passwords and grants. On a single host the first one is fine. Split Matomo and the database across two servers, Docker containers, Kubernetes pods, or an RDS instance, and MySQL sees the connection coming from the app server's address rather than localhost, then matches it against an account that might not exist.
Prefer a specific host, a private subnet such as 'matomo'@'10.0.2.%', or the managed database's recommended private-network rule. Use 'matomo'@'%' only when firewall rules, security groups, or network policy already restrict who can reach MySQL.
Those grants are the next thing to get right. This is the set Matomo's install docs ask for:
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX, DROP, ALTER,
CREATE TEMPORARY TABLES, LOCK TABLES
ON matomo_db.* TO 'matomo'@'localhost';
FLUSH PRIVILEGES;Finally, if a working install throws [1045] out of nowhere, Matomo didn't do it. It never edits the database password itself. Something moved on the database side: a password reset, a user or host change, a FLUSH PRIVILEGES that picked up a stricter grant table, a server upgrade. Diff config/config.ini.php against the live credentials and they'll disagree somewhere.
[2002] Connection refused: the server never answered
[2002] is a different animal. Matomo's MySQL client opened a connection and got nothing back: no rejection, no handshake. Credentials don't matter here, because the conversation never started. You're debugging whether the server is reachable at all.
Start with the obvious one, since it's free to check and it's often the answer: is the database even running?
mysqladmin -u root -p statusIf mysqladmin itself can't reach the server, check the service directly:
systemctl status mariadb # or: systemctl status mysqlIf it's down, start it. If it won't start, its own error log is your next stop, not Matomo's.
The next trick fixes [2002] more often than it has any right to: swap localhost for 127.0.0.1. On a lot of Linux systems the literal string localhost tells the MySQL client to connect over a Unix socket, while 127.0.0.1 forces a TCP connection to port 3306. If the socket path is wrong or the server isn't listening where you think, localhost fails and 127.0.0.1 quietly works. Change host in config/config.ini.php and try again.
If that doesn't do it, work outward: confirm the port (3306 by default) and check that nothing between Matomo and the server is dropping packets. For a remote or containerised database that's usually a host firewall or a security group. One cause people forget, and one Matomo's own FAQ calls out: a database server that has run out of disk space starts refusing connections. Run df -h on the database host.
If you actually want a socket connection, separate the installer from an already-installed Matomo. During installation, Matomo still accepts a socket path in the database host field, such as /var/run/mysqld/mysqld.sock. In config/config.ini.php after install, prefer the dedicated key; when it's set, it overrides whatever host and port you've configured:
[database]
unix_socket = "/var/run/mysqld/mysqld.sock"| Config value | What it usually does | Use it when |
|---|---|---|
host = "localhost" | May use the default MySQL socket on Unix-like systems | Matomo and MySQL are on the same host and the socket path is correct |
host = "127.0.0.1" | Forces TCP to the local MySQL listener | localhost is trying the wrong socket |
unix_socket = "/path/to/sock" | Uses the explicit socket and overrides host/port | You know the socket path after installation |
This same connectivity layer is what trips up scheduled archiving. If your cron job can reach the database from the shell but archiving still fails, the problem is usually downstream of the connection itself, which we pulled apart in why Matomo's cron archiving returns an invalid response.
Treat missing grants and load-data checks separately
If the system check is waving red flags about CREATE TEMPORARY TABLES or LOAD DATA INFILE, don't treat them as the same class of problem. CREATE TEMPORARY TABLES belongs in Matomo's required database grants. LOAD DATA INFILE is a performance feature; enabling it can speed archiving, but it comes with security and hosting constraints.
CREATE TEMPORARY TABLES is already in the grant statement above, and in any GRANT ALL ... ON matomo_db.*. If you ran that, you have it.
| Check | Required for Matomo? | Scope | Fix | Security note |
|---|---|---|---|---|
CREATE TEMPORARY TABLES | Yes | Matomo database | Include it in the database-level grant statement | Do not ignore this one |
LOCK TABLES | Yes | Matomo database | Include it in the same grant statement | Usually safe when scoped to the Matomo database |
LOAD DATA INFILE | No, but useful for performance | Server-side MySQL file import | Make MySQL able to read Matomo's tmp/assets files and grant FILE if needed | FILE is global and should be weighed carefully |
LOAD DATA LOCAL INFILE | No, but useful for performance | Client-side upload from the Matomo/PHP side | Enable MySQL client/server local-infile support and mysqli.allow_local_infile = On when using mysqli | Often the better route when MySQL is remote |
LOAD DATA INFILE is the fussy one. Matomo checks both LOAD DATA INFILE and LOAD DATA LOCAL INFILE, and either one working is enough. The server-side form may need the global FILE privilege, and that one, unlike the Matomo database grants, can't be granted at the database level:
GRANT FILE ON *.* TO 'matomo'@'localhost';Matomo's own docs note that handing out FILE is generally not recommended for security reasons, so weigh it before you do. Server-side loading also means the database server process has to read the files Matomo creates under tmp/assets, which can collide with AppArmor, secure_file_priv, or a separate database host that cannot see the Matomo filesystem.
The local form is different. It can work when the database lives on another server, but it needs local-infile support in the MySQL configuration and, for the mysqli driver, mysqli.allow_local_infile = On in php.ini.
Two more things worth knowing. There's a known false positive where some versions report a LOAD DATA INFILE warning even when the privilege is correctly in place (matomo-org/matomo issue #19267). And if you accept the performance tradeoff and want Matomo to stop trying this feature, turn it off:
[General]
enable_load_data_infile = 0Matomo keeps working. Archiving is a little slower on large data sets, and that's the whole cost.
Confirm the fix
Don't trust the absence of an error. Re-run the check. In the UI that's Administration → Diagnostics → System Check; from the CLI:
./console diagnostics:runAnd to see what the database actually thinks your user can do:
SELECT CURRENT_USER(), USER();
SHOW GRANTS;
-- From an admin account, replace this with the account CURRENT_USER() showed:
SHOW GRANTS FOR 'matomo'@'localhost';A clean diagnostics page and a dashboard that loads means you're done.
If your error turned up right after a Matomo upgrade rather than at install time, rule out the other classic upgrade-time database failure too: a half-applied migration that leaves a table behind, which we covered in Matomo's "table doesn't exist" error after an upgrade.
What we'd actually do
Read the code, then fix the matching half. A [1045] is never a Matomo bug. It's credentials, host, or grants, and you fix it on the database side. A [2002] is reachability: service down, wrong host string, firewall, disk. Credentials are a red herring until the connection opens. For privilege rows, add required database grants first; decide separately whether LOAD DATA INFILE is worth enabling or whether enable_load_data_infile = 0 is the right tradeoff for your install.
To keep all three from coming back, create a dedicated database user scoped to the Matomo database with exactly the grant statement above, and run a current stack instead of the stale floor. Matomo's current docs recommend the latest PHP 8.x release and MySQL 8+ or MariaDB for new or maintained installs. Persist config/config.ini.php too, so a container restart doesn't drop you back into the installer pointing at the wrong host.
The error already told you which half you're in. For a simple single-server install, this is often a five-minute fix. For Docker, managed databases, or Kubernetes, the same buckets apply, but the important part is testing from the Matomo runtime and granting the narrowest host that actually connects.
Martez connects Matomo with Meta Ads and Google Ads so ROAS, CLV, and attribution sit next to your web analytics instead of in a separate spreadsheet. It's in private beta. Join the waitlist if that's relevant.