MySQL에 접속할 때 가장 흔하게 마주치는 에러가 ERROR 1045다. 데이터베이스는 설치했는데 갑자기 connection이 끊기거나, 배포 후 운영 서버에서만 접속이 안 되거나, 비밀번호를 분명히 맞게 입력했는데도 denied 된다는 메시지가 떠서 당황해한다. 다만 대부분의 개발자들은 에러 메시지만 보고 비밀번호 한두 글자를 고쳐보다가, 근본 원인을 파악하지 못해 같은 실수를 반복한다. 이번에는 ERROR 1045가 정확히 뭔지, 왜 발생하는지, 단계별로 어떻게 진단하고 해결하는지 완벽하게 정리하겠다.
ERROR 1045는 MySQL 서버가 클라이언트의 인증 요청을 거부했다는 뜻이다. 사용자명(User)과 호스트(Host)는 맞았는데 비밀번호가 틀렸거나, 사용자 계정 자체가 없거나, 해당 호스트에서 접속할 권한이 없을 때 발생한다. MySQL의 권한 시스템은 User 테이블에서 (User, Host) 조합으로 사용자를 식별하므로, 같은 사용자명이라도 다른 호스트 주소면 별개의 계정이다. 예를 들어 'root'@'localhost'와 'root'@'127.0.0.1'은 전혀 다른 사용자다.
1. 비밀번호 불일치 또는 오타
가장 단순하지만 가장 많은 경우다. MySQL 접속 시 비밀번호가 틀렸으면 즉시 rejected 된다. 특히 개발 환경에서 테스트하던 비밀번호와 운영 서버의 비밀번호가 다를 때 이 문제가 생긴다.
2. 사용자 계정 미존재
데이터베이스 생성은 했는데 사용자를 만들지 않았거나, 만든 사용자 이름이 정확하지 않은 경우. 'user1' vs 'User1' 같은 대소문자 차이도 문제가 될 수 있다(비밀번호는 기본적으로 대소문자를 구분한다).
3. 호스트 주소 불일치
'localhost'에서만 접속 가능하도록 설정했는데 다른 호스트(예: 원격 서버)에서 접속하려 하거나, 반대로 특정 IP에서만 접속 가능하도록 제한했을 때 다른 IP에서 접속하려 하면 거부된다.
4. MySQL 서버 미시작 또는 비활성화
MySQL 데몬이 실행 중이지 않으면 connection 자체가 실패한다. 이 경우 ERROR 1045보다는 'Connection refused' 오류가 먼저 뜨지만, 상황에 따라 뒷부분에 인증 실패 메시지가 붙을 수 있다.
5. Unix Socket vs TCP 포트 혼재
로컬 접속일 때 Unix socket(/var/run/mysqld/mysqld.sock)으로 연결하는데 잘못된 소켓 경로를 지정하거나, TCP 포트로 접속해야 하는데 socket 옵션을 사용하면 인증 실패로 보일 수 있다.
가장 먼저 해야 할 일은 PHP/Node.js 같은 애플리케이션을 거치지 말고, 터미널에서 직접 MySQL에 접속해보는 것이다. 이렇게 하면 애플리케이션 코드 문제인지, MySQL 서버 문제인지 구분할 수 있다.
mysql -u root -p -h localhost
위 명령어를 실행하면 비밀번호를 입력하는 프롬프트가 나타난다. 원격 서버라면 -h 옵션에 서버 IP를 지정한다.
mysql -u myuser -p -h 192.168.1.100 -P 3306
포트가 기본값(3306)이 아니라면 -P 옵션으로 명시한다. 이 명령어가 성공하면 MySQL 서버와 사용자 계정은 정상이고, PHP/애플리케이션 코드에만 문제가 있다는 뜻이다.
만약 1단계에서 실패했다면, MySQL 관리자 권한으로 현재 사용자 목록을 확인해야 한다. localhost에서 root로 접속할 수 있다면 다음 쿼리를 실행한다.
SELECT User, Host, authentication_string FROM mysql.user;
이 쿼리는 mysql 데이터베이스의 user 테이블에서 모든 사용자와 호스트를 나열한다. 당신이 만든 사용자명과 호스트가 정말로 존재하는지 확인한다. 예를 들어 'webapp'@'localhost'를 찾고 있는데 'webapp'@'%'만 있다면, %는 모든 호스트를 의미하므로 localhost에서도 접속 가능해야 한다. 다만 'webapp'@'127.0.0.1'과 'webapp'@'localhost'는 다르므로 주의한다.
사용자 계정이 없거나 비밀번호가 틀렸다면, 다음과 같이 사용자를 생성하거나 비밀번호를 변경한다.
✗ 잘못된 방법 (MySQL 8.0 이상에서는 작동하지 않음)
CREATE USER 'webapp'@'localhost' IDENTIFIED BY PASSWORD 'mypassword';
위 구문은 MySQL 5.7 이상에서 deprecated 되었다. MySQL 8.0+에서는 다음과 같이 해야 한다.
✓ 올바른 방법
CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'mypassword';
GRANT ALL PRIVILEGES ON mydatabase.* TO 'webapp'@'localhost';
FLUSH PRIVILEGES;
FLUSH PRIVILEGES는 메모리에 로드된 권한 테이블을 다시 읽어오므로 반드시 실행해야 한다. 기존 사용자의 비밀번호만 변경하려면 다음과 같이 한다.
ALTER USER 'webapp'@'localhost' IDENTIFIED BY 'newpassword';
FLUSH PRIVILEGES;
MySQL 서버의 사용자 계정은 정상인데 PHP에서 여전히 ERROR 1045가 나면, 연결 코드의 자격증명을 확인한다.
✗ 잘못된 코드 (비밀번호 오타)
<?php
$servername = "localhost";
$username = "webapp";
$password = "mypasswor"; // 오타: mypassword가 아님
$dbname = "mydatabase";
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
?>
위 코드는 비밀번호가 mypasswor로 오타가 있으므로 ERROR 1045가 발생한다. 다음과 같이 수정한다.
✓ 올바른 코드
<?php
$servername = "localhost";
$username = "webapp";
$password = "mypassword"; // 정확한 비밀번호
$dbname = "mydatabase";
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
echo "Connected successfully";
?>
PDO를 사용한다면 다음과 같이 확인한다.
✓ PDO 올바른 연결
<?php
$host = "localhost";
$dbname = "mydatabase";
$username = "webapp";
$password = "mypassword";
try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
echo "Connected successfully";
} catch (PDOException $e) {
die("Connection failed: " . $e->getMessage());
}
?>
원격 서버의 MySQL에 다른 호스트에서 접속할 때 ERROR 1045가 나면, 먼저 사용자가 원격 접속을 허용하도록 생성되었는지 확인해야 한다.
✗ 잘못된 방법 (localhost에서만 접속 가능)
CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'mypassword';
위 사용자는 127.0.0.1이나 localhost에서만 접속할 수 있다. 원격 호스트(예: 192.168.1.50)에서 접속하려면 다음과 같이 해야 한다.
✓ 올바른 방법 (특정 IP에서 원격 접속 허용)
CREATE USER 'webapp'@'192.168.1.50' IDENTIFIED BY 'mypassword';
GRANT ALL PRIVILEGES ON mydatabase.* TO 'webapp'@'192.168.1.50';
FLUSH PRIVILEGES;
특정 IP가 아니라 모든 호스트에서 접속을 허용하려면 %를 사용한다.
✓ 모든 호스트에서 접속 허용
CREATE USER 'webapp'@'%' IDENTIFIED BY 'mypassword';
GRANT ALL PRIVILEGES ON mydatabase.* TO 'webapp'@'%';
FLUSH PRIVILEGES;
단, 원격 접속을 열 때는 보안상 주의가 필요하다. %로 열면 인터넷 어디서나 해당 계정으로 접속 시도가 가능해지므로, 충분히 복잡한 비밀번호를 설정하고, 방화벽에서 MySQL 포트(3306)를 차단하는 것이 좋다. VPN이나 특정 IP 대역만 허용하는 것이 안전하다.
✗ 실수 1: 비밀번호를 코드에 평문으로 저장
데이터베이스 비밀번호를 PHP 코드에 직접 써 놓으면 Git 커밋이나 배포 중에 노출될 위험이 크다. 환경변수나 .env 파일(버전 관리에서 제외)을 사용해 민감한 정보를 분리해야 한다.
✓ 올바른 방법 (.env 파일 사용)
// .env 파일
DB_HOST=localhost
DB_USER=webapp
DB_PASS=mypassword
DB_NAME=mydatabase
// PHP 코드
require 'vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
$host = $_ENV['DB_HOST'];
$user = $_ENV['DB_USER'];
$pass = $_ENV['DB_PASS'];
$dbname = $_ENV['DB_NAME'];
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
✗ 실수 2: 호스트 주소 혼동
'localhost'와 '127.0.0.1'은 다르다. 'localhost'는 먼저 Unix socket으로 연결을 시도하고, 실패하면 TCP 127.0.0.1:3306으로 재시도한다. 특정 주소로 사용자를 제한했다면 호스트명이 정확히 일치해야 한다.
✓ 명시적 주소 지정
// localhost를 명시하면 socket 우선
$conn = new mysqli("localhost", $user, $pass, $dbname);
// 127.0.0.1을 명시하면 TCP 사용
$conn = new mysqli("127.0.0.1", $user, $pass, $dbname);
✗ 실수 3: mysql.user 테이블 직접 수정
과거에는 INSERT나 UPDATE로 직접 user 테이블을 수정했지만, MySQL 5.7.6+ 버전에서는 CREATE USER와 ALTER USER를 써야 비밀번호가 제대로 해싱된다. 직접 수정하면 비밀번호 해시 형식이 맞지 않아서 ERROR 1045가 계속 발생한다.
✗ 잘못된 방법
UPDATE mysql.user SET Password=PASSWORD('newpass') WHERE User='webapp';
FLUSH PRIVILEGES;
✓ 올바른 방법
ALTER USER 'webapp'@'localhost' IDENTIFIED BY 'newpass';
FLUSH PRIVILEGES;
ERROR 1045가 뜨면 다음 순서대로 확인하면 대부분의 경우 해결된다.
1. MySQL 서버가 실행 중인가? (ps aux | grep mysql)
2. 커맨드라인에서 직접 접속 테스트를 했는가?
3. mysql.user 테이블에서 사용자와 호스트가 정확히 존재하는가?
4. PHP/애플리케이션의 사용자명, 비밀번호, 호스트명이 정확한가?
5. 원격 접속이라면 방화벽이 3306 포트를 차단하지 않는가?
6. 환경변수나 .env 파일에서 자격증명을 제대로 읽고 있는가?
7. FLUSH PRIVILEGES를 실행했는가?
이 체크리스트를 따라가면 99%의 ERROR 1045 문제를 해결할 수 있다. MySQL 권한 관리는 처음엔 복잡해 보이지만, (User, Host, Database, Privilege) 조합의 계층 구조를 이해하면 훨씬 명확해진다. 작은 검사 단계가 모여서 큰 버그를 미리 방지하는 효과를 만든다는 점을 잊지 말자. 이 글의 진단 섹션을 참고해 커맨드라인 테스트부터 시작하면, 구체적인 원인을 파악할 수 있을 것이다.