SQLSTATE[HY000] [2002] Connection refused in Laravel
Laravel can't reach MySQL. "Connection refused" is a wrong host or port or a stopped server; "No such file or directory" is DB_HOST=localhost using a socket.
SQLSTATE[HY000] [2002] means PHP couldn't open a connection to the MySQL or MariaDB server at all: no query ran. The text after [2002] tells you why, and each variant has a different fix. Recent Laravel versions add the connection details to the message, which makes the cause easier to spot:
Illuminate\Database\QueryException: SQLSTATE[HY000] [2002] Connection refused (Connection: mysql, Host: 127.0.0.1, Port: 3306, Database: laravel, SQL: select 1)
Illuminate\Database\QueryException: SQLSTATE[HY000] [2002] No such file or directory (Connection: mysql, Host: localhost, Port: 3306, Database: laravel, SQL: select 1)
Illuminate\Database\QueryException: SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo for mysql failed: nodename nor servname provided, or not known (Connection: mysql, Host: mysql, Port: 3306, Database: laravel, SQL: select 1)
These three came from one Laravel 13.40 app with no MySQL running, changing only DB_HOST. On Linux the third reads Name or service not known or Temporary failure in name resolution.
"No such file or directory": localhost means a socket
With DB_HOST=localhost, PHP's MySQL driver doesn't use TCP: it connects through a Unix socket file. "No such file or directory" means that file isn't where PHP looks. PHP looks at pdo_mysql.default_socket:
php -i | grep pdo_mysql.default_socket
# pdo_mysql.default_socket => /tmp/mysql.sock => /tmp/mysql.sock
mysql -u root -p -e "SELECT @@socket;"
If the two paths differ, or MySQL isn't running so there is no socket, you get this error. Two fixes:
# Use TCP instead of the socket
DB_HOST=127.0.0.1
DB_PORT=3306
# Or point Laravel at the real socket
DB_HOST=localhost
DB_SOCKET=/var/run/mysqld/mysqld.sock
127.0.0.1 is the usual fix. It also means MySQL must listen on TCP: check bind-address and skip-networking in its config.
"Connection refused": nothing listens on that host and port
The host answered, but no database server listens on the port. In order of likelihood:
- MySQL isn't running.
sudo systemctl status mysql(ormariadb),brew services liston macOS. Start it, and check its error log if it won't start. - Wrong port. Another local install, or a Docker container mapped to
3307. Check withss -ltnp | grep 3306on Linux orlsof -iTCP -sTCP:LISTEN | grep mysqlon macOS. - MySQL listens on another interface.
bind-address = 127.0.0.1refuses connections from another server or container. On a separate database server, bind it to the private interface and allow your app server in the firewall. - A managed database refuses your IP. On RDS, DigitalOcean, PlanetScale and similar, the app server's IP or security group must be allowed.
Test the connection without Laravel, from the app server:
mysql -h 127.0.0.1 -P 3306 -u DB_USERNAME -p
DB_USERNAME is the user from your .env. If this fails too, the problem is the database or the network, not Laravel.
In Docker, localhost is the container itself
Inside a container, 127.0.0.1 and localhost point at that container, not at the MySQL container next to it. That gives "Connection refused" (or "No such file or directory" for localhost). Use the Compose service name as the host:
# compose.yaml
services:
app:
build: .
environment:
DB_HOST: mysql
DB_PORT: 3306
depends_on:
mysql:
condition: service_healthy
mysql:
image: mysql:8.4
environment:
MYSQL_DATABASE: laravel
MYSQL_ROOT_PASSWORD: secret
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1"]
interval: 5s
retries: 10
The healthcheck with condition: service_healthy matters: MySQL takes several seconds to accept connections after its container starts, and an app that runs php artisan migrate on boot fails with "Connection refused" in that window.
Laravel Sail: the host is mysql inside, 127.0.0.1 outside
Sail's .env has DB_HOST=mysql, the service name. That works for everything run through Sail, because it runs inside the container network. Run the same command on your machine and the name doesn't resolve: that's the getaddrinfo for mysql failed variant.
# Works: runs inside the container
./vendor/bin/sail artisan migrate
# Fails with getaddrinfo for mysql: runs on your machine
php artisan migrate
For a GUI client or a host-side command, connect to 127.0.0.1 with FORWARD_DB_PORT (3306 by default) instead.
You changed .env, but Laravel still uses the old host
If the message shows a Host: you already changed, the config is cached. php artisan config:cache froze the old values in bootstrap/cache/config.php:
php artisan config:clear
php artisan config:cache
php artisan queue:restart
Restart PHP-FPM or Octane too. Long-running processes such as queue workers keep the old config until they restart.
PostgreSQL gives a different message
The same problem on PostgreSQL reads SQLSTATE[08006] [7] connection to server at "127.0.0.1", port 5432 failed: Connection refused. The causes are the same: the server isn't running, the host or port is wrong, or listen_addresses and pg_hba.conf don't allow your app.
Catch it in production
A database that can't be reached usually takes the whole app down, but Laravel's /up health route doesn't touch the database, so it keeps answering 200. Make it check the connection:
// app/Providers/AppServiceProvider.php
use Illuminate\Foundation\Events\DiagnosingHealth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
public function boot(): void
{
Event::listen(DiagnosingHealth::class, function () {
DB::select('select 1');
});
}
/up now returns 500 when the query throws. A Lampwick uptime monitor on /up alerts the team after two failed checks in a row. With the Laravel package, each QueryException also becomes one issue in Issues with its count and the requests and jobs it broke; see the other SQLSTATE codes for the errors that come after the connection works.