증상: All-in-One WP Migration 무료판에서 .wpress 백업을 복원하려 하면 유료 확장을 요구합니다.
원인: .wpress 는 플러그인 자체 묶음 형식이고, 무료판은 wp-cli 복원과 관리 화면 Restore 를 모두 막아 둡니다.
해결: 파이썬으로 파일을 직접 풀고, 테이블 접두어를 바꾼 SQL을 외래 키 검사를 끈 채 가져옵니다.
호스팅을 옮기려고 받은 백업이 .wpress 파일 하나뿐이었습니다. 새 서버에 플러그인을 깔고 복원하려니 무료판에서는 막혀 있었습니다. 유료 확장 없이 풀어서 옮긴 순서를 적습니다.
무료판에서 막히는 곳
| 시도 | 결과 |
|---|---|
| wp-cli 로 복원 | 유료 Unlimited Extension 필요 |
| 관리 화면 Backups 탭의 Restore 단추 | 역시 유료 확장 필요 |
| 관리 화면 가져오기 | 업로드 한도를 512M 로 올려도 플러그인 자체 한도 64MB |
| DB를 직접 가져오기 | 중간에 멈췄는데 성공처럼 보임 |
| 임시로 IP 주소로 접속 | 다른 사이트가 응답 |
파일을 백업 폴더에 직접 올려 업로드 한도는 피할 수 있었지만, 결국 Restore 자체가 유료라 다른 길이 필요했습니다.
자체 형식과 바뀐 접두어
.wpress 를 풀어 보니 구조가 단순했습니다. 파일마다 4377바이트 헤더가 붙고, 그 뒤에 파일 내용이 크기만큼 이어집니다. 헤더는 파일 이름 255바이트, 크기 14바이트, 수정 시각 12바이트, 경로 4096바이트이고, 각 문자열은 첫 null 바이트까지만 읽습니다. 파일의 끝은 0 바이트 4377개로 표시됩니다.
안에 든 database.sql 은 테이블 접두어가 SERVMASK_PREFIX_ 로 바뀌어 저장돼 있었습니다. 다른 서버에서도 쓸 수 있게 하려는 것으로 보입니다.
DB 가져오기가 멈춘 이유는 외래 키였습니다. wp_slim_events 가 wp_slim_stats 를 참조하는데 순서상 참조할 테이블이 아직 없어서 1473번째 줄에서 섰습니다. 그 뒤의 wp_users 같은 테이블이 통째로 빠졌는데, 오류를 놓치면 성공처럼 보입니다.
풀기, 접두어 바꾸기, 가져오기
1. 파이썬으로 헤더를 읽으며 파일을 풉니다.
import os
WPRESS = 'backup.wpress'
OUTDIR = 'restore'
EOF_BYTES = b'\x00' * 4377
with open(WPRESS, 'rb') as f:
while True:
header = f.read(4377)
if len(header) < 4377 or header == EOF_BYTES:
break
filename = header[:255].split(b'\x00')[0].decode('utf-8', errors='replace')
size_str = header[255:269].split(b'\x00')[0].decode('utf-8').strip()
prefix = header[281:4377].split(b'\x00')[0].decode('utf-8', errors='replace')
size = int(size_str) if size_str else 0
safe_prefix = prefix.lstrip('/').replace('..', '_')
out_path = os.path.join(OUTDIR, safe_prefix, filename)
os.makedirs(os.path.dirname(out_path), exist_ok=True)
with open(out_path, 'wb') as out:
remaining = size
while remaining > 0:
chunk = f.read(min(1024*1024, remaining))
out.write(chunk); remaining -= len(chunk)
풀고 나면 database.sql 과 plugins, themes, uploads 폴더가 나옵니다.
2. 접두어를 바꾸고, 외래 키와 고유 키 검사를 끈 채 가져옵니다.
sed 's/SERVMASK_PREFIX_/wp_/g' database.sql > converted.sql
(echo 'SET FOREIGN_KEY_CHECKS=0;'; echo 'SET UNIQUE_CHECKS=0;'; cat converted.sql; echo 'SET FOREIGN_KEY_CHECKS=1;') > import_safe.sql
mariadb -u root -p wp_example < import_safe.sql
3. wp-content 파일을 복사합니다. 원래 호스팅에 맞춘 drop-in 인 object-cache.php, advanced-cache.php 는 이름을 바꿔 잠시 끄고, wp-config.php 의 WP_CACHE 줄을 주석 처리합니다. 그 뒤 소유자를 웹서버 사용자로 맞춥니다.
mv object-cache.php object-cache.php.disabled
chown -R www-data:www-data /var/www/example.com
IP로 미리 볼 때 다른 사이트가 뜨는 문제
한 서버에 사이트가 여럿이면 IP로 접속했을 때 default_server 로 지정된 다른 사이트가 응답합니다. 이걸 모르고 가져오기를 눌렀다면 다른 사이트를 덮어쓸 뻔했습니다. Host 헤더 없이 요청해 어느 사이트가 답하는지 먼저 봅니다.
curl -sI http://localhost/
작업할 사이트의 vhost 에 listen 80 default_server; 를 임시로 주고, FORCE_SSL_ADMIN 은 잠시 false, siteurl 과 home 은 잠시 IP 주소로 둡니다. 작업이 끝나면 모두 되돌립니다.
복원 뒤 확인할 것
- 가져오기 뒤 테이블 목록에
wp_users가 있는지, 원래 사이트와 테이블 수가 같은지 봅니다. - 글을 눌렀을 때 빈 페이지나 리다이렉트 반복이 나오면 테마가 비활성 상태입니다. 원래 테마를 활성화하고 고유주소 규칙을 다시 씁니다.
wp theme activate astra
wp rewrite flush --hard
235MB 파일은 SFTP로 초당 1.4MB 정도, 약 3분에 올라갔습니다. 옮긴 뒤 사이트맵이 404라면 Rank Math 사이트맵 404 해결을 참고하세요.
작업한 환경
| 항목 | 내용 |
|---|---|
| 프로그램 | 워드프레스, All-in-One WP Migration 무료판, MariaDB, nginx |
| 확인한 날짜 | 2026년 5월 8일 |
더 읽을 글: wp-cli가 Success라는데 안 바뀌었을 때
이 글의 내용은 2026년 10월 5일에 마지막으로 확인했습니다. 틀린 곳을 발견하시면 연락처로 알려 주세요.