# 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:

```text
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`:

```bash
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:

```dotenv
# Use TCP instead of the socket
DB_HOST=127.0.0.1
DB_PORT=3306
```

```dotenv
# 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:

1. **MySQL isn't running.** `sudo systemctl status mysql` (or `mariadb`), `brew services list` on macOS. Start it, and check its error log if it won't start.
2. **Wrong port.** Another local install, or a Docker container mapped to `3307`. Check with `ss -ltnp | grep 3306` on Linux or `lsof -iTCP -sTCP:LISTEN | grep mysql` on macOS.
3. **MySQL listens on another interface.** `bind-address = 127.0.0.1` refuses 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.
4. **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:

```bash
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:

```yaml
# 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.

```bash
# 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`:

```bash
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:

```php
// 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](/docs/uptime/) on `/up` alerts the team after two failed checks in a row. With the [Laravel package](/docs/laravel/), each `QueryException` also becomes one issue in **Issues** with its count and the requests and jobs it broke; see [the other SQLSTATE codes](/errors/sqlstate/) for the errors that come after the connection works.
