누군가 새 서버를 받아서 PHP 코드를 띄우려는 순간, 반갑지 않은 빨간 에러가 화면을 덮는다. 'ERROR 1045: Access denied for user'. 대부분의 개발자는 이 메시지를 본 순간 비상이다. 데이터베이스 연결이 끊겼다는 뜻이니까. 다만 원인이 정말 다양해서, 유명한 스택오버플로우 글을 복사하거나 무작정 권한을 777로 풀어버린다. 이번에는 이 에러가 정확히 뭔지, 왜 발생하는지, 어떻게 제대로 해결하는지 완벽하게 정리해서 소개하겠다.
MySQL ERROR 1045는 단순하다. 입력한 사용자명(username)과 비밀번호(password)가 MySQL 서버에 없거나, 있어도 접근하려는 호스트(host)에서 권한이 없다는 뜻이다. MySQL은 사용자를 '사용자명@호스트' 조합으로 관리한다. 예를 들어 'root@localhost'와 'root@127.0.0.1'은 다른 사용자다. 그래서 올바른 비밀번호를 입력했어도 호스트가 맞지 않으면 접근이 거부된다.
더 복잡한 경우는 PHP가 실행되는 서버(웹 서버)와 MySQL이 실행되는 서버가 다를 때다. 로컬에서는 'localhost'로 연결되지만, 원격 서버에서는 '192.168.1.100' 같은 IP 주소로 연결해야 한다. 여기서 실수하면 ERROR 1045가 난다.
먼저 어디서 연결이 실패하는지 확인해야 한다. PHP 코드에서 에러 메시지를 정확히 출력해보자.
<?php
// ✗ 잘못된 방법: 에러를 무시하고 진행
$conn = mysqli_connect('localhost', 'root', 'password', 'mydb');
if (!$conn) {
echo 'Connection failed';
}
// ✓ 올바른 방법: 정확한 에러 메시지 출력
$conn = mysqli_connect('localhost', 'root', 'password', 'mydb');
if (!$conn) {
echo 'Connection failed: ' . mysqli_connect_error();
echo ' (Error Code: ' . mysqli_connect_errno() . ')';
exit;
}
?>
이렇게 출력하면 'Access denied for user 'root'@'localhost' (using password: YES)'처럼 구체적인 메시지가 나온다. '(using password: YES)'는 비밀번호를 입력했다는 뜻이다. 만약 'using password: NO'라면 비밀번호를 전달하지 않았다는 뜻이다.
가장 흔한 원인은 비밀번호 오타다. MySQL에 직접 접속해서 실제 비밀번호가 맞는지 테스트해보자.
# 터미널에서 직접 접속 테스트
mysql -u root -p -h localhost
# 비밀번호 입력 프롬프트가 나타나면 비밀번호 입력
# 또는 비밀번호를 인라인으로 입력 (보안상 추천하지 않음)
mysql -u root -p'your_password' -h localhost
터미널에서 접속되면 비밀번호는 맞다. 문제는 PHP 코드 어딘가에 있다는 뜻이다. 다음을 확인해보자.
| 확인 항목 | 예시 | 주의사항 |
|---|---|---|
| 특수문자 이스케이프 | 비밀번호가 'pass@123'이면 'pass@123'로 그대로 사용 | PHP 문자열에서 특수문자 때문에 잘못 파싱될 수 있음 |
| 여백 문자 | 복사할 때 앞뒤 공백 확인 | 'root ' (뒤에 공백)과 'root'는 다른 사용자 |
| 따옴표 타입 | 작은따옴표(' ')와 큰따옴표(" ") 혼용 금지 | 직접 입력 vs 복사로 인한 따옴표 문자 달라짐 |
비밀번호가 맞는데도 ERROR 1045가 난다면, 호스트 권한 설정을 확인해야 한다. MySQL에 관리자 권한으로 접속해서 사용자 권한을 조회해보자.
-- MySQL에 root로 접속한 후 실행
SELECT user, host FROM mysql.user WHERE user = 'myuser';
-- 예상 결과
-- +--------+-----------+
-- | user | host |
-- +--------+-----------+
-- | myuser | localhost |
-- | myuser | % |
-- +--------+-----------+
'localhost' 항목은 로컬(같은 서버)에서만 접근 가능하다. 웹 서버가 별도 서버에 있다면 '%'(모든 호스트) 또는 특정 IP(예: '192.168.1.50')를 추가해야 한다.
-- 현재 권한 확인
SHOW GRANTS FOR 'myuser'@'localhost';
-- 만약 사용자가 없으면 생성
CREATE USER 'myuser'@'localhost' IDENTIFIED BY 'password';
-- 웹 서버가 다른 머신에 있다면 추가 사용자 생성
CREATE USER 'myuser'@'192.168.1.50' IDENTIFIED BY 'password';
-- 또는 모든 호스트에서 접근 허용 (운영 환경에서는 피할 것)
CREATE USER 'myuser'@'%' IDENTIFIED BY 'password';
-- 데이터베이스 권한 부여
GRANT ALL PRIVILEGES ON mydb.* TO 'myuser'@'localhost';
GRANT ALL PRIVILEGES ON mydb.* TO 'myuser'@'192.168.1.50';
-- 권한 적용
FLUSH PRIVILEGES;
이제 정확한 연결 코드를 작성해보자. 먼저 MySQLi의 절차형 방식이다.
<?php
// 연결 정보
$servername = 'localhost';
$username = 'myuser';
$password = 'password';
$dbname = 'mydb';
// 연결
$conn = mysqli_connect($servername, $username, $password, $dbname);
if (!$conn) {
die('Connection failed: ' . mysqli_connect_error());
}
echo 'Connected successfully';
mysqli_close($conn);
?>
이제 객체지향 방식(권장)이다.
<?php
$servername = 'localhost';
$username = 'myuser';
$password = 'password';
$dbname = 'mydb';
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die('Connection failed: ' . $conn->connect_error);
}
echo 'Connected successfully';
$conn->close();
?>
마지막으로 PDO 방식이다(가장 권장).
<?php
$servername = 'localhost';
$dbname = 'mydb';
$username = 'myuser';
$password = 'password';
try {
$conn = new PDO(
'mysql:host=' . $servername . ';dbname=' . $dbname . ';charset=utf8mb4',
$username,
$password,
array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)
);
echo 'Connected successfully';
} catch (PDOException $e) {
die('Connection failed: ' . $e->getMessage());
}
?>
✗ 잘못된 호스트명: 'mysql.example.com'으로 원격 접속하려는데 MySQL이 외부 접근을 허용하지 않은 경우
✓ 올바른 해결법: MySQL에서 bind_address를 확인하고 필요시 변경
# MySQL 설정 파일 확인 (Linux)
grep -n 'bind_address' /etc/mysql/mysql.conf.d/mysqld.cnf
# 결과가 'bind_address = 127.0.0.1'이면 localhost만 허용
# 모든 인터페이스 허용으로 변경
# bind_address = 0.0.0.0
# 또는 특정 IP만 허용
# bind_address = 192.168.1.100
# MySQL 재시작
sudo systemctl restart mysql
✗ 잘못된 방법: 비밀번호를 코드에 하드코딩
✓ 올바른 방법: 환경변수나 .env 파일 사용
<?php
// .env 파일에서 읽기 (dotenv 라이브러리 사용)
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
$username = $_ENV['DB_USER'];
$password = $_ENV['DB_PASS'];
$servername = $_ENV['DB_HOST'];
$conn = new mysqli($servername, $username, $password);
?>
에러가 계속 나면 이 순서로 확인해보자.
1. 터미널에서 직접 MySQL 접속 가능한가?
mysql -u myuser -p -h localhost mydb
2. 사용자가 정확한 호스트에 존재하는가?
SELECT user, host FROM mysql.user WHERE user = 'myuser';
3. 데이터베이스 선택 권한이 있는가?
SHOW GRANTS FOR 'myuser'@'localhost';
4. PHP에서 전달하는 호스트명이 정확한가? (localhost vs 127.0.0.1은 다를 수 있음)
5. MySQL 서버가 실제로 실행 중인가?
ps aux | grep mysql
# 또는
sudo systemctl status mysql
6. 방화벽이 3306 포트를 막지는 않는가?
sudo ufw status
# 필요시 3306 포트 열기
sudo ufw allow 3306/tcp
MySQL ERROR 1045는 권한 문제다. 비밀번호, 사용자명, 호스트 조합이 정확해야 한다. 이 글에서 다룬 진단 체크리스트를 따라가면, 90% 이상의 경우 원인을 찾을 수 있다. 특히 원격 서버에서는 호스트 권한과 bind_address 설정이 가장 흔한 걸림돌이다. 무작정 권한을 777로 풀거나 비밀번호를 코드에 박아넣지 말고, 정확한 원인을 진단한 후 최소 권한 원칙에 따라 설정하자. 그것이 보안과 운영 안정성을 모두 지키는 방법이다.