Introduction

PHP PDO (PHP Data Objects) connects to MySQL using a DSN (Data Source Name) string. When the MySQL server is not running, the port is blocked, credentials are wrong, or the socket path is incorrect, PDO throws a PDOException with "Connection refused". Without proper error handling, this crashes the application with an uncaught exception.

Symptoms

  • PDOException: SQLSTATE[HY000] [2002] Connection refused
  • PDOException: SQLSTATE[HY000] [2002] No such file or directory
  • PDOException: SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed
  • Application returns 500 Internal Server Error
  • Works from command line but fails from web server (different socket path)
bash
Fatal error: Uncaught PDOException: SQLSTATE[HY000] [2002] Connection refused
in /app/src/Database.php:15
Stack trace:
#0 /app/src/Database.php(15): PDO->__construct('mysql:host=local...', 'root', 'password')
#1 /app/src/App.php(8): Database->connect()

Common Causes

  • MySQL server not running or crashed
  • Wrong hostname or port in DSN
  • Unix socket path incorrect (different for CLI vs PHP-FPM)
  • Firewall blocking port 3306
  • MySQL bind-address set to 127.0.0.1 but connecting from remote host

Step-by-Step Fix

  1. 1.Diagnose the connection issue:
  2. 2.```bash
  3. 3.# Check if MySQL is running
  4. 4.systemctl status mysql
  5. 5.# or
  6. 6.service mysql status

# Test TCP connection mysql -h 127.0.0.1 -P 3306 -u root -p

# Test socket connection mysql -u root -p

# Check MySQL bind address grep bind-address /etc/mysql/mysql.conf.d/mysqld.cnf

# Check if port is listening netstat -tlnp | grep 3306 # or ss -tlnp | grep 3306 ```

  1. 1.Fix DSN configuration:
  2. 2.```php
  3. 3.// WRONG - localhost uses socket, not TCP
  4. 4.$dsn = 'mysql:host=localhost;dbname=myapp';
  5. 5.// On some systems, localhost resolves to socket at wrong path

// CORRECT - use 127.0.0.1 for TCP connection $dsn = 'mysql:host=127.0.0.1;port=3306;dbname=myapp;charset=utf8mb4';

// OR specify socket explicitly $dsn = 'mysql:unix_socket=/var/run/mysqld/mysqld.sock;dbname=myapp;charset=utf8mb4';

// Find correct socket path: // MySQL: SELECT @@socket; // MariaDB: mysql_config --socket ```

  1. 1.Add proper error handling with retry logic:
  2. 2.```php
  3. 3.function connectWithRetry(string $dsn, string $user, string $pass, int $maxRetries = 3): PDO {
  4. 4.$options = [
  5. 5.PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  6. 6.PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
  7. 7.PDO::ATTR_EMULATE_PREPARES => false,
  8. 8.PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci",
  9. 9.];

for ($attempt = 1; $attempt <= $maxRetries; $attempt++) { try { $pdo = new PDO($dsn, $user, $pass, $options); return $pdo; } catch (PDOException $e) { if ($attempt === $maxRetries) { // Log the full error error_log("Database connection failed after $maxRetries attempts: " . $e->getMessage()); throw new RuntimeException('Database unavailable', 503, $e); }

// Exponential backoff $delay = pow(2, $attempt); error_log("Connection attempt $attempt failed, retrying in {$delay}s"); sleep($delay); } } } ```

  1. 1.Configure PHP-FPM vs CLI socket differences:
  2. 2.```php
  3. 3.// Detect environment and use correct socket
  4. 4.function getDatabaseDsn(): string {
  5. 5.$socket = php_sapi_name() === 'cli'
  6. 6.? '/var/run/mysqld/mysqld.sock' // CLI socket
  7. 7.: '/run/mysqld/mysqld.sock'; // PHP-FPM socket (may differ)

if (file_exists($socket)) { return "mysql:unix_socket=$socket;dbname=myapp;charset=utf8mb4"; }

// Fallback to TCP return 'mysql:host=127.0.0.1;port=3306;dbname=myapp;charset=utf8mb4'; } ```

Prevention

  • Use 127.0.0.1 instead of localhost to force TCP and avoid socket path issues
  • Always set PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
  • Include charset=utf8mb4 in DSN to avoid encoding issues
  • Add connection retry logic for transient MySQL restarts
  • Use environment variables for DSN, not hardcoded values
  • Add a health check endpoint that tests database connectivity
  • Monitor MySQL uptime and connection count in production dashboards