You type mariadb on a Debian 13 box and get MariaDB ERROR 2002 instead of a prompt. Or the prompt never shows up because root gets Access denied. Most guides treat this as a socket path problem, tell you to symlink something into /var/run, and stop there. On trixie, which ships MariaDB 11.8 (1:11.8.6-0+deb13u1 on packages.debian.org at the time of writing), there are at least five separate causes. Each one needs a different fix, and a wrong guess can make things worse. This article goes through the causes in order of how cheap they are to check.
These are the two messages covered here:
ERROR 2002 (HY000): Can't connect to local server through socket '/run/mysqld/mysqld.sock' (2)
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
The number at the end of the 2002 line is an errno from the socket connect() call. (2) is ENOENT (no such file), so the socket file does not exist at that path. (111) is ECONNREFUSED: the file exists but nothing is listening on it, usually a stale socket left by a crash. (13) is EACCES, a permissions problem on the socket or its directory. The Access denied line is not a connection problem. The server is up and your login was rejected. The exact error code depends on the account's authentication plugins. What matters is the 'root'@'localhost' part.
Quick fix: the server is simply not running
In most cases the daemon is down. Check that first:
systemctl status mariadb
journalctl -u mariadb -b --no-pager -n 50
If the status is inactive (dead) and the journal shows a clean stop (someone stopped it, or it was not enabled after a restore), start it and enable it:
systemctl enable --now mariadb
mariadb-admin ping
If the status is failed, do not restart it in a loop. The journal tells you why it failed. Debian's packaging doesn't set log_error in 50-server.cnf, so under systemd the error log goes to journald. The rest of the article covers what those journal lines mean.
Decision tree for MariaDB ERROR 2002 and Access denied
| What you see | Likely cause | First check |
|---|---|---|
2002 with (2), unit failed | Startup error: bad option, full disk, InnoDB recovery, sandbox | journalctl -u mariadb -b |
2002 with (2), unit active | Client and server disagree on socket path | ss -lxp |
2002 with (111) | Stale socket file, server crashed or is still starting | systemctl status mariadb |
| 2002 only from a script or cron job | --defaults-file hides the real config (MDEV-35907) | my_print_defaults |
Access denied for 'root'@'localhost' | unix_socket authentication | sudo mariadb |
Server up, wrong socket path
If systemctl status mariadb shows active (running) and you still get 2002, find out where the server actually put its socket:
ss -lxp | grep -E 'mysqld|mariadbd'
On a stock trixie install, the socket comes from one line in /etc/mysql/mariadb.cnf, in the [client-server] group, which the server and all MariaDB clients both read:
[client-server]
socket = /run/mysqld/mysqld.sock
The same file then includes /etc/mysql/conf.d/ and /etc/mysql/mariadb.conf.d/. Debian's own 50-server.cnf uses [mariadbd] groups, plus [server], [embedded] and a version-specific [mariadb-11.8]. Old configs and many tutorials use [mysqld], which MariaDB still reads. The trap: if someone adds socket = /var/lib/mysql/mysql.sock under [mysqld] or [mariadbd], only the server changes. Clients read [client], [client-mariadb] and [client-server], never the server groups, so they keep looking at the old path.
Print what each side actually resolves:
my_print_defaults client client-mariadb client-server
my_print_defaults client-server server mysqld mariadbd mariadb-11.8
mariadbd --print-defaults
If you can log in over TCP with a password account, ask the running server directly. With the command-line client, -h localhost still uses the socket, so you need -h 127.0.0.1:
SHOW VARIABLES LIKE 'socket';
The fix is to set the socket path once, in [client-server], and remove the copies in server-only groups. Don't symlink sockets between directories. The symlink breaks the next time someone changes either side.
The --defaults-file pitfall (MDEV-35907)
If the interactive client works but a backup script or cron job fails with 2002, check how it loads its config. --defaults-file=path makes a program read only that file and ignore every other option file. So a custom socket set in /etc/mysql disappears, and the client falls back to its compiled-in default. --defaults-extra-file=path reads the file in addition to the normal ones. MariaDB bug MDEV-35907 was this exact bug in Debian's debian-start helper with a non-standard socket. It was fixed upstream in January 2025. In trixie's 1:11.8.6-0+deb13u1 source, debian-start uses --defaults-extra-file=/etc/mysql/debian.cnf. Your own scripts are another matter:
grep -rn -- '--defaults-file' /etc/cron.d /etc/cron.daily /usr/local/bin /etc/systemd/system
Replace those flags with --defaults-extra-file, and keep the flag as the first argument on the command line.
Server will not start: reading the journal
A stale or unknown option in my.cnf
mariadbd won't start if its config contains an option it doesn't recognise. In a Nextcloud forum report with MariaDB 11.4, apt autoremove had removed compression provider plugins while their .cnf files stayed behind. The journal showed:
/usr/sbin/mariadbd: unknown variable 'provider_lz4=force_plus_permanent'
[ERROR] Aborting
The same thing happens with options that upstream has removed and that are still in a config copied over from an old server. Find the line and comment it out. If it belongs to a plugin, reinstall the plugin package or remove its config file. As a temporary measure, prefixing an option with loose- turns the error into a warning. Don't leave it that way for good, because it also hides typos. Then check the config before you restart. mariadbd --print-defaults only lists the options picked up from the option files; it does not validate them. Running the server's option parser with --help --verbose does, and it stops with the same unknown variable error if something is wrong:
grep -rn 'provider_' /etc/mysql/
sudo -u mysql mariadbd --help --verbose >/dev/null
Full disk or InnoDB crash recovery
If the journal talks about InnoDB, check disk space before anything else:
df -h /var/lib/mysql
df -i /var/lib/mysql
A full filesystem can stop InnoDB from writing its redo log or creating temporary files, and if inodes run out, that fails the same way. If df looks fine but writes still fail, see No Space Left on Device but df Shows Free Space.
If the disk is fine and InnoDB reports corruption during crash recovery, the last resort is innodb_force_recovery. MariaDB's documentation recommends starting with mode 1 and raising the value one step at a time only if needed. Recovery mode does not repair anything, and from mode 4 up secondary indexes can be corrupted. The only goal is to get the server up long enough to dump the data.
Warning: take a file-level copy of the stopped datadir before you try this. Higher modes can cause more damage.
[mariadbd]
innodb_force_recovery = 1
systemctl stop mariadb
cp -a /var/lib/mysql /root/mysql-datadir-copy
systemctl start mariadb
mariadb-dump --all-databases --single-transaction > /root/all-databases.sql
Once the dump is done, delete 99-recovery.cnf and rebuild from the dump. Don't run production with this setting.
Custom datadir blocked by the systemd sandbox
If you moved datadir and the server now fails with permission errors even though the ownership is mysql:mysql, look at the unit. Upstream's mariadb.service sets ProtectHome=true, which hides /home, /root and /run/user, and ProtectSystem=full, which makes /usr, /boot and /etc read-only for the service:
systemctl cat mariadb
systemctl show mariadb -p ProtectHome -p ProtectSystem -p ReadWritePaths
A datadir under /home is therefore invisible to mariadbd, whatever the permissions say. The clean fix is to put it somewhere the sandbox allows, such as /srv/mariadb or a bind mount onto /var/lib/mysql. Don't weaken the unit. If you really need an override, keep it as small as possible and read Sandboxing systemd Services on Debian 13 before you remove protections.
Ubuntu-era advice blames AppArmor. On Debian, the packaged MariaDB AppArmor profile is intentionally empty, so AppArmor is off for default installs. It only matters if you wrote your own profile. To rule it out:
aa-status
journalctl -k -b | grep -i 'apparmor="DENIED"'
Access denied for root: the unix_socket trap
On Debian, root authenticates with the unix_socket plugin. The server reads the connecting process's UID from the socket (SO_PEERCRED) and only lets in the MariaDB account with the same name. No password is involved. So mariadb -u root -p run by a normal user fails, and so does a GUI tool over TCP, even with the "right" password. This works:
sudo mariadb
Debian's README.Debian says not to delete the root account and to create separate accounts for applications. Don't switch root to password authentication just to make a tool work. Create an admin account for the tool instead:
CREATE USER 'dbadmin'@'localhost' IDENTIFIED BY 'use-a-long-random-secret';
GRANT ALL PRIVILEGES ON *.* TO 'dbadmin'@'localhost' WITH GRANT OPTION;
Recovering root when sudo mariadb also fails
If someone ran ALTER USER root@localhost IDENTIFIED VIA mysql_native_password and the password is lost, sudo mariadb stops working too. There is no --skip-grant-networking option, despite what some posts say. You have two real options.
The safer one is init_file. The server runs the SQL in it at startup, so authentication is never turned off. The unit's ExecStart passes $MYSQLD_OPTS, so you can inject the option without editing any files. Put the SQL file in /tmp and not /root, because ProtectHome hides /root from the service.
Write the file and set its owner in one step. Debian enables fs.protected_regular, so even root cannot redirect into an existing file in /tmp that belongs to mysql; a pre-created file followed by cat > ends up empty. Check that cat prints the ALTER USER line before you restart anything.
Warning: this restarts the database twice. Applications lose their connections.
install -m 0600 -o mysql -g mysql /dev/stdin /tmp/mariadb-root-reset.sql <<'EOF'
ALTER USER 'root'@'localhost' IDENTIFIED VIA unix_socket;
EOF
cat /tmp/mariadb-root-reset.sql
systemctl set-environment MYSQLD_OPTS='--init-file=/tmp/mariadb-root-reset.sql'
systemctl restart mariadb
systemctl unset-environment MYSQLD_OPTS
rm /tmp/mariadb-root-reset.sql
systemctl restart mariadb
sudo mariadb -e 'SELECT CURRENT_USER()'
The fallback is --skip-grant-tables --skip-networking. skip_networking only stops the TCP listener. The Unix socket stays open, and with grant tables off, any local user who can reach it gets full access. Only use this on a host where you trust every local account, and stop application services first. In the session, run FLUSH PRIVILEGES before ALTER USER, because account management doesn't work until the grant tables are loaded. Afterwards, unset MYSQLD_OPTS and restart straight away.
How to prevent it
- Set
socketonce, in[client-server]. Never put it only in[mysqld]or[mariadbd]. - Put local changes in your own file under
/etc/mysql/mariadb.conf.d/instead of editing Debian's files, so upgrades don't leave you with.dpkg-distconflicts. - After any config change, run
sudo -u mysql mariadbd --help --verbose(output to/dev/null) before the restart to catch unknown variables. - Use
--defaults-extra-filein scripts, never--defaults-file. - Leave root on
unix_socket. Give tools and apps their own accounts. - Keep the datadir under
/var/libor/srv, not/home. - Monitor free space and inodes on the datadir filesystem.
Takeaways
systemctl status mariadbandjournalctl -u mariadb -bfirst. Running or not decides which branch you're in.- Running but 2002: compare
ss -lxpwithmy_print_defaults client client-server. - Only scripts fail: look for
--defaults-file. - Not starting: the journal names the bad option, the disk problem, or the InnoDB error. Fix that instead of the socket.
- Custom datadir: check
ProtectHomeandProtectSystembefore suspecting AppArmor. - Root Access denied: try
sudo mariadb. To recover, prefer--init-fileover--skip-grant-tables.
Sources
- Debian trixie package: mariadb-server
- MDEV-35907: debian-start script fails when using non-standard socket path
- Debian source: mariadb 1:11.8.6-0+deb13u1 mariadb.cnf
- Debian source: mariadb 1:11.8.6-0+deb13u1 50-server.cnf
- Debian source: mariadb 1:11.8.6-0+deb13u1 debian-start
- Debian source: mariadb-server README.Debian
- Debian source: mariadb apparmor-profile
- Debian source: mariadb.service.in
- MariaDB docs: Configuring MariaDB with Option Files
- MariaDB docs: Authentication Plugin - Unix Socket
- MariaDB docs: Authentication from MariaDB 10.4
- MariaDB docs: mariadbd Options
- MariaDB docs: Server System Variables
- MariaDB docs: InnoDB Recovery Modes
- MariaDB docs: Troubleshooting Connection Issues Guide
- Nextcloud forum: MariaDB 11 fails to start because of plugins
- Debian Wiki: NewInBuster (fs.protected_regular)
Comments