synology / self-hosting / security / performance

워드프레스 보안

워드프레스 코어 자동 업데이트가 멈춘 이유: files_not_writable과 빈 옵션

워드프레스 3곳이 서로 다른 버전에서 멈춘 원인을 auto_core_update_failed 기록에서 찾아 파일 소유권, auto_update_core_major 옵션, 방문자 없는 사이트의 wp-cron 문제를 고친 과정입니다.

NAS의 워드프레스 8개 중 3개가 서로 다른 버전에서 멈춰 있었습니다. 하나는 6.9.1, 하나는 6.8.2, 하나는 7.0.5. 자동 업데이트를 켜 둔 줄 알았는데 한동안 안 올라가고 있었던 겁니다.

관리자 화면에는 별다른 경고가 없었습니다. 원인은 DB 안에 조용히 남아 있었습니다.

실패 기록은 DB에 있습니다

워드프레스는 코어 자동 업데이트에 실패하면 wp_options 테이블의 auto_core_update_failed에 이유를 남깁니다.

SELECT option_value FROM wp_options WHERE option_name = 'auto_core_update_failed';

나온 오류 코드는 두 가지였습니다.

  • files_not_writable — 업데이트할 파일에 쓸 권한이 없음
  • copy_failed_for_update_core_file — 새 파일을 복사하다 실패

둘 다 결국 같은 얘기입니다. PHP를 실행하는 웹 서버 사용자가 코어 파일을 고칠 수 없었던 겁니다.

원인 1: 파일 주인이 웹 서버가 아니었습니다

시놀로지 Web Station에서 PHP는 http 사용자로 돕니다. 그런데 웹 루트 파일 중에 제 계정 소유로 되어 있어서 http가 쓸 수 없는 파일들이 있었습니다. 파일을 SSH나 파일 스테이션으로 올리면 올린 사람 소유가 됩니다.

그리고 한 곳은 제가 만든 원인이었습니다. 해킹된 가족 블로그를 정리하면서 코어 파일을 원본으로 갈아 끼우고, 보안을 위해 코어 소유자를 제 계정으로 바꿔 웹 서버가 못 쓰게 해 뒀습니다. 공격자가 코어를 못 고치게 하려던 건데, 워드프레스 자신도 못 고치게 된 겁니다.

원인 2: 메이저 업데이트 옵션이 비어 있었습니다

권한 말고도 옵션 문제가 있었습니다. 워드프레스 5.6부터 관리자 화면의 "새 버전 자동 업데이트" 설정은 DB의 auto_update_core_major 값에 저장됩니다.

사이트별 auto_update_core_major 값
사이트값결과
3곳없음(unset)마이너 보안 업데이트만 받음
1곳disabled메이저 업데이트 꺼짐

이 값이 없으면 7.0 → 7.1 같은 메이저 업데이트는 자동으로 안 받습니다.

고친 순서

  1. DB부터 백업했습니다.
  2. 8개 사이트 웹 루트의 소유자를 http:users로 바꾸고 폴더 775, 파일 664, wp-config.php는 660으로 맞췄습니다.
  3. 업데이트 중에 쓰는 wp-content/upgrade 폴더가 없는 곳은 만들었습니다.
  4. DB에서 auto_update_core_major를 enabled로 넣고, 실패 기록(auto_core_update_failed)과 업데이트 잠금 값을 지웠습니다.
  5. 다음 예약 실행을 기다리지 않고, http 사용자로 PHP CLI에서 워드프레스의 자동 업데이트를 바로 돌렸습니다. 대략 이런 코드입니다.
<?php
// http 사용자로 실행: php84 run_update.php
define('WP_USE_THEMES', false);
require '/path/to/site/wp-load.php';
require_once ABSPATH . 'wp-admin/includes/admin.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
(new WP_Automatic_Updater())->run();
echo get_bloginfo('version'), "\n";

8곳 모두 7.1.1로 올라갔고, 각 사이트의 wp-admin/upgrade.php?step=1로 DB 업그레이드까지 마쳤습니다.

방문자가 없는 사이트는 업데이트도 안 돕니다

하나 더 있었습니다. 워드프레스의 예약 작업(wp-cron)은 진짜 cron이 아니라 누군가 사이트에 방문했을 때 같이 실행됩니다. 방문자가 거의 없는 사이트는 업데이트 확인 자체가 한참 늦어질 수 있습니다.

그래서 NAS의 cron에 매시 23분마다 8개 사이트의 wp-cron.php를 직접 부르는 스크립트를 넣었습니다.

# /etc/crontab
23 * * * * http /bin/sh /path/to/wp_cron_all.sh

# wp_cron_all.sh
for h in site1.example site2.example ...; do
  curl -k -s -o /dev/null -m 120 --resolve "$h:443:127.0.0.1" \
    "https://$h/wp-cron.php?doing_wp_cron=$(date +%s)"
done

--resolve로 NAS 자기 자신에게 바로 보내서 외부 네트워크를 거치지 않게 했습니다.

닷새 뒤

9월 27일에 확인해 보니, 그사이 쓰지 않는 사이트 하나를 정리해 남은 7곳 모두 제가 손대지 않았는데 7.1.2로 올라가 있었습니다. 자동 업데이트가 제대로 돌고 있다는 뜻입니다.

대신 치른 대가도 적어 둡니다. 코어 파일을 웹 서버가 쓸 수 있게 되돌렸으니, 웹셸이 다시 들어오면 코어도 고칠 수 있습니다. 저는 보안 업데이트를 제때 못 받는 쪽이 더 위험하다고 봤고, 대신 코어가 바뀌면 바로 알 수 있게 wordpress.org 체크섬으로 코어를 대조하는 점검기를 매시간 돌리고 있습니다. 파일 권한을 잠그는 보안과 자동 업데이트는 같이 가질 수 없어서, 둘 중 하나를 고르고 나머지를 감시로 메우는 쪽을 택했습니다.

확인한 공식 자료

구현 과정에서 판단 기준을 교차 확인한 공식 문서입니다. 글의 사례와 결론은 운영자가 직접 겪은 작업을 바탕으로 작성했습니다.