새 서버에서 홈페이지가 정상적으로 열린다고 해서 WordPress 이전이 끝난 것은 아니다. 그 상태는 대개 파일과 데이터베이스가 새 환경에서도 읽힌다는 첫 번째 확인에 불과하다.
안전한 이전은 이전 직전까지 생성된 데이터가 모두 반영됐는지, 실제 트래픽이 새 환경으로 들어오는지, TLS·메일·Cron·캐시·외부 연동이 새 환경에서 동작하는지, 문제가 생겼을 때 어느 시점까지 어떻게 되돌릴지를 검증해야 완료된다. 마지막에는 기존 환경의 쓰기를 막고 백업·자격증명·DNS·방화벽·모니터링까지 정리해야 한다.
따라서 WordPress 이전은 파일 복사 → DB Import가 아니라 다음 다섯 단계로 관리하는 편이 안전하다.
Preflight → Rehearsal → Cutover → Validation → Decommission
2026년 8월 기준 WordPress 실행 환경
2026년 8월 21일 기준 WordPress 최신판은 8월 19일 공개된 7.1이다. 공식 Release Archive의 7.1 Branch에는 아직 7.1만 등록돼 있어, 이 시점에는 7.1 계열 유지보수 릴리스가 확인되지 않는다. (WordPress.org)
| 항목 | 현재 공식 상태 | 새 이전 대상에 적용할 기준 |
|---|---|---|
| WordPress Core | 최신판 7.1 | 최신판이라는 이유만으로 이전과 동시에 올리지 않는다 |
| PHP 공개 권장 | 8.3 이상 | 최소선이 아니라 현대적 권장 기준 |
| PHP 운영 권장 | Hosting Handbook은 8.4 이상 권장 | 전체 Plugin·Theme·Custom Code 검증 후 8.4를 기본 후보로 삼는다 |
| PHP Core 최소 | WordPress 7.1은 PHP 7.4 이상 | 실행 가능 하한일 뿐 신규 운영 환경의 목표 버전으로 보지 않는다 |
| PHP 8.5 | WordPress Core 7.1이 지원하며 PHP Project의 Active Support 상태 | 전체 확장 코드와 PHP Extension 검증을 통과했을 때 선택 |
| DB 공개 권장 | MariaDB 10.11+ 또는 MySQL 8.0+ | DB 제품의 현재 지원 수명까지 함께 확인한다 |
| DB Legacy 하한 | MySQL 5.5.5+에서 실행 가능 | EOL이므로 신규 대상에 사용하지 않는다 |
| 현재 LTS 후보 | MySQL 8.4, MariaDB 10.11·11.4·11.8 | Plugin·Custom Query·문자셋·Collation·SQL Mode 테스트 후 결정 |
| HTTPS | WordPress Requirements의 기본 조건 | 인증서 발급뿐 아니라 Redirect·Proxy Header·Callback URL까지 검증 |
WordPress Hosting Handbook은 PHP 8.4 이상을 운영 환경에 권장하고 PHP 8.5도 WordPress 6.9 이상에서 지원한다고 설명한다. PHP 8.2와 8.3은 PHP Project 기준 Security Support 단계이며 8.4와 8.5는 Active Support 상태다. (Make WordPress)
DB에서는 문서 사이의 기준을 구분해야 한다. WordPress Requirements의 MySQL 8.0+는 넓은 호환 기준이지만 MySQL 8.0의 EOL 날짜는 이미 2026년 4월을 지났다. MariaDB 10.6 역시 Hosting Handbook에 기록된 EOL 날짜가 2026년 7월 6일이다. 신규 환경은 현재 지원 중인 LTS 계열을 선택하되, “WordPress Core가 지원한다”는 사실을 Plugin이나 Custom Query까지 자동으로 호환된다는 뜻으로 해석하면 안 된다. (WordPress.org)
Core 업그레이드와 인프라 이전을 한 번에 하지 않는 이유
다음 변경을 하나의 Cutover에 묶는 경우를 생각해 보자.
- 서버와 OS 변경
- PHP 8.1에서 8.5로 변경
- MySQL 8.0에서 8.4로 변경
- WordPress 7.0에서 7.1로 변경
- Plugin과 Theme 동시 업데이트
오류가 발생하면 원인이 서버 구성인지, PHP 호환성인지, DB 동작 차이인지, Core 변경인지, Plugin 업데이트인지 분리하기 어렵다. 더 큰 문제는 일부 업데이트가 DB 구조나 저장 데이터를 변경한 뒤에는 기존 환경으로 트래픽만 되돌리는 방식이 안전한 롤백이 아닐 수 있다는 점이다.
WordPress 7.1 Field Guide도 일부 내장 라이브러리 변경이 이를 사용하는 Plugin의 동작이나 스타일에 영향을 줄 수 있으므로 테스트가 필요하다고 설명한다. WordPress Core의 PHP 호환성 표는 Core 범위의 판단이며 설치된 Plugin·Theme·MU Plugin·Custom Code의 호환성을 보증하지 않는다. (Make WordPress)
안전한 기본 순서는 다음과 같다.
- 기존 WordPress·Plugin·Theme 버전을 기준으로 새 인프라를 먼저 Rehearsal한다.
- 인프라 이전을 완료하고 Observation Window를 통과한다.
- Core, PHP, DB 또는 Plugin 업그레이드를 별도 변경으로 수행한다.
기존 PHP나 DB가 새 환경에서 제공되지 않아 변경이 불가피하다면, 이를 단순 서버 이전이 아니라 인프라 이전과 런타임 업그레이드가 결합된 변경으로 기록해야 한다. 환경별 Acceptance Criteria와 Rollback Trigger도 각각 나눈다.
Bulk Copy → Final Sync → Traffic Cutover
구조 다이어그램
기존 환경 — Read / Write
│
│ ① Bulk Copy
│ 파일·Uploads·DB 초기 복사
▼
새 환경 — Rehearsal
외부 메일·결제·Webhook·Cron은 차단 또는 테스트 모드
│
│ 기존 환경에서는 실제 변경이 계속 발생
│
▼
Write Freeze
관리자·폼·댓글·REST API·결제 Callback·Cron·Queue의 쓰기 통제
│
│ ② Final Sync
│ 최종 DB + Uploads Delta + 기타 가변 파일
▼
새 환경 — Cutover Candidate
Smoke Test와 데이터 정합성 확인
│
│ ③ Traffic Cutover
│ DNS / Reverse Proxy / Load Balancer
▼
새 환경 — Read / Write
기존 환경 — Write Blocked, Cron·Queue Disabled
│
│ Observation Window 통과
▼
기존 환경 Decommission
Bulk Copy 이후 기존 사이트에서 주문·회원·댓글·파일 업로드가 계속 발생했다면, 처음 복사한 데이터는 Cutover 시점의 최신 상태가 아니다. Final Sync 없이 트래픽만 바꾸면 “접속은 되지만 최근 데이터가 사라진 사이트”가 될 수 있다.
Master Migration Checklist
1. Preflight — 무엇을 옮기는지 먼저 확정한다
Preflight의 목적은 서버 정보를 모으는 데 그치지 않는다. 어떤 상태가 복사 대상이고 무엇이 외부 시스템에 남아 있으며 Cutover 전후 어느 환경이 Source of Truth인지 정하는 단계다.
Environment Compatibility Matrix
아래 표는 실제 값을 채워 사용하는 템플릿이다. MATCH는 단순 일치, TEST_REQUIRED는 Rehearsal 필요, BLOCK은 Cutover 전에 해결해야 함을 뜻한다.
| Layer | 기존 환경 | 새 환경 | 반드시 비교할 항목 | Pass Evidence | 판정 |
|---|---|---|---|---|---|
| WordPress Core | 기입 | 기입 | 버전, 자동 업데이트 정책, Core 수정 여부 | Version·Checksum·변경 기록 | |
| PHP | 기입 | 기입 | 버전, SAPI, FPM 설정, Memory·Timeout | phpinfo 또는 구성 Export, 기능 테스트 |
|
| PHP Extensions | 기입 | 기입 | json, mysqli, curl, mbstring, intl, openssl, xml, zip, 이미지 처리 모듈 |
Extension 목록과 Upload·Remote Request 테스트 | |
| Database | 기입 | 기입 | MySQL/MariaDB 종류·버전, Charset, Collation, SQL Mode, Timezone, Packet Size | Import Log, 주요 Query, 문자·정렬 검증 | |
| Web Server | 기입 | 기입 | Nginx/Apache, Rewrite, Upload Limit, 압축, 정적 파일 처리 | Permalink·Admin·REST·대용량 Upload 테스트 | |
| Plugin | 기입 | 기입 | 활성·비활성 목록, 버전, PHP·Core 요구사항, License, Custom Table | 기능별 Acceptance Test | |
| Theme | 기입 | 기입 | Parent·Child Theme, 버전, 빌드 파일, Template Override | 주요 Template·반응형·관리 화면 검증 | |
| MU Plugin | 기입 | 기입 | 파일 목록, 로딩 순서, 환경별 조건 | 초기 Bootstrap와 관리자 기능 테스트 | |
| Custom Code | 기입 | 기입 | Theme Functions, Drop-in, Snippet, 별도 App·API | 코드 목록과 Critical Flow 테스트 | |
| Multisite | 기입 | 기입 | Subdomain/Subdirectory, Network Table, Domain Mapping, Cookie·Upload Path | Network Admin 및 사이트별 테스트 | |
| Uploads·Storage | 기입 | 기입 | 경로, 소유자, 권한, 외부 Object Storage, Generated File | 개수·용량·최근 파일·썸네일 검증 | |
| Object Cache | 기입 | 기입 | Redis/Memcached, DB 번호, Prefix, Serializer, Drop-in | Cache Flush 후 Read·Write·Session 테스트 | |
| Page Cache·CDN | 기입 | 기입 | Cache Key, Bypass Route, Purge, Origin | HIT/MISS·Admin 제외·Purge 확인 | |
| WP-Cron·System Cron | 기입 | 기입 | 호출 방식, 주기, 사용자, DISABLE_WP_CRON |
이벤트 목록·실행 로그 | |
| Queue | 기입 | 기입 | Worker 위치, Pending·Failed 작업, Retry | Queue 상태와 중복 실행 부재 | |
| 기입 | 기입 | SMTP·API 방식, From Domain, 인증, Bounce·Log | 실제 수신과 Provider Log | ||
| Webhook·Callback | 기입 | 기입 | Endpoint, Secret, IP 제한, Retry, Idempotency | 고유 Test ID와 수신 Log | |
| DNS·TLS·Proxy·WAF | 기입 | 기입 | Record, TTL, Certificate, Origin, Forwarded Header, Rule | 외부·Origin 양쪽 테스트 | |
| Monitoring | 기입 | 기입 | Uptime, PHP Error, Web Log, DB, Disk, Queue, Alert | Dashboard·Alert Route 검증 |
WordPress Hosting Handbook은 json과 mysqli를 필수 PHP Extension으로 설명하며 원격 요청·미디어·문자 처리·TLS·압축 등에 필요한 여러 Extension을 별도로 권장한다. “PHP 버전이 같다”는 것만으로 서버 환경이 같다고 판단할 수 없는 이유다. (Make WordPress)
Preflight Checklist
| 완료 | 확인 항목 | 남겨야 할 Evidence |
|---|---|---|
| □ | 이전 범위가 인프라 변경인지, URL 변경인지, Core·PHP·DB Upgrade까지 포함하는지 구분 | Scope 문서 |
| □ | WordPress·PHP·DB 버전과 PHP Extension을 기록 | Environment Matrix |
| □ | 활성·비활성 Plugin 전체와 버전·요구 PHP·Custom Table을 기록 | Plugin Inventory |
| □ | Parent·Child Theme, Custom Template, 빌드 산출물을 기록 | Theme Inventory |
| □ | mu-plugins, object-cache.php, advanced-cache.php, 기타 Drop-in을 확인 |
MU Plugin·Drop-in 목록 |
| □ | functions.php, Snippet, 별도 PHP 파일, 배포 스크립트 등 Custom Code를 확인 |
Custom Code Register |
| □ | Multisite 여부, 사이트 수, Domain Mapping, Subdomain/Subdirectory 구조를 확인 | Network Map |
| □ | WooCommerce·예약·회원·댓글·폼 등 Write-heavy 기능을 분류 | Writer Inventory |
| □ | wp-content/uploads와 별도 Upload 경로·외부 저장소·최근 변경량을 측정 |
Uploads Inventory |
| □ | Page Cache의 위치와 Bypass Route를 확인 | Cache Map |
| □ | Object Cache의 Prefix·DB·Drop-in·공유 여부를 확인 | Object Cache Map |
| □ | CDN의 Origin·Cache Rule·Purge 경로를 확인 | CDN Configuration |
| □ | WP-Cron인지 System Cron인지, 어느 서버에서 무엇을 실행하는지 확인 | Cron Inventory |
| □ | Plugin Queue·Pending·Failed 작업과 Worker 위치를 확인 | Queue Snapshot |
| □ | SMTP·Mail API·발신 도메인·인증·반송 확인 경로를 기록 | Mail Flow Map |
| □ | Form, Webhook, Payment Callback, 외부 API의 Endpoint와 Secret을 확인 | Integration Register |
| □ | DNS Record, 현재 TTL, Proxy 여부, 권한 보유자를 확인 | DNS Export |
| □ | 모든 Hostname의 TLS 인증서와 갱신 책임을 확인 | Certificate Inventory |
| □ | Reverse Proxy가 전달하는 Host·Scheme·Client IP Header를 확인 | Proxy Contract |
| □ | WAF·접근 통제 규칙 중 Admin·REST·Webhook에 영향을 주는 항목을 확인 | Security Rule Export |
| □ | DB·파일·설정 Backup이 있고 실제 Restore 가능한지 확인 | Restore Evidence |
| □ | DB, Uploads, 외부 거래 데이터별 Source of Truth를 선언 | Source-of-Truth Table |
| □ | Acceptance Criteria, Rollback Trigger, 승인자와 연락 경로를 확정 | Cutover Runbook |
Source of Truth를 하나의 문장으로 정한다
예를 들어 다음처럼 기록한다.
| 시점 | WordPress DB | Uploads | Cron·Queue | 외부 결제·메일 |
|---|---|---|---|---|
| Rehearsal 중 | 기존 환경 | 기존 환경 | 기존 환경만 실행 | 실제 외부 시스템 |
| Write Freeze 후 Final Sync 중 | 기존 환경, 쓰기 중지 | 기존 환경, 쓰기 중지 | 중지 또는 Drain | 수신 중인 Callback 별도 통제 |
| Cutover Candidate | 새 환경 후보 | 새 환경 후보 | 아직 실행 보류 | 테스트 요청만 허용 |
| Acceptance 통과 후 | 새 환경 | 새 환경 | 새 환경만 실행 | 새 Endpoint와 정합성 확인 |
| Observation Window | 새 환경 | 새 환경 | 새 환경만 실행 | 외부 시스템과 지속 대조 |
WordPress DB만 Source of Truth라고 쓰면 부족하다. Uploads가 파일시스템에 있고 결제 결과가 외부 PG에 있으며 메일 상태가 발송 사업자에 있다면, 이전 대상은 이미 여러 시스템에 분산돼 있다.
2. Rehearsal — 실제 전환 전에 한 번 끝까지 수행한다
Rehearsal은 새 서버에서 홈 화면을 띄우는 테스트가 아니다. Cutover와 동일한 순서로 복사·Import·설정·검증을 수행하고 걸리는 시간과 실패 지점을 기록하는 작업이다.
운영 도메인이 그대로 유지된다면 테스트를 위해 DB 전체 URL을 임시 도메인으로 바꾸기보다, 승인된 테스트 장비에서 Hosts Override나 임시 Proxy Routing을 사용하는 편이 불필요한 DB 변경을 줄일 수 있다. 임시 Hostname을 사용해야 한다면 검색엔진 차단뿐 아니라 실제 메일·결제·Webhook·Cron이 실행되지 않도록 별도로 막는다.
URL이 바뀌는 경우
WordPress의 Option, Widget, Theme 설정과 Plugin 데이터에는 PHP Serialized Data가 들어갈 수 있다. 직렬화된 문자열에는 값의 길이가 함께 저장되므로, 길이가 다른 URL을 Raw SQL의 전역 REPLACE()로 바꾸면 데이터가 깨질 수 있다. WordPress 공식 Migration 문서는 이를 경고하고 WP-CLI의 search-replace는 Serialized Data를 인식해 처리한다고 설명한다. (WordPress Developer Resources)
안전한 기본 절차는 새 환경의 복사본에서 먼저 Dry Run을 수행하는 것이다.
bash 예시
# 변경 대상과 건수만 확인한다. DB는 수정하지 않는다.
wp search-replace \
'https://old.example' \
'https://new.example' \
--skip-columns=guid \
--dry-run
결과와 DB Backup을 검토한 뒤에만 같은 명령에서 --dry-run을 제거한다.
Plugin이 WordPress에 등록되지 않은 Custom Table을 사용한다면 테이블을 먼저 확인한 뒤 --all-tables-with-prefix 사용 여부를 결정한다. Multisite 전체를 대상으로 할 때는 --network가 필요하지만 이것만으로 Network 구조 검토가 끝나는 것은 아니다. WP-CLI 기본 동작은 현재 사이트에 등록된 테이블을 대상으로 하며 --network·--all-tables-with-prefix는 범위를 넓히는 옵션이다. (WordPress Developer Resources)
guid는 URL처럼 보이더라도 바꾸지 않는다. WordPress는 이를 Post의 지속적인 식별자로 사용하며 Domain이 바뀌어도 그대로 유지하도록 공식 문서에서 명시한다. guid를 바꾸면 Feed Reader가 기존 글을 새 글로 인식하는 문제가 생길 수 있다. (WordPress Developer Resources)
Dry Run 성공도 최종 증거는 아니다. 변경 후에는 메뉴, Block·Widget 설정, CSS의 배경 이미지, 미디어 URL, SEO Metadata, Plugin Custom Table과 Redirect를 실제 화면과 DB에서 확인해야 한다.
Rehearsal Checklist
| 완료 | 확인 항목 | Pass Criteria |
|---|---|---|
| □ | Staging 또는 Temporary Host를 운영 트래픽과 분리 | 일반 사용자가 접근하지 않고 검색 색인·실제 외부 Side Effect가 차단됨 |
| □ | 실제 목표 PHP·DB·Web Server로 실행 | Fatal Error·Deprecated 폭증 없이 Critical Flow 동작 |
| □ | 파일 소유자와 권한 복원 | WordPress가 필요한 디렉터리에만 쓰고 과도한 전역 쓰기 권한이 없음 |
| □ | DB Export·Import 수행 | Import Error가 없고 주요 Table·Entity 수가 예상 범위와 일치 |
| □ | URL 변경 Dry Run과 적용 | Serialized Data가 유지되고 guid가 변경되지 않음 |
| □ | Permalink·Rewrite 구성 | 글·페이지·REST API·Admin 경로가 정상 응답 |
| □ | Upload 생성·썸네일 처리 | 원본과 파생 이미지가 생성되고 공개 URL에서 읽힘 |
| □ | Plugin·Theme·MU Plugin·Custom Code 실행 | Critical Flow별 Acceptance Criteria 통과 |
| □ | 메일·Webhook·결제는 테스트 모드로 검증 | 운영 사용자나 실제 거래에 Side Effect가 없음 |
| □ | Bulk Copy와 Final Sync 예상 시간 측정 | 계획한 Maintenance Window 안에 수행 가능 |
| □ | Rollback Rehearsal | Traffic Reversal과 기존 환경 재활성화 절차가 문서대로 가능 |
| □ | Rollback Trigger 확정 | 누가 어떤 증거로 언제 중단할지 명확함 |
3. Cutover — 최신 데이터와 실제 트래픽을 함께 옮긴다
Cutover 순서
| 순서 | 작업 | 완료 조건 |
|---|---|---|
| 1 | Bulk Copy 완료 상태 확인 | 새 환경에 기준 시점의 DB·파일이 존재 |
| 2 | 변경 공지와 Write Freeze 시작 | 관리자·사용자·API·Callback의 쓰기 경로가 통제됨 |
| 3 | 기존 환경 Cron·Queue 통제 | Final Sync 중 새 데이터나 외부 Side Effect를 만들지 않음 |
| 4 | Final DB Sync | Freeze 기준 시점까지의 DB가 새 환경에 반영됨 |
| 5 | Final Upload Sync | Bulk Copy 이후 추가·변경·삭제된 파일이 반영됨 |
| 6 | URL·환경 설정 최종 적용 | 운영 URL·DB·Secret·Proxy 설정이 목표값과 일치 |
| 7 | Cutover Candidate Smoke Test | Login·Read·Write·Upload 등 차단 조건이 없음 |
| 8 | DNS·Proxy·Load Balancer 전환 | 외부 요청이 새 환경으로 유입 |
| 9 | TLS 확인 | 인증서·Chain·Redirect·Forwarded Scheme 정상 |
| 10 | Page Cache·Object Cache·CDN 무효화 | 이전 응답·이전 Origin·오래된 객체가 제거됨 |
| 11 | 새 환경 Write Open | 새 환경만 Source of Truth로 쓰기 시작 |
| 12 | 기존 환경 Write Block 유지 | 우회 접속으로 새 데이터가 생성되지 않음 |
Write Freeze는 관리자 공지만으로 끝나지 않는다
WordPress의 쓰기 경로는 /wp-admin/에만 있지 않다.
- 댓글과 회원 가입
- 문의·예약·설문 Form
- REST API
admin-ajax.php- 결제·배송·CRM Callback
- 예약 발행과 WP-Cron
- Plugin Queue와 재시도 Worker
- 외부 프로그램의 Application Password·API 요청
관리자 화면에 Maintenance 안내를 띄웠어도 Webhook과 Cron이 계속 실행되면 Final DB Sync 이후 데이터가 다시 달라질 수 있다. Write-heavy 사이트에서는 쓰기 주체를 모두 멈추거나, 멈출 수 없는 쓰기를 별도 원장으로 기록해 재조정해야 한다.
짧은 Write Freeze조차 허용되지 않는 사이트라면 이 Runbook의 단순 Final Dump 방식만으로는 충분하지 않다. Replication이나 변경 데이터 추적, Callback 재처리와 정합성 대조를 포함한 별도의 Online Migration 설계가 필요하다.
DNS TTL은 완료 시간을 보장하지 않는다
TTL을 미리 낮추면 기존 Cache가 만료된 뒤 새 Record를 더 빨리 조회하게 만들 수 있다. 그러나 이미 조회한 Resolver의 TTL은 남은 시간 동안 계속 감소하고 일부 Resolver는 장애 회피를 위해 만료된 데이터를 일시적으로 제공할 수도 있다. 따라서 “TTL을 300초로 낮췄으므로 5분 뒤 전 세계가 새 서버를 본다”는 식으로 Cutover 시간을 보장하면 안 된다. (RFC Editor)
전환 후에는 여러 Resolver와 네트워크에서 결과를 확인하고 요청이 어느 Backend에 도달했는지를 서버 로그나 테스트용 Deployment Marker로 판별해야 한다. Marker에는 Origin IP나 내부 Hostname 같은 민감한 정보를 노출하지 않는다.
TLS·Proxy·WAF에서 확인할 것
- 모든 실제 Hostname이 인증서에 포함됐는가
- Edge와 Origin 사이 TLS도 정상인가
- HTTP→HTTPS Redirect가 Loop를 만들지 않는가
- Reverse Proxy가 원래 Host와 HTTPS Scheme을 WordPress에 올바르게 전달하는가
- 새 Origin IP·Port·Health Check가 반영됐는가
- WAF가 Login, Admin, REST API, Webhook, Payment Callback을 잘못 막지 않는가
- 기존 Origin으로 직접 접속해 쓰기를 만들 수 없는가
4. Validation — 화면이 아니라 기능과 운영 증거로 승인한다
검증은 “담당자가 둘러봤는데 괜찮았다”로 끝내지 않는다. 항목별 Owner와 Pass Criteria를 미리 정하고 요청 ID·로그·수신 결과처럼 다시 확인할 수 있는 Evidence를 남긴다.
Validation / Owner / Pass Criteria
| 검증 대상 | 기본 Owner | Pass Criteria |
|---|---|---|
| Home | WordPress Owner | 운영 도메인에서 기대한 Home이 열리고 새 Backend로 처리됨 |
| Read | Content Owner | 대표 글·페이지·검색·분류·미디어가 정상이며 최근 콘텐츠가 존재 |
| Admin | WordPress Owner | Dashboard 진입, 메뉴, Editor, Plugin 관리 화면에 오류·Redirect Loop 없음 |
| Login | App Owner | 로그인·로그아웃·비밀번호 재설정과 권한별 접근이 정상 |
| Session | App Owner | 연속 요청에서 Session이 유지되고 이전 환경과 잘못 공유되지 않음 |
| Write | Content Owner | 테스트 글·설정 변경이 새 DB에 저장되고 Front에 반영 |
| Upload | Content Owner | 파일 업로드·썸네일 생성·조회·삭제가 정상 |
| Form | Business Owner | 제출 데이터가 저장되고 후속 처리 상태까지 확인 |
| Mail Owner | 요청 처리뿐 아니라 실제 수신, Provider Log, 반송 여부까지 확인 | |
| Webhook | Integration Owner | 고유 Test ID가 서명 검증을 통과해 한 번만 처리됨 |
| Payment | Commerce Owner | 승인된 Test/Sandbox Flow에서 Callback과 주문 상태가 일치 |
| WP-Cron | WordPress Owner | 예정 이벤트가 정해진 호출 방식으로 실행되고 중복 실행되지 않음 |
| System Cron | Infrastructure Owner | 지정 사용자·주기·명령으로 실행되며 새 환경만 호출 |
| Queue | App Owner | Pending·Failed가 예상 범위이고 Old/New Worker가 동시에 실행되지 않음 |
| Object Cache | App Owner | Flush 후 정상 재생성되고 다른 환경과 Key가 충돌하지 않음 |
| Page Cache | Web Owner | 동적 경로가 Bypass되고 공개 페이지의 HIT·MISS가 의도와 일치 |
| CDN | Web Owner | 새 Origin을 사용하며 Purge 후 이전 콘텐츠가 남지 않음 |
| Redirect | SEO/Web Owner | HTTP·WWW·이전 URL의 목적지가 맞고 Chain·Loop가 없음 |
| 대표 URL | SEO Owner | 공개 URL만 대표 주소로 출력되고 Temporary Host가 남지 않음 |
| robots | SEO Owner | 운영 정책과 일치하며 Staging용 전체 차단이 남지 않음 |
| Sitemap | SEO Owner | 운영 URL을 사용하고 주요 콘텐츠가 포함되며 정상 응답 |
| Analytics | Analytics Owner | Page View·주요 Event가 수집되고 Tag가 중복 실행되지 않음 |
| Logs | Operations Owner | PHP Fatal·DB Error·Permission Error·5xx가 비정상 증가하지 않음 |
| Monitoring | Operations Owner | Uptime·Latency·Error·Disk·DB·Queue 감시가 새 환경을 대상으로 동작 |
| Site Health | WordPress Owner | Migration 관련 Critical Issue가 없고 환경 정보가 Matrix와 일치 |
| Business Acceptance | Business Approver | 핵심 업무 여정이 모두 Pass하고 미해결 위험이 승인 범위 안에 있음 |
WP-Cron과 System Cron은 다른 것이다
WP-Cron은 전통적인 System Cron처럼 계속 실행되는 Scheduler가 아니다. 기본 동작에서는 Page Load가 발생할 때 예정된 작업이 있는지 검사한다. 트래픽이 없으면 정해진 시각보다 늦게 실행될 수 있다. (WordPress Developer Resources)
System Cron을 이용해 wp-cron.php 또는 WP-CLI를 정기적으로 호출하도록 구성한 사이트에서는 일반 Page Load 기반 호출을 DISABLE_WP_CRON으로 끄는 경우가 있다. 이전할 때는 다음 두 가지를 각각 확인해야 한다.
- WordPress 안에 예정된 Event가 그대로 존재하는가
- 새 서버의 OS Scheduler가 실제로 WordPress Cron을 호출하는가
wp cron test는 WP-Cron Spawn 동작을 검사하는 도구다. System Cron을 사용하는 구성에서는 OS Scheduler의 실행 로그와 실제 Event 완료 기록까지 별도로 확인해야 한다. 기존 서버와 새 서버가 동시에 Cron을 실행하면 메일, 구독, 예약, 재고 동기화가 중복 처리될 수 있다. (WordPress Developer Resources)
메일 함수 성공과 수신 성공은 다르다
WordPress의 wp_mail()이 true를 반환해도 수신자가 메일을 받았다는 뜻은 아니다. 공식 함수 문서도 해당 값은 사용된 전송 방식이 요청을 오류 없이 처리했다는 뜻일 뿐이라고 명시한다. (WordPress Developer Resources)
메일 검증에는 다음 Evidence가 필요하다.
- WordPress 또는 Plugin이 발송 요청을 생성
- SMTP·Mail API Provider가 요청을 수락
- 발신 도메인과 인증이 정상
- 실제 Test Mailbox에 도착
- Spam·Bounce·Block 여부 확인
- 비밀번호 재설정·주문·Form 등 Template별 확인
PHP의 mail() 호출이나 wp_mail() 반환값만 보고 Pass를 주지 않는다.
Site Health Green도 전체 Acceptance는 아니다
Site Health는 WordPress·Plugin·Theme·Server·DB·Filesystem Permission 등 구성 정보와 알려진 문제를 확인하는 데 유용하다. Upload 비활성화, Loopback 실패, 오래된 PHP, HTTP Request 차단 같은 문제도 탐지할 수 있다. (WordPress.org)
그러나 Site Health는 주문 결제, 실제 메일 수신, 외부 Webhook, Analytics, 최신 데이터 정합성, DNS 전환과 롤백 가능성을 대신 검증하지 않는다. 따라서 Site Health가 모두 Green이라는 사실은 Migration Acceptance의 한 항목이지 전체 증거가 아니다.
5. Decommission — 기존 서버를 끄는 것도 이전 작업이다
새 환경이 정상이라고 판단한 직후 기존 서버를 삭제하면 안 된다. 반대로 기존 환경을 무기한 살아 있게 두는 것도 안전하지 않다. 오래된 Server, Plugin, Credential과 Firewall Rule이 관리되지 않은 채 남아 새로운 공격면과 운영 혼선을 만들 수 있기 때문이다.
Observation Window의 길이를 모든 사이트에 동일하게 적용할 수는 없다. 최소한 다음을 실제로 관측할 수 있는 기간이어야 한다.
- 핵심 예약 작업의 한 주기
- 실제 또는 승인된 합성 Write Flow
- 메일·Webhook·결제 Callback
- Cache 만료와 재생성
- 운영 시간대의 실제 트래픽
- Backup과 Monitoring의 정기 실행
매월 한 번 실행되는 Critical Job이 있다면 이틀간 홈페이지가 정상이라는 이유만으로 해당 작업을 검증했다고 볼 수 없다.
Decommission Checklist
| 완료 | 항목 | 종료 조건 |
|---|---|---|
| □ | Observation Window 운영 | 미해결 Critical Issue가 없고 종료 기준 충족 |
| □ | 기존 환경 Write Block | 우회 Domain·IP·관리 경로에서도 새 데이터 생성 불가 |
| □ | 기존 Cron·Queue 중지 | 외부 Side Effect와 중복 작업이 발생하지 않음 |
| □ | 기존 환경 최종 Backup | DB·파일·설정의 Restore 가능한 사본과 식별자 보존 |
| □ | Backup Retention 확정 | 법적·계약·운영 정책에 맞는 삭제일과 Owner 지정 |
| □ | Credential Rotation | 기존 서버가 보유했던 DB·SMTP·API·Webhook Secret을 필요 범위에서 교체 |
| □ | DNS Cleanup | Old Record·Temporary Host·검증용 Entry 제거 |
| □ | TLS Cleanup | 사용하지 않는 Certificate·Challenge·갱신 작업 정리 |
| □ | Firewall·Allowlist Cleanup | 기존 IP·Port·관리 접근 허용 제거 |
| □ | Monitoring Cleanup | 중복 Alert 제거, 필요한 Historical Data 보존 |
| □ | Backup·Security Agent Cleanup | 불필요한 비용·권한·중복 작업 제거 |
| □ | Shutdown Approval | Business·Application·Infrastructure Owner의 승인 기록 |
| □ | Server Termination | 종료 시각·자산 ID·최종 증거 기록 |
| □ | 사후 문서 갱신 | Runbook·Architecture·Credential Owner·Asset Registry 최신화 |
WordPress-specific Rollback Contract
Rollback Contract는 “문제가 생기면 DNS를 원래대로 바꾼다”는 문장이 아니다. WordPress에서는 DB, Uploads, Cron, Queue, Form, 주문, 메일과 Callback이 동시에 상태를 바꿀 수 있으므로 어느 시점까지 단순 복귀가 가능한지 먼저 정해야 한다.
Contract Template
| Field | 기록할 내용 |
|---|---|
| Rollback Authority | 복귀를 승인할 역할과 대리인 |
| Decision Deadline | Cutover 후 단순 복귀 판단이 가능한 마지막 시각 |
| Old Environment Mode | Write Blocked, Cron·Queue Disabled |
| Source of Truth Before Freeze | 기존 DB·Uploads |
| Source of Truth After Acceptance | 새 DB·Uploads |
| Traffic Reversal Method | DNS·Proxy·Load Balancer별 실행 순서 |
| Restore Point | Final Sync 직전·직후 Backup 식별자 |
| Rollback Trigger | Login·Write·Payment·Data Integrity·Error 등 구체 조건 |
| New-write Detection | 새 환경에 생성된 Post·User·Order·Upload·Form·Queue 목록 |
| External Reconciliation | 결제·Webhook·메일·CRM 상태 대조 방법 |
| Cache Action | Object·Page·CDN Cache 무효화 순서 |
| Cron·Queue Authority | 어느 환경을 언제 다시 활성화할지 |
| Evidence Owner | 로그·DB 비교·요청 ID를 수집할 담당자 |
| Business Approval | 데이터 손실·중복 처리 위험을 승인할 역할 |
| Closure Criteria | Rollback 완료와 운영 정상화를 판정할 조건 |
새 환경에서 쓰기가 시작되기 전
새 환경이 아직 실제 쓰기를 받지 않았다면 일반적으로 기존 환경으로 트래픽을 되돌리고 Write Block을 해제하는 절차가 가능하다. 이 단계가 가장 단순한 Rollback Window다.
새 환경에서 쓰기가 시작된 뒤
다음 데이터가 새 환경에 생성됐을 수 있다.
- 주문과 결제 상태
- 회원·로그인·비밀번호 변경
- 댓글·게시물·예약
- 업로드 파일
- Form 제출
- Webhook 처리 결과
- Cron·Queue가 만든 변경
- 외부로 발송된 메일
이 상태에서 트래픽만 기존 서버로 돌리면 새 환경에서 발생한 데이터가 사용자에게 사라져 보이거나, 같은 Callback과 Queue가 두 번 처리될 수 있다. 따라서 다음 중 하나가 필요하다.
- 새 쓰기를 기존 환경에 안전하게 재반영
- 외부 원장과 양쪽 DB를 대조해 수동 조정
- 기존 환경으로의 복귀 대신 새 환경에서 Forward Fix
- 위험이 해결될 때까지 새 쓰기를 다시 잠그고 제한 운영
어느 방법이 가능한지는 Cutover 전에 정해야 한다. Rollback 시점에 처음 논의하면 이미 데이터가 갈라진 뒤일 수 있다.
코드·설정·DB·트래픽을 포괄하는 일반적인 Rollback Architecture는 「롤백 가능한 배포를 만드는 법」에서 다룬다. 이 글에서는 그 원칙을 WordPress의 DB·Uploads·Cron·Plugin·외부 Callback 상황에 적용한 실행 계약에 집중한다.
사이트 유형별 추가 항목
| 유형 | 기본 체크리스트에 추가할 것 |
|---|---|
| 일반 콘텐츠 사이트 | 댓글·회원 가입·Form이 실제로 쓰기를 만드는지 확인, 예약 발행과 Sitemap 갱신, 최근 Uploads Delta |
| Write-heavy 사이트 | Write Freeze 대상 API 전체, 주문·예약·재고·구독 원장, Payment Callback Retry·Idempotency, Queue Drain, Final Entity Count, Cutover 이후 신규 Write 추적 |
| WooCommerce | 주문·결제·환불·재고·쿠폰·메일·Webhook·Scheduled Action을 각각 검증하고 외부 PG 원장과 상태 대조 |
| Membership·LMS | 로그인 Session, 권한·수강 상태·진도·구독 갱신·비밀번호 메일·Background Job 검증 |
| Multisite | wp_site, wp_blogs, 각 사이트의 Options, Domain Mapping, Network-activated Plugin, Upload Path, Wildcard DNS·TLS, Cookie Domain, Network Admin 검증 |
| Multisite URL 변경 | WP-CLI --network 범위 확인, 각 사이트의 home·siteurl·Upload 설정과 Network Table 수동 검토, Subdomain/Subdirectory 전환 여부 확인 |
WordPress 공식 Migration 문서도 Multisite는 DB 여러 곳에 서버명과 경로가 존재해 단일 사이트보다 복잡하며 wp_site, wp_blogs, 사이트별 Options와 SUBDOMAIN_INSTALL 같은 설정을 별도로 검토해야 한다고 설명한다. (WordPress Developer Resources)
Observation Log Template
Observation Log에는 Credential, 개인 데이터, 전체 결제 정보를 복사하지 않는다. 필요하면 식별 가능한 최소 Request ID나 내부 Evidence ID만 남긴다.
| Timestamp | Phase | Observer | 기능·이벤트 | Test / Request ID | 처리 Backend | Expected | Observed | Result | Evidence | Action / Owner |
|---|---|---|---|---|---|---|---|---|---|---|
| YYYY-MM-DD HH:mm | Cutover | Login | New | 로그인·Session 유지 | PASS / FAIL | Log ID | ||||
| YYYY-MM-DD HH:mm | Validation | Upload | New | 원본·썸네일 생성 | PASS / FAIL | Screenshot / Log | ||||
| YYYY-MM-DD HH:mm | Validation | New | 실제 수신 | PASS / FAIL | Provider Message ID | |||||
| YYYY-MM-DD HH:mm | Observation | Cron / Queue | New | 정시·단일 실행 | PASS / FAIL | Event / Job ID | ||||
| YYYY-MM-DD HH:mm | Observation | Error Review | New | 기준 대비 비정상 증가 없음 | PASS / FAIL | Dashboard |
Observation Window를 닫을 때는 최소한 다음을 기록한다.
yaml 예시
observation_started_at:
observation_ended_at:
critical_flows_observed:
scheduled_jobs_observed:
mail_delivery_confirmed:
webhook_and_payment_reconciled:
old_environment_writes_detected:
open_incidents:
rollback_artifacts_verified:
shutdown_approved_by:
이전 완료의 기준
“새 서버에서 사이트가 잘 보인다”는 것은 Validation의 첫 행에 가깝다.
안전하게 Migration이 끝났다고 말하려면 다음 질문에 모두 답할 수 있어야 한다.
- 최종 데이터는 어느 환경에 있는가
- 기존 환경과 새 환경에서 동시에 쓰기가 발생하지 않았는가
- 사용자·관리자·외부 시스템의 실제 기능이 통과했는가
- 메일·Cron·Queue·Cache·Webhook이 새 환경에서만 동작하는가
- 장애가 발생하면 어느 시점까지 어떻게 복귀할 수 있는가
- 기존 서버와 Credential을 언제 어떤 승인으로 제거하는가
먼저 이 글의 Master Migration Checklist를 실제 Runbook으로 복사해 Owner와 Evidence를 채운다. 일반적인 롤백 설계는 「롤백 가능한 배포를 만드는 법」, 백업과 복원 가능성은 「매일 스냅샷을 찍는데도 별도 백업이 필요한 이유」에서 이어서 확인할 수 있다. 이전 후 WordPress 운영 책임의 배분은 별도 운영 문서로 관리한다.