
워드프레스를 두 번째로 깔면서 반년 전에 한 걸 그대로 다시 하고 있다는 걸 알았다.
반년 전에 한 번 해 봤지만 남은 기록이 없었다. 테마를 뭘 골랐는지, 글 주소를 어떻게 잡았는지, 플러그인은 뭘 껐는지를 하나씩 다시 찾았고, 두 번째 설치에 든 시간이 첫 번째와 같았다. 이전 작업에서 설정 방법을 남기지 않은 탓이었다.
그래서 스크립트로 옮겼다. 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 을 올려뒀다.
전체 코드는 저장소에 있다.