워드프레스를 두 번째로 설치하면서 반년 전에 했던 작업을 그대로 반복하고 있다는 것을 알았다. 기록이 없어 테마, 글 주소 형식, 플러그인 설정을 하나씩 다시 찾았다. 두 번째 설치에도 첫 번째만큼 시간이 들었다.
그래서 Claude Code와 함께 설치 과정을 스크립트로 옮겼다. 지금은 ./setup-local.sh를 실행하고 기다리면 된다. 다만 그 과정이 한 번에 끝나지는 않았다. 인증, 압축 해제, 글 주소 처리에서 막힌 세 가지 문제와 해결 과정을 적었다.
1. 클릭은 기록이 안 남는다
관리자 화면 설치는 편하다. 이름 넣고 설치 누르고 활성화 누르면 끝. 그 순간엔 아무 문제가 없다.
문제는 나중에 온다.
테마가 “GeneratePress 였는지 Astra 였는지”를 확인할 방법이 없어서, 예전 블로그를 열어 푸터에 박힌 테마 이름을 보고 알아냈다. 이런 게 하나가 아니다. 퍼머링크 형식이 뭐였는지, 시간대를 어디서 바꿨는지, 기본 플러그인 중에 뭘 지웠는지. 전부 클릭으로 했으니 어디에도 안 남는다.
더 곤란한 건 빠뜨려도 모른다는 거다. 뭘 빠뜨렸는지 알려면 목록이 있어야 하는데 그 목록이 없다. 설정 하나를 빠뜨린 채로 반년을 굴려도 알아차릴 방법이 없다.
스크립트면 파일이 목록이다.
2. 클릭 여섯 번이 한 줄
wp-cli 라는 게 있다. 관리자 화면에서 마우스로 하던 걸 터미널에서 하게 해준다.
테마 하나 까는 걸로 보면, 화면에서는 외모 → 테마 → 새로 추가 → 검색 → 설치 → 활성화까지 여섯 번을 눌러야 한다. wp-cli로는 이거다.
$WP theme install generatepress --activate
한 줄이고 파일에 남는다. 반년 뒤에 열어보면 그냥 읽으면 된다.
스크립트에 이렇게 적혀 있다.
$WP theme install generatepress --activate
$WP plugin install seo-by-rank-math --activate
$WP plugin install ad-inserter --activate
앞의 $WP는 긴 명령을 미리 담아둔 이름이다. 왜 길어졌는지는 뒤에 나온다. 두 번째로 막힌 곳이 거기다.
설정도 같은 식이다.
$WP option update timezone_string 'Asia/Seoul'
$WP rewrite structure '/%category%/%postname%/' --hard
시간대를 서울로 맞추고 글 주소를 /카테고리/글제목/으로 쓴다는 뜻이다. 화면에서 하려면 메뉴를 따로따로 찾아 들어가야 하는 건데 여기선 두 줄로 나란히 있다.
3. 다시 돌려도 되나
첫 실행은 데이터베이스 만드는 데서 멈췄다. 원인은 뒤에 나올 인증 문제였는데, 그걸 고치고 나서도 바로 다시 돌리지는 못했다.
이미 받아둔 코어는 어떻게 되지? 방금 만들어진 계정은? 다시 돌리면 두 개씩 생기나?
데이터베이스가 꼬이면 원인 찾는 것만 하루다. 그래서 스크립트를 쓰는 내내 이 조건을 먼저 잡았다. 몇 번을 돌려도 결과가 같아야 한다.
이런 걸 멱등성이라고 한다. 열 번 시켜도 한 번 시킨 거랑 결과가 같다는 뜻이다. 세 번 돌려도 데이터베이스가 세 개 생기면 안 된다.
방법은 별거 없다. 하기 전에 이미 되어 있는지 보면 된다.
if [ ! -f "$WP_DIR/wp-load.php" ]; then
$WP core download --locale=ko_KR
fi
wp-load.php는 코어를 풀면 생기는 파일이라 이게 있으면 이미 받은 거다. 그럼 건너뛴다.
설치했는지는 짐작 안 하고 그냥 물어봤다.
if ! $WP core is-installed 2>/dev/null; then
$WP core install --url="$WP_URL" ...
fi
이렇게 해두면 “어디까지 했더라”를 기억하지 않아도 된다. 막히면 고치고 처음부터 다시 돌리면 그만이다.
4. 첫 번째 — 비밀번호를 묻지도 않고 거부
첫 실행에서 이게 떴다.
ERROR 1698 (28000): Access denied for user 'root'@'localhost'
메시지만 보면 비밀번호가 틀린 것 같은데, 비밀번호를 물어보는 절차 자체가 없었다.
root가 두 개다. 데이터베이스 안의 관리자 계정이 하나, 맥 자체의 관리자 사용자가 하나. 이름만 같고 서로 다른 계정이다. 여기서 오해가 생긴다.
Homebrew로 깐 MariaDB는 root 계정에 비밀번호를 안 쓴다. 대신 지금 이 명령을 친 컴퓨터 사용자가 누군지를 본다. unix_socket 인증이라고 한다.
그러니까 데이터베이스의 root는 이 컴퓨터의 root 사용자만 받는데, 명령을 친 계정은 kim이다. 처음부터 통과할 수가 없다.
확인해 보니 kim이름으로 된 데이터베이스 계정이 이미 있었다. 권한도 다 있고.
GRANT ALL PRIVILEGES ON *.* TO `kim`@`localhost` ... WITH GRANT OPTION
mysql -u root를 mysql로 바꿨다. 사용자를 안 적으면 지금 컴퓨터 계정으로 붙는다.
한 단어 지운 게 답이었다.
5. 두 번째 — 압축 풀다가 죽음
데이터베이스 넘기니까 이번엔 이거다.
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted
134217728 바이트면 128MB다. PHP는 프로그램 하나가 쓸 수 있는 메모리에 상한을 둔다. 워드프레스 압축 파일 안에 파일이 수천 개인데 그걸 한 번에 푸는 과정에서 이 상한에 걸린다.
방법은 두가지 정도가 있었다. php.ini 열어서 상한을 아예 올리거나, 이 명령 돌릴 때만 올리거나. 후자로 선택했다.
WP="php -d memory_limit=512M $ROOT/bin/wp.phar --path=$WP_DIR"
앞에서 말한 $WP가 이거다. -d memory_limit=512M은 이 명령에만 붙으니까 설정 파일은 안 건드린다. 블로그 하나 깔자고 시스템 설정을 바꿀 이유는 없다.
6. 세 번째 — 글이 전부 404
설치가 끝나고 첫 화면도 잘 떴는데, 글 제목을 누르니까 404가 나왔다.
앞의 둘과 달리 이건 단서가 없다. 그쪽은 에러 메시지가 원인을 알려주는데 이건 그냥 없는 페이지라고만 한다.
주소창에 치는 게 항상 서버에 있는 파일은 아니다. /미분류/글제목/이라는 주소에 맞는 폴더나 파일은 서버 어디에도 없다.
아파트로 치면, “3층 김씨댁”이라고 적힌 우편함은 없다. 편지는 관리실이 다 받아서 “3층 김씨댁이면 302호구나” 하고 나눠주지 않나.
워드프레스에서 그 관리실이 index.php 하나다. 모든 요청을 이게 받아서 주소를 해석하고 알맞은 글을 찾아준다. Apache나 nginx는 “없는 주소는 전부 index.php로 보내라”는 규칙을 알고 있고, 워드프레스가 만들어주는 .htaccess에 그게 들어 있다.
그런데 PHP 내장 서버에는 그게 없다. 주소에 맞는 파일 찾다가 없으면 바로 404다. 관리실이 없는 아파트인 셈이다.
그래서 만들었다. 열 줄이면 됐다.
<?php
// PHP 내장 서버에서 WordPress 퍼머링크를 동작시키는 라우터
$root = __DIR__ . '/wordpress';
$path = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
$file = $root . $path;
if ( $path !== '/' && file_exists( $file ) && ! is_dir( $file ) ) {
return false; // 실제 파일은 그대로 서빙
}
$_SERVER['SCRIPT_NAME'] = '/index.php';
require $root . '/index.php';
요청 들어오면 그 주소에 진짜 파일이 있는지 먼저 본다. 이미지나 CSS처럼 있는 파일이면 그대로 내보내고(return false), 없으면 워드프레스 index.php로 넘긴다.
서버 띄울 때 이 파일을 마지막에 붙인다.
php -S localhost:8080 -t "$ROOT/wordpress" "$ROOT/router.php"
이거 붙이고 나서야 글이 열렸다.
7. 이 스크립트의 한계
로컬이 실서버랑 다르다. PHP 내장 서버는 개발용이라 동시 접속을 못 견딘다. 실서버에서는 nginx랑 PHP-FPM 쓸 텐데 그러면 router.php는 필요 없어지고 대신 nginx 설정이 필요하다. 로컬에서 되던 게 서버에서 다르게 굴러갈 여지가 남는다.
이 컴퓨터에서만 돈다. 경로가 /opt/homebrew로 박혀 있다. Apple Silicon 맥에서만 그대로 실행된다는 뜻이다. Intel 맥은 /usr/local이고 리눅스는 구조가 아예 다르고.
관리할 게 하나 늘었다. 예전엔 워드프레스만 챙기면 됐는데 이제 스크립트도 챙긴다. 워드프레스 바뀌면 스크립트도 따라 고쳐야 한다.
8. 지금 하는 일
순서는 이렇다.
- 비밀번호를 무작위로 만들어
.env에 넣는다 (처음 한 번만) - MariaDB를 켜고 응답할 때까지 기다린다
- 데이터베이스와 전용 계정을 만든다
- 워드프레스 코어를 한국어판으로 받는다
wp-config.php를 만든다 (파일 편집 차단, 리비전 5개 제한)- 워드프레스를 설치한다
- 시간대·날짜 형식·글 주소를 잡는다
- 기본 샘플 글과 안 쓰는 테마·플러그인을 지운다
- 테마를 깔고 켠다
- 플러그인을 깔고 켠다
ads.txt자리를 만들어 둔다
안 한 것도 있다. 실서버용은 따로 만들어야 하고, macOS 밖에서 도는지는 안 봤다.
9. 남은 것
설치 시간이 얼마나 줄었는지는 측정하지 못했다.
같이 해보니 막힌 자리를 좁히는 건 확실히 빨랐다. 에러 메시지를 그대로 붙여넣으면 원인 후보를 몇 개 짚어주니까 하나씩 확인만 하면 됐다. 다만 세 번째 404는 테마 문제로 잘못 짚어서 한 번 돌아갔다. .htaccess와 PHP 내장 서버의 차이를 직접 읽고 나서 풀렸다.
실서버용을 만들 때도 처음부터 다시 알아볼 필요는 없다. 로컬 스크립트에 뭘 어떤 순서로 하는지 다 적혀 있으니까 거기서 이어 나가면 된다.
비밀번호가 든 .env는 저장소에 올리지 않았다. 스크립트가 만들어 넣으니까 파일만 빼면 됐고, 대신 어떤 항목이 필요한지 적은 .env.example을 올려뒀다.
전체 코드는 저장소에 있다.

답글 남기기