Fix: MySQL ERROR 2002 (HY000): Can't connect to local MySQL server through socket
Part of: Docker, DevOps & Infrastructure
Quick Answer
How to fix MySQL ERROR 2002 (HY000) when the MySQL client can't connect through the Unix socket file on Linux, macOS, Docker, and WSL.
The Error
You try to connect to MySQL and get:
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)Or one of these variations:
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2)ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (13)The (2) at the end means “No such file or directory” — the socket file doesn’t exist. The (13) means “Permission denied” — the socket file exists but your user can’t access it. Both point to the same underlying problem: the MySQL client can’t communicate with the MySQL server through its Unix socket.
Why This Happens
When you connect to MySQL on Linux or macOS without specifying a hostname (e.g., just mysql -u root), the client doesn’t use TCP. Instead, it connects through a Unix domain socket — a special file on disk that allows inter-process communication. This is faster than TCP because it bypasses the network stack entirely.
The socket file is created by the MySQL server when it starts and removed when it stops. Its default location varies by distribution:
- Debian/Ubuntu:
/var/run/mysqld/mysqld.sock - RHEL/CentOS/Fedora:
/var/lib/mysql/mysql.sock - macOS (Homebrew):
/tmp/mysql.sock - SUSE/OpenSUSE:
/var/run/mysql/mysql.sock
ERROR 2002 means either:
- MySQL isn’t running, so the socket file was never created (or was removed on shutdown).
- The socket file exists, but the client is looking in the wrong place. The server and client have different ideas about where the socket lives.
- The socket file or its directory has wrong permissions, so the client can’t access it.
- The socket file was deleted while MySQL was still running (e.g., by an overly aggressive
tmpwatchorsystemd-tmpfiles). - Something external is blocking access to the socket file (AppArmor, SELinux, or container isolation).
Fix 1: Start the MySQL Service
The most common cause. MySQL isn’t running, so the socket file doesn’t exist.
Linux (systemd):
sudo systemctl start mysqlOn RHEL/CentOS/Fedora, the service name is mysqld:
sudo systemctl start mysqldCheck the status:
sudo systemctl status mysqlIf it’s not enabled to start on boot:
sudo systemctl enable mysqlLinux (older init systems):
sudo service mysql startmacOS (Homebrew):
brew services start mysqlAfter starting, verify the socket file was created:
ls -la /var/run/mysqld/mysqld.sockThen test the connection:
mysql -u root -pIf the service fails to start, skip ahead to Fix 9 to check the error log.
Real-world scenario: You installed MySQL via Homebrew on macOS, then later upgraded macOS to Sequoia. The OS aggressively cleaned
/tmp, deleting your socket file. MySQL is running fine, but every client connection fails because the socket is gone.
Fix 2: Fix the Socket Path in my.cnf
The MySQL client and server both read the socket path from configuration files. If they disagree about the path, the server creates the socket in one location while the client looks for it in another.
Find which config files MySQL is reading:
mysql --help | grep -A 1 "Default options"This shows something like:
Default options are read from the following files in the given order:
/etc/my.cnf /etc/mysql/my.cnf ~/.my.cnfCheck the socket path configured for both [mysqld] (server) and [client] (client):
grep -r "socket" /etc/my.cnf /etc/mysql/ 2>/dev/nullMake sure both sections use the same path. Edit your my.cnf:
[mysqld]
socket=/var/run/mysqld/mysqld.sock
[client]
socket=/var/run/mysqld/mysqld.sockIf you just want to connect right now without changing the config, pass the socket path directly:
mysql -u root -p --socket=/var/run/mysqld/mysqld.sockTo find where the running MySQL server actually created the socket:
sudo find / -name "mysql*.sock" -type s 2>/dev/nullRestart MySQL after changing the config:
sudo systemctl restart mysqlFix 3: Recreate the Socket Directory
On some systems, the socket directory (e.g., /var/run/mysqld/) doesn’t survive a reboot because /var/run is mounted as a tmpfs. If the directory is missing, MySQL can’t create the socket file and fails to start.
Check if the directory exists:
ls -la /var/run/mysqld/If it doesn’t exist, create it with the correct ownership:
sudo mkdir -p /var/run/mysqld
sudo chown mysql:mysql /var/run/mysqld
sudo chmod 755 /var/run/mysqldThen restart MySQL:
sudo systemctl restart mysqlTo make this permanent across reboots, create a tmpfiles.d configuration:
echo "d /var/run/mysqld 0755 mysql mysql -" | sudo tee /etc/tmpfiles.d/mysql.confFix 4: Fix Permissions on the Socket File
If the error ends with (13) instead of (2), the socket file exists but your user can’t access it. This is a permissions problem, similar to file and socket permission issues in Docker.
Check the socket file permissions:
ls -la /var/run/mysqld/mysqld.sockThe socket should be owned by the mysql user and be readable/writable by others:
srwxrwxrwx 1 mysql mysql 0 Apr 17 10:00 /var/run/mysqld/mysqld.sockIf the permissions are too restrictive, fix them:
sudo chmod 777 /var/run/mysqld/mysqld.sockAlso check the directory permissions. The user running the MySQL client needs execute permission on every directory in the path:
sudo chmod 755 /var/run/mysqldIf the permissions keep resetting, check the my.cnf for a restrictive socket permission setting or examine whether AppArmor/SELinux is interfering (see Fix 7).
Fix 5: Connect via TCP Instead of the Socket
If you need to connect immediately and can’t fix the socket issue right away, bypass the socket entirely by forcing a TCP connection:
mysql -u root -p --protocol=tcpOr use 127.0.0.1 instead of localhost. When you use localhost, MySQL uses the Unix socket. When you use 127.0.0.1, it uses TCP:
mysql -u root -p -h 127.0.0.1For application connection strings, use 127.0.0.1 instead of localhost:
mysql://root:[email protected]:3306/mydbIn PHP (php.ini or your app config):
mysqli.default_host = 127.0.0.1This is a valid workaround, but it’s slightly slower than socket connections for local access. For remote connections, TCP is the only option anyway. Make sure MySQL is listening on TCP by checking:
sudo ss -tlnp | grep 3306If nothing shows up, ensure skip-networking is not enabled in my.cnf:
# Remove or comment out this line:
# skip-networkingFix 6: Fix a Deleted Socket File (Without Restarting)
If something deleted the socket file while MySQL is still running (for example, tmpwatch or systemd-tmpfiles cleaning up /tmp or /var/run), MySQL is running but unreachable.
Check if MySQL is actually running:
ps aux | grep mysqldIf it is running, the quickest fix is to restart MySQL so it recreates the socket:
sudo systemctl restart mysqlTo prevent tmpwatch or systemd-tmpfiles from cleaning up the socket in the future, exclude the directory. For systemd-tmpfiles, create a configuration:
echo "x /var/run/mysqld/*" | sudo tee /etc/tmpfiles.d/mysql-protect.confFor tmpwatch on RHEL/CentOS, edit /etc/cron.daily/tmpwatch and add an exclusion for the socket directory.
Pro Tip: If you need to connect right now and don’t have time to debug the socket, use
mysql -u root -p -h 127.0.0.1to force a TCP connection. This bypasses the socket entirely and works as long as MySQL is listening on port 3306.
Fix 7: AppArmor or SELinux Blocking Access
Security frameworks like AppArmor (Ubuntu/Debian) and SELinux (RHEL/CentOS/Fedora) can silently block MySQL from creating or accessing the socket file.
SELinux:
Check if SELinux is blocking MySQL:
sudo ausearch -m AVC -ts recent | grep mysqlOr check the audit log:
sudo grep mysql /var/log/audit/audit.log | tail -20Temporarily set SELinux to permissive to test:
sudo setenforce 0
sudo systemctl restart mysqlIf MySQL works in permissive mode, generate a proper SELinux policy:
sudo ausearch -c 'mysqld' --raw | audit2allow -M mysql_local
sudo semodule -i mysql_local.ppThen re-enable enforcing mode:
sudo setenforce 1AppArmor:
Check if AppArmor is restricting MySQL:
sudo aa-status | grep mysqlIf the MySQL profile is in enforce mode, temporarily switch it to complain mode:
sudo aa-complain /usr/sbin/mysqld
sudo systemctl restart mysqlIf that fixes it, update the AppArmor profile to allow the socket path and then re-enforce:
sudo aa-enforce /usr/sbin/mysqldFix 8: Docker MySQL Socket Issues
When running MySQL in a Docker container, the socket file exists inside the container, not on the host. A client running on the host can’t access it.
Connect to Docker MySQL via TCP:
mysql -u root -p -h 127.0.0.1 -P 3306Make sure you exposed the port in your docker run command:
docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=secret mysql:8Share the socket file with the host:
Mount a volume for the socket:
docker run -d --name mysql \
-v /var/run/mysqld:/var/run/mysqld \
-e MYSQL_ROOT_PASSWORD=secret \
mysql:8Connecting from another container (docker-compose):
Use the service name as the hostname, just as you would with PostgreSQL container networking:
services:
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: secret
ports:
- "3306:3306"
app:
build: .
environment:
DATABASE_URL: mysql://root:secret@db:3306/mydb
depends_on:
- dbThe hostname inside the container is db, not localhost.
Fix 9: MySQL Crashed — Check the Error Log
If MySQL was running and suddenly stopped, the socket file disappears and you get ERROR 2002. Check the error log to find out why it crashed:
# Debian/Ubuntu
sudo tail -100 /var/log/mysql/error.log
# RHEL/CentOS/Fedora
sudo tail -100 /var/log/mysqld.log
# Or use journalctl
sudo journalctl -u mysql --no-pager -n 100Common crash causes:
- Disk full. MySQL can’t write and shuts down. Free up space:
df -h- Out of memory. The OOM killer terminated MySQL. Check:
sudo dmesg | grep -i "oom" | grep mysqlReduce MySQL memory usage in my.cnf:
[mysqld]
innodb_buffer_pool_size = 256M
key_buffer_size = 32M- Corrupted InnoDB tablespace. Look for
InnoDB: corruptionmessages in the error log. Start MySQL in recovery mode:
[mysqld]
innodb_force_recovery = 1Start with 1 and increase up to 6 if needed. Dump your data and rebuild once it starts.
- Stale PID file. If MySQL didn’t shut down cleanly, a PID file may prevent it from starting:
sudo rm /var/run/mysqld/mysqld.pid
sudo systemctl start mysqlOnly remove the PID file if you’ve confirmed MySQL is not running with ps aux | grep mysqld.
Fix 10: tmpdir Full or Unwritable
MySQL uses a temporary directory for sorting, temporary tables, and other operations. If this directory is full or unwritable, MySQL may fail to start.
Check the configured tmpdir:
mysql -u root -p -e "SHOW VARIABLES LIKE 'tmpdir';"If you can’t connect, check the config:
grep tmpdir /etc/mysql/my.cnf /etc/my.cnf 2>/dev/nullCheck if the tmpdir has space and correct permissions:
df -h /tmp
ls -la /tmpIf /tmp is full, clean it up or point MySQL to a different directory:
[mysqld]
tmpdir = /var/tmpRestart MySQL after changing this.
Fix 11: Multiple MySQL Installations Conflicting
Having multiple MySQL installations (or MySQL alongside MariaDB) can cause socket conflicts. Each instance may try to use a different socket path, or one installation’s client may try to connect to another installation’s server.
Check for multiple installations:
# Debian/Ubuntu
dpkg -l | grep -i mysql
dpkg -l | grep -i mariadb
# RHEL/CentOS
rpm -qa | grep -i mysql
rpm -qa | grep -i mariadbCheck for multiple running instances:
ps aux | grep mysqldIf multiple instances are running, each should have a unique socket path, port, and data directory in its my.cnf. To connect to a specific instance, use its socket:
mysql -u root -p --socket=/var/run/mysqld/mysqld.sock
mysql -u root -p --socket=/var/run/mysqld/mysqld2.sockIf you only need one installation, remove the conflicting one. On Debian/Ubuntu, MariaDB and MySQL use the same socket path by default, so they can’t run simultaneously without reconfiguration.
Fix 12: macOS Homebrew-Specific Issues
On macOS with Homebrew, MySQL uses /tmp/mysql.sock by default, but several things can go wrong.
Check if MySQL is running:
brew services list | grep mysqlStart MySQL:
brew services start mysqlCheck where the socket actually is:
ls -la /tmp/mysql.sockIf the socket is missing but MySQL is running, check if Homebrew installed MySQL with a custom socket path:
cat /opt/homebrew/etc/my.cnf 2>/dev/null
cat /usr/local/etc/my.cnf 2>/dev/nullCommon macOS fix — create a symlink:
If your client expects the socket at /tmp/mysql.sock but MySQL creates it elsewhere:
sudo ln -s /opt/homebrew/var/mysql/mysqld.local.sock /tmp/mysql.sockFix for macOS Sequoia and later:
Recent macOS versions may clean up /tmp more aggressively. If the socket keeps disappearing, configure MySQL to use a different directory:
[mysqld]
socket=/opt/homebrew/var/mysql/mysqld.sock
[client]
socket=/opt/homebrew/var/mysql/mysqld.sockIf you previously installed MySQL via the official DMG and now use Homebrew (or vice versa), conflicting configurations may exist. Check for config files in multiple locations:
ls -la /etc/my.cnf /usr/local/etc/my.cnf /opt/homebrew/etc/my.cnf ~/.my.cnf 2>/dev/nullFix 13: WSL (Windows Subsystem for Linux) Specific Issues
Running MySQL inside WSL has unique challenges because WSL doesn’t use systemd by default (WSL 1 and older WSL 2).
Start MySQL manually on WSL without systemd:
sudo service mysql startOr start the daemon directly:
sudo mysqld_safe &Check if MySQL is running:
ps aux | grep mysqldSocket path issues on WSL:
The socket directory may not exist after a WSL restart. Create it:
sudo mkdir -p /var/run/mysqld
sudo chown mysql:mysql /var/run/mysqld
sudo service mysql startWSL 2 with systemd enabled:
If you’ve enabled systemd in WSL 2 (via /etc/wsl.conf), the standard systemctl commands work:
sudo systemctl start mysqlConnecting from Windows to MySQL inside WSL:
From Windows, connect via TCP to WSL’s IP address:
mysql -u root -p -h 127.0.0.1 -P 3306The socket file is not accessible from Windows because it’s inside the WSL filesystem. Always use TCP for cross-system connections.
Connecting from WSL to MySQL on Windows:
If MySQL is installed on Windows (not WSL), connect via TCP using the Windows host IP:
mysql -u root -p -h $(hostname).local -P 3306How Other Databases Handle Local Sockets
The “localhost means Unix socket, 127.0.0.1 means TCP” behavior is specific to MySQL’s client library, and it surprises developers moving between databases. Knowing where each tool draws the line saves a lot of “why does this work in PostgreSQL but not MySQL” debugging.
MySQL Unix socket:
The client picks socket vs TCP based on the host string. localhost (string, not address) means “use the Unix socket from [client] socket=... in my.cnf.” Any other value — 127.0.0.1, an IP, a hostname — means TCP. The socket path is per-build, so MySQL from APT, Homebrew, and the official .dmg may each default to a different path on the same machine.
MariaDB Unix socket:
MariaDB inherits MySQL’s socket conventions but defaults the socket path to /var/run/mysqld/mysqld.sock on Debian/Ubuntu and /var/lib/mysql/mysql.sock on RHEL — the same paths MySQL uses, which is why having both installed simultaneously causes immediate conflicts (Fix 11). MariaDB 10.4+ uses unix_socket authentication by default for the root user, meaning sudo mysql works without a password while mysql -h 127.0.0.1 over TCP requires one. That asymmetry is a frequent source of “works locally, fails from app” reports.
PostgreSQL Unix socket:
PostgreSQL also supports Unix sockets but uses a different convention: the socket lives in a directory whose path is the host argument. The default is /var/run/postgresql/ on Debian/Ubuntu and /tmp/ on the official .dmg build. You connect with psql -h /var/run/postgresql -p 5432 mydb — the host argument is a directory path, not a service name. If you say psql -h localhost, libpq uses TCP. This is the opposite of MySQL, where localhost means socket. If your PostgreSQL client times out connecting to localhost, see Fix: PostgreSQL Connection Refused.
TCP fallback behavior:
MySQL never falls back from socket to TCP automatically — if localhost and the socket fails, you get ERROR 2002. PostgreSQL’s psql does not fall back either. By contrast, some ORMs (e.g., Sequelize, SQLAlchemy with mysqlclient) silently retry over TCP when the socket connection fails, which can mask configuration drift between dev and prod for months.
Language-driver differences:
- PHP (
mysqli,PDO_MYSQL): Readsmysqli.default_socket/pdo_mysql.default_socketfromphp.ini. If those are set to a stale path, PHP fails even when the CLI client works. Thehostargument string:prefix ('localhost:/var/run/mysqld/mysqld.sock') forces a socket;'127.0.0.1'forces TCP. - Python (
mysqlclient,PyMySQL): Accept aunix_socket=keyword argument. Without it, both default to TCP regardless of thehostvalue — sohost='localhost'in Python does not trigger socket selection the way the CLI does. - Node.js (
mysql2): ThesocketPathoption enables Unix socket connections. Without it,mysql2always uses TCP. Thehost: 'localhost'shortcut does not mean “use socket” the way the CLI does — Node has no built-in mapping. - Go (
go-sql-driver/mysql): The DSN syntax explicitly distinguishes:user:pass@unix(/var/run/mysqld/mysqld.sock)/dbnamevsuser:pass@tcp(127.0.0.1:3306)/dbname. There is no implicitlocalhostrule.
Practical takeaway:
If your CLI works but your app fails with ERROR 2002, the app probably is not using unix_socket at all — it is hitting TCP on a different port or address. Conversely, if your app works but the CLI fails, the CLI is loading a [client] socket path from my.cnf that does not match the server’s actual socket. Always check what string each layer is passing.
Still Not Working?
MySQL starts but the socket disappears after a few seconds
This usually means MySQL starts, encounters an error, and shuts down immediately. The socket exists briefly but is removed on shutdown. Check the error log — the real error is there:
sudo tail -50 /var/log/mysql/error.logCommon causes: wrong file ownership on the data directory, InnoDB log file size mismatch after a configuration change, or insufficient file descriptors.
Connection works as root but not as your user
If sudo mysql -u root works but mysql -u root doesn’t, the issue is file permissions on the socket or its parent directory. Your regular user needs read and execute access to /var/run/mysqld/. This is similar to other permission-denied issues in Bash where the current user lacks the necessary access rights.
ERROR 1045 instead of ERROR 2002
If you’ve fixed the socket issue but now see ERROR 1045 (28000): Access denied for user, that’s a separate authentication problem. The connection itself is working — your credentials are wrong. See Fix: MySQL Access Denied for User for solutions.
The socket file exists but connections still fail
If the socket file is present and has correct permissions but connections still fail, the file may be stale (left over from a crashed MySQL instance). Remove it and restart:
sudo rm /var/run/mysqld/mysqld.sock
sudo systemctl restart mysqlApplication-specific connection issues
Some applications have their own MySQL socket configuration separate from the system default:
- PHP: Check
pdo_mysql.default_socketandmysqli.default_socketinphp.ini. - Python (mysqlclient): Pass
unix_socketin your connect call or set it in amy.cnfthat your app reads. - Ruby on Rails: Set
socketinconfig/database.yml. - WordPress: Set
DB_HOSTinwp-config.phptolocalhost:/var/run/mysqld/mysqld.sock.
Nothing works and you need to connect to a remote database
If you’re actually trying to reach a MySQL server on another machine, the socket approach won’t work at all. Use TCP with the remote host’s IP or hostname:
mysql -u root -p -h 192.168.1.100 -P 3306Make sure the remote server’s bind-address in my.cnf allows connections from your IP, and that the firewall permits traffic on port 3306. This is conceptually the same as troubleshooting remote connection issues with MongoDB or Redis — verify the service is listening, the bind address is correct, and the firewall is open.
MariaDB Replaced MySQL Under You During a Distro Upgrade
On Debian/Ubuntu, apt upgrade between major releases sometimes replaces mysql-server with mariadb-server because Debian defaults to MariaDB. The socket path stays the same but the daemon, the data file format expectations, and the auth plugin all change. If your error appeared right after an upgrade, check which server is actually installed:
mysqld --version
apt list --installed 2>/dev/null | grep -E '(mysql|mariadb)'If MariaDB replaced MySQL, the data directory may still be in MySQL format. Run mariadb-upgrade (or mysql_upgrade on older versions) once the service starts. If it refuses to start, restore from a backup of /var/lib/mysql and switch back to the official MySQL APT repo.
The Socket Is on a Different mount namespace (systemd PrivateTmp)
Modern systemd unit files for MySQL often set PrivateTmp=true, which gives the MySQL process its own /tmp namespace. If you configured socket=/tmp/mysql.sock, the socket lives inside the private /tmp, not the host’s /tmp. Your client sees nothing in /tmp even though ls from inside the service would. Move the socket out of /tmp:
[mysqld]
socket = /var/run/mysqld/mysqld.sockOr set PrivateTmp=false in a drop-in unit (systemctl edit mysql). The first option is the better long-term fix.
The Client Library Was Compiled Against a Different Socket Path
Compiled MySQL client libraries (e.g., the mysqlclient Python package, PHP’s mysqlnd) embed a default socket path at build time. If you installed mysqlclient from a wheel built on Debian (/var/run/mysqld/mysqld.sock) but you are running on RHEL (/var/lib/mysql/mysql.sock), the embedded default is wrong. Pass unix_socket explicitly in your connect call rather than relying on the default:
import MySQLdb
conn = MySQLdb.connect(unix_socket='/var/lib/mysql/mysql.sock', user='root', passwd='...')Or rebuild the client from source against the local libmysqlclient, so the embedded default matches your distro.
Related: If you’re troubleshooting other database connection problems, see Fix: PostgreSQL Connection Refused and Fix: MySQL Access Denied for User. For Docker socket and permission issues, see Fix: Docker Permission Denied. If your connection string is coming from environment variables that aren’t loading, see Fix: Bash Permission Denied.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Access denied for user 'root'@'localhost' (MySQL ERROR 1045)
How to fix MySQL 'ERROR 1045 (28000): Access denied for user root@localhost (using password: YES/NO)' on Linux, macOS, Windows, and Docker. Covers password reset, auth plugin issues, skip-grant-tables recovery, MySQL 8 vs 5.7 differences, and host mismatches.
Fix: MySQL Replication Not Working — Replica Lag, Stopped Replication, or GTID Errors
How to fix MySQL replication issues — SHOW REPLICA STATUS errors, relay log corruption, GTID configuration, replication lag, skipping errors, and replica promotion.
Fix: MySQL Full-Text Search Not Working — MATCH AGAINST Returns No Results
How to fix MySQL full-text search issues — FULLTEXT index creation, minimum word length, stopwords, boolean mode vs natural language mode, InnoDB vs MyISAM, and LIKE fallback.
Fix: MySQL Index Not Being Used — Query Optimizer Skipping Indexes
How to fix MySQL indexes not being used by the query optimizer — EXPLAIN output, implicit conversions, function on columns, composite index order, cardinality issues, and forcing indexes.