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 refusedPDOException: SQLSTATE[HY000] [2002] No such file or directoryPDOException: 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)
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.Diagnose the connection issue:
- 2.```bash
- 3.# Check if MySQL is running
- 4.systemctl status mysql
- 5.# or
- 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.Fix DSN configuration:
- 2.```php
- 3.// WRONG - localhost uses socket, not TCP
- 4.$dsn = 'mysql:host=localhost;dbname=myapp';
- 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.Add proper error handling with retry logic:
- 2.```php
- 3.function connectWithRetry(string $dsn, string $user, string $pass, int $maxRetries = 3): PDO {
- 4.$options = [
- 5.PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
- 6.PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
- 7.PDO::ATTR_EMULATE_PREPARES => false,
- 8.PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci",
- 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.Configure PHP-FPM vs CLI socket differences:
- 2.```php
- 3.// Detect environment and use correct socket
- 4.function getDatabaseDsn(): string {
- 5.$socket = php_sapi_name() === 'cli'
- 6.? '/var/run/mysqld/mysqld.sock' // CLI socket
- 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.1instead oflocalhostto force TCP and avoid socket path issues - Always set
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION - Include
charset=utf8mb4in 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