← Back to blog

Guide

MySQL port 3306 already in use: Homebrew, MAMP, Docker, and the socket confusion

September 14, 20263 min read

A busy 3306 is almost never a mystery process. It is a second MySQL you forgot you installed, and the useful question is which one, because the way you stop it depends entirely on what started it.

Find the owner first

lsof -i :3306

The command name points you at the case:

  • mysqld on a Mac with Homebrew, usually a brew service.
  • mysqld with MAMP or XAMPP installed, that bundle’s own MySQL.
  • com.docke or docker-proxy, a container.

This matters more than the PID, because killing the process does not work when a manager owns it. Homebrew registers a launchd job whose entire purpose is to restart what stops. Kill it and it returns, which looks like the kill silently failed.

Homebrew

brew services list
brew services stop mysql

Use the exact name the list prints. If you have both mysql and mysql@8.0 installed, they are separate services and both want 3306.

MAMP and XAMPP

Stop MySQL from the control panel, not from a terminal. These bundles manage their own processes and restart them on their own schedule.

Worth knowing: MAMP historically used 8889 for MySQL rather than 3306, so if you have MAMP installed and a conflict on 3306, it is probably not MAMP. Check the panel before assuming.

Docker

docker ps --filter "publish=3306"
docker stop <container>

Killing docker-proxy achieves nothing. Docker republishes the port, and a container with a restart policy comes back after a reboot too.

The socket error is a different problem

This one gets mixed up with port conflicts constantly, and it is not the same thing:

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock'

That is not “the port is busy”. It usually means MySQL is not running at all, or it is running somewhere your client cannot reach through a Unix socket.

The reason it confuses people is that mysql on macOS connects through a socket file rather than TCP when you give it localhost. So the client never touches port 3306, and a port listing tells you nothing about why it failed.

To force it over TCP instead, which is the quickest way to tell the two problems apart:

mysql -h 127.0.0.1 -P 3306 -u root -p

localhost means the socket, 127.0.0.1 means TCP. If the TCP version connects and the socket version does not, your problem is the socket path, not the port.

Running two versions on purpose

Keeping an old MySQL around for a legacy project is common and fine, as long as they do not both want 3306. Give the second one another port in its config:

[mysqld]
port = 3307

Then connect with -P 3307. Deliberate beats accidental, and you stop having to stop one to start the other.

When you actually should kill it

If you started mysqld by hand rather than through a manager, terminating it normally is correct:

lsof -ti :3306 | xargs kill

Plain kill sends SIGTERM and MySQL shuts down cleanly. Avoid kill -9 on a database. An unclean shutdown means recovery work on the next start, and in the worst cases it is how corruption happens.

Related: the same pattern on PostgreSQL’s 5432, where the owner also matters more than the PID.

Bosun shows what holds each port from the menu bar, with Docker containers named properly, so you can see which case you are in before deciding what to stop. macOS 14 or later, 14 day trial.

See this instead of typing it

Bosun lives in your menu bar and shows every open port on your Mac, live, mapped to the process behind it. One-click kill, SIGTERM first. Useful the first time this happens. Genuinely useful the fifth time it happens in one afternoon.