증상과 정확한 오류
WordPress 플러그인 업데이트 뒤 사이트와 WP-CLI가 같은 fatal error로 멈추면 일반 wp plugin deactivate나 롤백 명령도 부팅 단계에서 실패할 수 있습니다. 이때는 문제 플러그인 로드를 건너뛰고 정확한 slug와 버전을 확인한 뒤, 파일 복원이 데이터베이스 변경까지 되돌리지는 않는다는 전제로 복구해야 합니다.
WP-CLI도 명령에 따라 WordPress를 부팅하고 활성 플러그인을 로드합니다. 문제 플러그인이 모든 bootstrap에서 즉시 fatal error를 내면, 그 플러그인을 비활성화하려는 명령까지 플러그인 코드에 막힐 수 있습니다. 이때 핵심은 파일을 먼저 삭제하는 것이 아니라 --skip-plugins로 일반 플러그인 로드를 건너뛴 상태에서 현재 버전과 활성 상태를 확인하는 것입니다.
또한 플러그인 파일을 이전 버전으로 되돌리는 것과 데이터베이스를 되돌리는 것은 별개입니다. 업데이트 과정에서 schema나 option 형식이 바뀌었다면 코드 롤백만으로 완전히 복구되지 않을 수 있습니다. 그래서 명령을 실행하기 전에 데이터베이스와 현재 플러그인 디렉터리를 함께 보관해야 합니다.
플러그인 로드 우회가 실제로 복구한 범위
2026-09-13 KST에 운영 Oracle과 별개인 Linux 호스트에서 WordPress 7.1, PHP 8.3.33, WP-CLI 2.12.0, MariaDB 11.4.13으로 작은 복구 실험을 했습니다. 새 데이터베이스와 테스트 관리자만 만들고, 외부 네트워크와 공개 포트는 연결하지 않았습니다. 아래 결과는 실제 사이트 장애 이력이 아니라, 일반 플러그인이 WordPress 부팅을 막는 조건을 의도적으로 만든 격리 실험입니다.
테스트 플러그인 nole-lab-fault 1.0은 처음에는 아무 기능도 하지 않습니다. 활성화한 뒤 같은 디렉터리에 fail.flag가 존재하면 예외를 내도록 했습니다. 운영 사이트나 실제 플러그인에 이 코드를 넣으면 안 됩니다. 실험용 WordPress의 wp-content/plugins/nole-lab-fault/nole-lab-fault.php에만 사용한 코드입니다.
<?php
/* Plugin Name: Nole lab fault
Version: 1.0 */
if (file_exists(__DIR__.'/fail.flag')) { throw new RuntimeException('NOLE_LAB_BOOT_FAILURE'); }
처음의 플러그인 활성화는 종료 코드 0이었습니다. 그 뒤 실험용 flag를 만든 상태에서 아래 명령을 실행했습니다. 표에서는 컨테이너 실행 접두부를 생략하고 WP-CLI 명령만 적었습니다. 전체 실행 기록에는 명령, 출력, 종료 코드가 함께 남아 있습니다.
| 상태와 명령 | 실제 결과 | 알 수 있는 것 |
|---|---|---|
장애 상태의 wp plugin list | 종료 1, NOLE_LAB_BOOT_FAILURE | 일반 플러그인 로드에서 부팅이 중단됨 |
같은 상태의 wp core version | 종료 0, 7.1 | 버전 조회는 부팅 성공을 증명하지 않음 |
| 장애 상태의 홈 요청 | HTTP 500 | 웹 경로에도 오류가 있음 |
wp plugin list --skip-plugins | 종료 0, 테스트 플러그인 active | 일반 플러그인 로드를 우회하면 상태를 읽을 수 있음 |
wp plugin deactivate nole-lab-fault --skip-plugins | 종료 0, deactivated | 문제 코드 로드를 피해서 비활성화함 |
우회 옵션 없는 wp eval | 종료 0, bootstrap-ok | 일반 부팅 경로가 다시 통과함 |
| 복구 후 웹·REST·관리자 | 홈 200, REST에서 테스트 글 조회, authenticated-dashboard=yes | 세 경로에서 관찰한 기능이 복구됨 |
실제로 실패한 경로의 오류는 다음과 같았습니다. 긴 stack trace 중 오류 종류와 실험용 파일만 남겼습니다.
PHP Fatal error: Uncaught RuntimeException: NOLE_LAB_BOOT_FAILURE
in /var/www/html/wp-content/plugins/nole-lab-fault/nole-lab-fault.php:4
wp plugin deactivate nole-lab-fault --skip-plugins
# Success: Deactivated 1 of 1 plugins.
wp eval 'echo "bootstrap-ok\n";'
# bootstrap-ok
wp plugin status nole-lab-fault --skip-plugins
# Status: Inactive
플러그인 파일과 flag는 비활성화 후에도 남겨 둔 채 복구를 확인했습니다. 즉, 파일을 삭제해서 우연히 정상화한 결과가 아닙니다. 다만 문제 플러그인이 비활성 상태이므로 그 플러그인의 기능이 되살아났다는 뜻도 아닙니다. 실제 서비스라면 우선 기본 접속을 회복한 뒤, 검증한 정상 코드와 현재 DB의 호환성을 따로 확인해야 합니다.
이 실험에서는 테스트 글이 REST에 나타나는지와 테스트 관리자 인증 후 대시보드의 로그아웃 항목이 있는지까지 확인했습니다. 상용 플러그인 기능, 결제, 예약 작업 실행, mu-plugin 오류, 테마 오류, DB schema 롤백, Recovery Mode 이메일은 시험하지 않았습니다. 홈 200과 관리자 로그인만으로 그런 기능까지 복구됐다고 기록하지 않습니다.
실험 준비 중에는 DB의 초기화용 임시 서버가 응답한 시점에 WordPress 설치를 시도해 DB 연결 오류가 한 번 있었습니다. 별도 새 실험 DB에서 TCP 준비를 확인한 뒤 설치를 다시 진행했습니다. 이것은 환경 준비 실패이며 플러그인 장애 결과와 구분했습니다. 실험을 마친 컨테이너는 제거했습니다.
먼저 확인할 값
| 확인 지점 | 명령 또는 자료 | 판단 기준 |
|---|---|---|
| 첫 fatal error | PHP-FPM 로그 또는 wp-content/debug.log | 마지막 연쇄 오류보다 처음 나온 파일 경로, 줄 번호, 예외 종류로 문제 플러그인을 식별합니다. |
| 플러그인 로드 우회 | wp plugin list --skip-plugins | 이 명령은 성공하고 일반 명령만 실패하면 활성 플러그인 bootstrap 문제로 범위가 좁혀집니다. |
| 현재 버전 | wp plugin get plugin-slug --skip-plugins | 업데이트된 버전과 활성 상태를 기록해 정확한 이전 버전을 선택합니다. |
| mu-plugin 영향 | wp plugin list --status=must-use --skip-plugins | --skip-plugins를 써도 must-use 플러그인은 로드될 수 있어 오류 경로가 mu-plugins면 별도 조치가 필요합니다. |
| Recovery Mode | 관리자 이메일의 복구 링크 | 복구 모드로 관리자 화면이 열리면 문제 플러그인을 안전하게 비활성화할 수 있습니다. |
plugin-slug는 로그의 디렉터리명과 wp plugin list --skip-plugins 결과로 확인합니다. 표시 이름만 보고 비슷한 다른 플러그인을 대상으로 삼지 않습니다. 로그가 테마나 PHP 확장, core 파일을 가리킨다면 이 절차를 플러그인 롤백으로 강행하지 않습니다.
원인 분기
WordPress WP-CLI 롤백이 부팅에서 막히는 이유
--skip-plugins에서 WP-CLI가 정상: 일반 플러그인 중 하나가 bootstrap을 막는 경우입니다. 로그에서 확인한 플러그인을 우선 비활성화하고 버전을 복원합니다.--skip-plugins에서도 같은 fatal error: must-use 플러그인, 활성 테마, core, PHP preload나 확장 문제일 수 있습니다.--skip-themes로 테마를 구분할 수 있지만 로그 경로가 확인되지 않은 상태에서 파일을 임의로 옮기지 않습니다.- Recovery Mode는 열림: 관리자 화면에서 오류로 일시 중단된 플러그인을 확인하고 비활성화할 수 있습니다. 복구 모드는 문제 원인을 영구 수정하는 기능이 아니므로 이후 버전 복원과 검증이 필요합니다.
- 이전 코드에서도 데이터 오류: 플러그인 업데이트가 데이터베이스 migration을 실행했다면 파일만 내린 뒤에도 쿼리 오류나 옵션 역직렬화 오류가 남을 수 있습니다. 플러그인 문서의 하위 호환 여부를 확인하고 백업 DB 복원 여부를 결정합니다.
- 업데이트 파일이 불완전: 배포 중 연결 종료나 디스크 부족으로 새 버전 파일 일부만 바뀐 경우에는 같은 버전을 깨끗하게 다시 설치해도 해결될 수 있습니다. 이전 버전 롤백과 불완전 설치 복구를 구분합니다.
수정 순서
- 사이트 쓰기를 멈추고 두 종류의 백업을 만듭니다. DB와 플러그인 파일을 웹 루트 밖의 접근 제한 디렉터리에 보관합니다. WordPress 디렉터리에서 파일명만 지정해 export하면 SQL이 공개 경로에 생길 수 있습니다. 아래 예제의
BACKUP_DIR은 실제 웹 서버가 제공하지 않는 디렉터리로 먼저 준비하고 권한을 확인해야 합니다. WP-CLI DB export도 실패하면 호스팅 또는 DB 서버의 검증된 백업 수단을 사용합니다. - 문제 플러그인과 버전을 확정합니다. 첫 fatal error의 파일 경로를 보고
wp plugin get plugin-slug --skip-plugins결과를 백업 기록에 남깁니다. 업데이트 직전 정상 버전은 배포 기록이나 플러그인 릴리스 기록에서 확인합니다. - 플러그인 로드를 우회해 비활성화합니다.
wp plugin deactivate plugin-slug --skip-plugins를 실행한 뒤wp plugin status plugin-slug --skip-plugins에서 inactive인지 확인합니다. 비활성화가 성공하기 전에 디렉터리를 삭제하지 않습니다. - 이전 버전을 명시적으로 다시 설치합니다. 공식 저장소에 필요한 버전이 있다면
wp plugin install plugin-slug --version=1.2.3 --force --skip-plugins처럼 버전을 고정합니다. 상용 플러그인은 검증된 배포 ZIP과 공급자의 설치 절차를 사용하고 출처가 불명확한 파일을 넣지 않습니다. - 비활성 상태에서 기본 부팅을 확인합니다. 문제 플러그인을 비활성화한 뒤
wp eval 'echo "bootstrap-ok\n";'를--skip-plugins없이 실행합니다.wp core version은 WordPress 로드 전 버전만 읽으므로 부팅 성공의 증거가 아닙니다. 홈, 관리자 로그인, PHP 로그도 확인한 뒤 스테이징에서 해당 버전과 현재 DB 조합을 검사합니다. - 검증된 뒤에만 다시 활성화합니다. 스테이징 검증 후 유지보수 시간에
wp plugin activate plugin-slug를 실행하고 즉시 로그와 핵심 기능을 확인합니다. 활성화 대상 플러그인의 코드는 실행되므로--skip-plugins가 위험한 활성화를 안전하게 만들어 주지는 않습니다. fatal error가 다시 나오면 반복하지 말고wp plugin deactivate plugin-slug --skip-plugins로 보류한 뒤 DB 호환성과 의존 플러그인을 조사합니다.
다음은 WordPress 루트에서 실행하는 Bash 스크립트입니다. BACKUP_ROOT는 미리 준비한 접근 제한 경로로 지정하고 웹 서버 alias 등으로도 공개되지 않는지 확인합니다. plugin-slug는 확인한 대상 이름으로 바꿉니다. 매 실행마다 mktemp가 고유 디렉터리를 만들며, 기존 백업 파일을 덮어쓰지 않습니다. SQL과 로그에는 비밀값이 있을 수 있으므로 공개하거나 AI 대화에 붙이지 않습니다.
#!/usr/bin/env bash
set -eu
umask 077
# Run from the WordPress root. Prepare a private, non-web-served parent first.
BACKUP_ROOT=${BACKUP_ROOT:-/srv/private-backups/wordpress}
test -d "$BACKUP_ROOT" && test -w "$BACKUP_ROOT" || exit 1
WP_ROOT=$(pwd -P)
BACKUP_ROOT=$(cd "$BACKUP_ROOT" && pwd -P)
case "$BACKUP_ROOT/" in "$WP_ROOT/"*) printf '%s\n' 'STOP: backup path is inside the web root' >&2; exit 1 ;; esac
BACKUP_DIR=$(mktemp -d "$BACKUP_ROOT/plugin-rollback.XXXXXXXX")
wp db export "$BACKUP_DIR/before-plugin-rollback.sql" --skip-plugins >"$BACKUP_DIR/export.log" 2>&1 || {
printf '%s\n' 'STOP: database export failed; inspect the private log' >&2; exit 1;
}
test -s "$BACKUP_DIR/before-plugin-rollback.sql" || exit 1
tar -C wp-content/plugins -czf "$BACKUP_DIR/plugin-slug-before-rollback.tar.gz" plugin-slug >"$BACKUP_DIR/archive.log" 2>&1 || {
printf '%s\n' 'STOP: plugin archive failed; backup set is incomplete' >&2; exit 1;
}
tar -tzf "$BACKUP_DIR/plugin-slug-before-rollback.tar.gz" >/dev/null || exit 1
(cd "$BACKUP_DIR" && sha256sum before-plugin-rollback.sql plugin-slug-before-rollback.tar.gz > SHA256SUMS) || exit 1
printf '%s\n' 'Backup files created and archive readable; restore remains untested'
export 또는 압축이 실패하면 즉시 종료하고 이후 플러그인 변경을 진행하지 않습니다. 실패한 디렉터리와 private 로그는 조사용으로 남습니다. SHA256SUMS는 두 파일 생성과 압축 목록 읽기 이후에만 만들어지며, DB 복원 성공 표시는 아닙니다. 별도 환경에서 SQL import와 플러그인 파일 복원 가능성을 확인해야 합니다. 이번 추가 시험은 더미 wp 명령의 성공·실패와 실제 tar로 셸의 중단·고유 경로·권한을 확인한 범위이며, 아래의 기존 WordPress 부팅 실험과 구분합니다.
검증과 롤백
복구 성공은 홈 화면 200 한 번으로 판단하지 않습니다. 관리자 로그인, 플러그인이 제공하는 핵심 화면이나 예약 작업, REST 요청, PHP 로그를 함께 확인합니다. wp plugin get plugin-slug --skip-plugins에서 의도한 버전과 active 상태가 맞고, 새 fatal error가 없어야 합니다.
- 업데이트 전부터 사용하던 기능을 실제 계정과 최소 권한 계정으로 각각 확인합니다.
wp cron event list --skip-plugins에서 플러그인 관련 작업이 비정상적으로 누적되지 않았는지 봅니다.- 데이터베이스 오류가 있으면 파일 버전을 다시 바꾸기 전에 백업 시점과 migration 여부를 확인합니다.
- 자동 업데이트를 잠시 중지하더라도 원인 버전이 수정되면 검증 후 정상 업데이트 정책으로 되돌립니다.
이전 버전에서도 문제가 계속되고 업데이트가 DB 구조를 변경한 사실이 확인되면, 현재 DB를 별도 보관한 뒤 before-plugin-rollback.sql과 플러그인 파일 백업을 같은 시점으로 복원합니다. 다른 글이나 주문처럼 백업 이후 생성된 데이터가 있다면 전체 DB 덮어쓰기를 서두르지 말고 관련 테이블과 서비스 영향부터 확인합니다.
같이 확인할 글
공식 문서 및 확인 기준
- WP-CLI core version
- WP-CLI eval
- WP-CLI db export
- WordPress Recovery Mode — 치명적 오류가 발생한 관리자 화면에 제한적으로 접속해 문제 플러그인을 비활성화하는 복구 모드
- WP-CLI plugin deactivate — 치명적 오류를 일으킨 플러그인을 로드하지 않고 비활성화하는 WP-CLI 명령
- WP-CLI plugin install — 지정 버전 플러그인을 강제로 다시 설치하는 WP-CLI 롤백 경로
- WP-CLI plugin update — 플러그인 버전 지정·dry-run과
--skip-plugins전역 옵션을 사용하는 업데이트 명령
이 글에 남아 있던 이전 검증 기록
작성: Nole IT 운영자
검증 수준: 공식 문서 대조
복구 명령은 WordPress Recovery Mode와 현재 WP-CLI 명령 문서를 대조했습니다. 버전을 강제로 바꾸기 전에 데이터베이스와 플러그인 디렉터리를 백업하세요.