posts

git pushしたらデザインが消えた。CSSが404になった原因はフォルダの権限だった

トップページのデザインを作り直して git push したら、サイトのデザインが丸ごと消えて、文字だけの画面になりました。キャッシュを疑い、Nginxの設定を疑い、設定ファイルを壊しかけ、最終的にたどり着いた原因はフォルダの読み取り権限でした。

先に結論を書いておきます。

  • GitHub Actionsで送ったフォルダが、持ち主しか入れない状態(drwx------)になっていて、NginxがCSSを読めなかった
  • 本当は「読めない」のに、Nginxは 404(見つからない) を返していたので、原因の見当がつかなかった
  • 持ち主と権限を直し(chown と chmod)、Actionsの手順書に「必ず権限を直すステップ」を入れて再発を防いだ

同じように「ファイルはあるはずなのに404」で困っている人の参考になればと思います。

症状:文字だけのサイトになった

このブログは、番外編2の手順で、git push するとGitHub Actionsが自動でサーバーに公開する仕組みにしています。

トップページの見出しやデザインを作り直して git push したところ、まずActionsの画面で、転送のステップが失敗(赤い×)になっていました。そこを直しているうちに、サイトのほうもおかしいことに気づきました。

  • 新しく作った見出しやデザインが反映されていない
  • ブラウザの再読み込み(Ctrl + Shift + R)をしても変わらない
  • シークレットウィンドウで開くと、CSSがまったく効いていない、文字だけの画面になる
CSSが読み込まれていないときのトップページ
シークレットウィンドウで開いたときの画面。見出しや箇条書きは出ていますが、色や配置などのデザインが全部消えています

シークレットウィンドウでも同じなので、自分のブラウザのキャッシュの問題ではありません。サーバーがCSSを返していない、ということです。

遠回り1:Nginxの設定を疑った

CSSのファイルを直接取りに行って確かめました。PowerShellで打ちます。

curl.exe -I https://noluckblog.com/css/style.css

返ってきたのは 404 Not Found でした。「CSSのファイルが無い」と言われています。

手元の public フォルダにはCSSがありますし、Actionsで送ったはずです。そこで「Nginxが正しい場所を見ていないのでは」と考えて、Nginxの設定ファイルを開き、CSSのための設定を書き足してみました。

コピペした設定に、リンクの書き方が混ざった

ここでさらにハマりました。書き足す設定を、チャットの画面からコピーしてサーバーに貼ったところ、server_name の行がこんな形で貼られていたのです。

server_name noluckblog.com [www.noluckblog.com](http://www.noluckblog.com);

チャットの画面が、www. で始まる文字を自動でリンクにしていて、コピーするとMarkdownのリンクの書き方([表示する文字](URL))ごと持ってきてしまっていました。当然、nginx -t は通りません。何度コピーし直しても同じ形で貼られてしまうので、最終的には sed コマンドで、この形の部分をまとめて元の文字に置き換えて直しました。

nanoの「.save」ファイルも残っていた

もう1つ、nginx -t が通らない原因がありました。nanoで編集している途中で画面が閉じたせいで、sites-enabled の中に yourlog.save というファイルが残っていたのです。Nginxは sites-enabled の中のファイルを全部設定として読むので、中身が同じ設定が2つあることになり、エラーになっていました。このファイルを消して、ようやく nginx -t が test is successful に戻りました。

ところが、設定を直して反映しても、CSSは404のまま。何も変わりませんでした。

原因:フォルダが「持ち主しか入れない」状態だった

設定ではない、となると、ファイルそのものを疑うしかありません。サーバーに入って、公開フォルダの中身を見ました。

ls -la /var/www/yourlog/public

こんな行が並んでいました。

drwx------  2 1001 1001  4096 (日付) css
drwx------  … 1001 1001  …    (日付) posts

さらに、Nginxのエラーの記録(ログ)を見ました。

tail -n 20 /var/log/nginx/error.log
[crit] … "/var/www/yourlog/public/css/style.css" failed (13: Permission denied)

Permission denied(権限がない)。ファイルはちゃんとあったのに、Nginxが読めなかったのです。

drwx------ の意味

ls -la の左端の10文字は、そのファイルやフォルダの権限を表しています。

d   rwx   ---   ---
│   │     │     └ その他の人(Nginxはここ)
│   │     └ グループ
│   └ 持ち主
└ d はフォルダという意味
文字 意味
r 読める
w 書ける
x フォルダの場合は「中に入れる」、ファイルの場合は「実行できる」
- その権限がない

drwx------ は、持ち主だけが読み書きでき、中に入れる。それ以外の人は何もできないという状態です。NginxはUbuntuでは www-data というユーザーで動いていて、このフォルダから見ると「その他の人」です。だから css フォルダの中に入れず、style.css を読めませんでした。

持ち主が 1001 という番号になっていたのは、GitHub Actions側で使われているユーザーの番号です。Actionsの転送(appleboy/scp-action)は、ファイルをまとめてサーバーに送って展開する仕組みで、そのときに送り元の持ち主や権限の情報まで一緒に運ばれてしまったようです。

権限がないのに、なぜ404だったのか

ここが一番の遠回りの原因でした。権限がないなら、ふつうは 403 Forbidden(禁止されています) が返ってくるはずです。手動で公開していたころは、実際に403が出ていました。

今回404だったのは、このブログのNginxの設定にこの行があったからです。

location / {
    try_files $uri $uri/ =404;
}

try_files は「このファイルがあれば返す。なければ次を試す。全部なければ404」という意味です。Nginxから見ると、読めないファイルは「無い」のと同じ扱いになり、最後の =404 まで進んで404を返していました。

つまり、画面に出ていたのは404でも、本当の原因は権限でした。ステータスコードだけで判断せず、エラーログを見るのが大事だと身にしみました。

直し方

サーバーに入って、次の4つを打ちました。

chown -R root:root /var/www/yourlog
find /var/www/yourlog -type d -exec chmod 755 {} +
find /var/www/yourlog -type f -exec chmod 644 {} +
systemctl reload nginx
コマンド 何をしているか
chown -R root:root /var/www/yourlog 中身全部の持ち主を、サーバーの root にそろえる
find … -type d -exec chmod 755 {} + フォルダを全部 drwxr-xr-x(誰でも中に入れて読める)にする
find … -type f -exec chmod 644 {} + ファイルを全部 -rw-r--r--(誰でも読める。書けるのは持ち主だけ)にする
systemctl reload nginx Nginxに反映する

フォルダとファイルで数字を分けているのは、フォルダには「中に入る(x)」が必要で、ファイルには要らないからです。chmod -R 755 で全部まとめて変えても読めるようにはなりますが、ファイルに不要な実行の権限まで付いてしまいます。

もう一度確かめると、

curl.exe -I https://noluckblog.com/css/style.css

200 OK が返ってきて、サイトのデザインも元どおりになりました。

再発防止:Actionsの手順書を直す

もともとActionsの手順書(deploy.yml)には、転送のあとに chmod -R 755 で権限を直すステップを入れていました。それでも直らなかったのは、転送のステップが失敗扱いになると、その後ろのステップは実行されないからです。最初に見た赤い×のとき、権限を直すステップは飛ばされていました。

そこで、最後のステップをこう変えました。

      - name: Fix permissions and reload Nginx
        if: always()
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_IP }}
          username: root
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            chown -R root:root /var/www/yourlog
            find /var/www/yourlog -type d -exec chmod 755 {} +
            find /var/www/yourlog -type f -exec chmod 644 {} +
            systemctl reload nginx

if: always() は「前のステップが成功しても失敗しても、必ず実行する」という指定です。これで、転送がどんな結果になっても、最後に必ず持ち主と権限がそろうようになりました。それ以来、同じ症状は出ていません。

次に同じことが起きたら見る順番

  1. シークレットウィンドウで開く … 同じならキャッシュではなくサーバー側の問題
  2. curl.exe -I でCSSのURLを直接たたく … 200か、403か、404か
  3. tail /var/log/nginx/error.log を見る … Permission denied なら権限、No such file なら本当にファイルがない
  4. ls -la で権限と持ち主を見る … フォルダが drwxr-xr-x、ファイルが -rw-r--r-- になっているか
  5. Nginxの設定は最後 … 書き換えたら必ず nginx -t。コピペした中身は目で確認する

今回は5から始めてしまったせいで、設定ファイルまで壊しかけました。「設定を変える前に、まずログを見る」を自分のルールにしています。

学んだこと

つまずいたこと 原因 学んだこと
CSSが404になった フォルダが drwx------ で、Nginxが中に入れなかった 404でも権限が原因のことがある。エラーログを見る
403ではなく404だった try_files が「読めない」を「無い」として扱った 表示されたステータスコードだけで判断しない
nginx -t が通らなくなった コピペで [www…](http://www…) が混ざった 貼った設定は目で確認する
nginx -t がまだ通らない nanoの .save ファイルが sites-enabled に残っていた sites-enabled に余計なファイルを置かない
権限を直すステップがあったのに直らなかった 前のステップが失敗して、飛ばされていた 後片付けのステップには if: always() を付ける

ファイルの権限は、手動で公開していたころから何度もつまずいてきたところです。仕組みを一度ちゃんと理解したので、次は数分で直せると思います。

あわせて読みたい