rsync使ってフォルダごと同期したら403エラーが出た

WEB

本ブログはWordPressのCocoonテーマを使っており、子テーマにダークモード対応などのカスタマイズを加えています。

子テーマのファイル更新はWordPressの管理画面からではなく、rsyncを使って行っています。rsyncはローカルとサーバー間でファイルを同期するコマンドです。

まず-n(ドライラン:実際には転送せず、対象ファイルだけ確認するオプション)で送られるファイルを確認してから、問題なければ-nを外して本番送信します。

# 確認(ドライラン)
rsync -avzn -e "ssh -i ~/.ssh/鍵ファイル -p ポート番号" ./cocoon-child-master/ ユーザー@サーバー:~/テーマパス/
# 本番送信(-nを外す)
rsync -avz -e "ssh -i ~/.ssh/鍵ファイル -p ポート番号" ./cocoon-child-master/ ユーザー@サーバー:~/テーマパス/

普段はstyle.cssのみ変更することがほとんどだったので、style.cssを直接指定してアップロードしていました。
ある時他のphpファイルにも手を加え、複数ファイルをアップロードする必要が出てきました。そこで、テーマファイルをフォルダごとまとめて差分アップロードしてみることにしました。

転送は成功したのに、ダークモードが消えた

rsyncの転送はエラーなく完了しました。ところがブラウザで確認すると、実装したはずのダークモードがまったく効いていません。

ファイルの中身を確認してもコードは正しいし、バグのありそうな箇所は見当たりません。おかしいなと思いながら、ブラウザの開発者ツール(F12を押すと開く検証ツール)でコンソールを見てみました。

すると、CSSファイルが403Forbidden(アクセス拒否)エラーになっているではありませんか。
サーバー上のファイルが「アクセスできない状態」になっていたようです。

原因:ローカルのパーミッションがそのままサーバーに転送されていた

原因は、「間違ったパーミッション(ファイルやフォルダへのアクセス権限)のままフォルダごとrsyncした」せいでした。

style.cssだけ個別に送っていたときは問題が起きていませんでした。しかしフォルダごと送ることで、ローカルのフォルダに付いていたパーミッションがそのままサーバーに転送されてしまったのです。

rsyncの-a(アーカイブモード)の仕様として、このオプションには-p(パーミッション保持)フラグが含まれており、ローカルの権限値をそのままコピーする動作になります。Webサーバーの公開ファイルとして不適切な値がそのままコピーされたため、403エラーが発生したようです。

パーミッションを直接修正して応急処置

ファイルの中身はローカルとサーバーで変わっていないため、SSH経由でサーバーに接続し、パーミッションを直接修正しました。

# ディレクトリのパーミッションを修正
find ~/テーマパス -type d -exec chmod 755 {} \;
# ファイルのパーミッションを修正
find ~/テーマパス -type f -exec chmod 644 {} \;

これで403エラーが消えてダークモードが戻りました。

--chmodオプションで再発防止

今回の敗因は、rsyncでの送信時に--chmod=D755,F644オプションを付けていなかったことでした。--chmodは転送時にディレクトリ(D)を755、ファイル(F)を644に強制設定するオプションです。

つまり、以下のようにコマンドを設定していれば問題は起きませんでした。

rsync -avz --chmod=D755,F644 -e "ssh -i ~/.ssh/鍵ファイル -p ポート番号" ./cocoon-child-master/ ユーザー@サーバー:~/テーマパス/

FTPでは起きにくい理由

別のブログではGitHub Actionsを使い、GitHubにpushしたらFTPで自動デプロイされる仕組みにしています。そちらではこのエラーが起きたことがありません。

調べたところ、FTPはパーミッション情報を転送しない仕様のようです。FTPでアップロードしたあとのパーミッションはサーバー側の設定(umask)で決まるため、ローカルの権限値がそのまま反映されないとのことです。今回のような「ローカルの間違ったパーミッションが転送される」問題が構造上起きにくいんですね。

今後:GitHub Actions + SSH + rsync に切り替えたい

この記事を書くために調べていたら、「GitHub Actions上でrsyncを実行し、SSH経由でサーバーに転送するワークフロー」が一般的な自動デプロイ手法だとわかりました。--chmodオプションも含めて設定できるので、デプロイの自動化とパーミッション管理を両立できます。

いまは手動のrsyncとFTPを使い分けている状態なので、おいおいこちらの方法に統一していこうと思います。