0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

この記事について

サーバーでこういうエラーに出会ったとき、何が起きているか説明できるだろうか。

Failed to open stream: Permission denied

「とりあえず chmod 777 したら直った」で切り抜けてきた人は多いと思う。
実際それで動くのだが、777 は「サーバー上の誰にでも書き換えを許す」という意味で、後述するとおり本番環境では事故のもとになる。

この記事では、ls -l の出力を読み解けるようになるところまで、ユーザーとグループの仕組みを前提知識なしで説明する。
題材として、実際に本番運用で踏んだ「ログファイルに書けなくなる障害」を最後に読み解く。

サーバーの中には複数のアカウントがいる

Linux サーバーは1台のコンピュータだが、中では複数のアカウント(ユーザー)が別々の仕事をしている。
会社の社員だと思えばよい。

アカウントの例 仕事
ubuntu 管理者。人間が SSH でログインするときに使う
nginxwww-data1 Web サーバーやアプリを動かす係。ページの表示もログの書き込みもこの人
postgres データベースを動かす係

人間が使うアカウントだけでなく、プログラムもそれぞれ専用のアカウントで動いている。
「誰がこのファイルに書けるか」という問いの「誰」は、このアカウントたちを指す。

ファイルには3つの欄がある

Linux のすべてのファイルとフォルダには、アクセス権を決める3つの欄が付いている。

  • 所有者:このファイルの持ち主であるアカウント1名に何を許すか
  • グループ:指定した1つのグループのメンバーに何を許すか
  • その他:上の2つに該当しない全アカウントに何を許すか

ls -l の出力は、この3欄の設定を左端の10文字に表示している。

-rw-r----- 1 ubuntu nginx 4967 .env

左端の10文字を分解すると次のとおりで、続く ubuntu nginx が所有者とグループの名前である。

位置 この例では 意味
1文字目 - ファイル種別。d ならディレクトリ
2〜4文字目 rw- 所有者(ubuntu)に許すこと
5〜7文字目 r-- グループ(nginx)のメンバーに許すこと
8〜10文字目 --- その他の全員に許すこと

r は読める、w は書ける、- は許されていない、を表す2

この例では、所有者の ubuntu は読み書きでき、nginx グループのメンバーは読むだけ、それ以外のアカウントは開くことすらできない。

グループは「全員」ではなく名簿

グループという言葉から「サーバーの利用者全員」を想像するかもしれないが、そうではない。
グループは名前を付けて作る名簿であり、誰を載せるかは管理者が決める。
メッセージアプリのグループと同じで、招待したメンバーだけが入っている。

sudo groupadd deploy              # deploy という名簿を作る
sudo usermod -aG deploy ubuntu    # ubuntu を名簿に載せる

usermod-a(append)を忘れると、既存の所属が指定グループだけに総入れ替えになる。
ubuntu の場合は sudo グループからも外れる(管理者権限を失う)ので、-aG はセットで覚えたい。

1人のアカウントは何個のグループにでも掛け持ちで入れる。
一方、ファイル側のグループ欄に書けるグループは1つだけである。
この非対称は後の節でもう一度使う。

ubuntu:ubuntu の正体

ls -l を眺めると、こういう表記をよく見かける。

-rw-rw-r-- 1 ubuntu ubuntu 614 Makefile

所有者も ubuntu、グループも ubuntu。
これは、多くのディストリビューションがアカウントを作るときに同じ名前の1人だけのグループを自動で作るためである。

つまり ubuntu:ubuntu は「所有者は ubuntu、共有先は ubuntu 1人だけの名簿」で、実質は本人専用の状態を表す。
グループ欄が仕事をしていないファイルは、だいたいこの形をしている。
逆に言えば、グループ欄に deploynginx のような意味のある名簿が入っているファイルは、誰かが設計して共有を組んだファイルである。

数字の読み方

chmod 644 のような数字は、3つの欄の設定を1桁ずつ並べたものである。
読み書きだけに絞れば、6 が読み書き、4 が読むだけ、0 が何も許さない、と覚えれば足りる3

数字 所有者 グループ その他 用途の例
777 読み書き 読み書き 読み書き 誰でも書ける。とりあえず動くが事故のもと
644 読み書き 読むだけ 読むだけ 多くのツールの既定値
664 読み書き 読み書き 読むだけ グループの仲間と共同で書く
640 読み書き 読むだけ なし 秘密のファイルを特定の係にだけ読ませる

たとえば DB のパスワードが書かれた .env640ubuntu:nginx にすると、「管理者は編集できる、Web アプリは読んで動ける、他のアカウントは開けもしない」という状態を作れる。
777 で運用していたファイルを締めるときの基本形である。

ディレクトリの rwx は意味が変わる

ここまでファイルの話をしてきたが、ディレクトリにも同じ3欄がある。
ただし rwx の意味が変わる。

ビット ディレクトリでの意味
r 中のファイル名の一覧を見られる
w 中でファイルを作る、消す、名前を変えられる
x 中に入れる(通過できる)

この違いから、直感に反する挙動が2つ生まれる。

1つは、x のないディレクトリには cd すらできないこと。
ホームディレクトリを 700 にすると、他のアカウントは中を覗くどころか通過もできなくなる。

もう1つは、ファイルの削除に必要なのは、ファイル自身の w ではなく親ディレクトリの w であること。
削除とは「ディレクトリという棚から名前を取り除く操作」なので、棚側の権限で決まる。
読み取り専用のファイルでも、親ディレクトリに書ければ消せてしまう。

権限は「作成時」に決まる

もう一つ、見落とされがちな性質がある。
ファイルの3欄は、そのファイルが作られた瞬間に、作った側の既定値で決まる。
親ディレクトリの権限設定が新しいファイルにコピーされるわけではない。

この「作った側の既定値」の正体は、プログラムの要求と umask の組み合わせである。
多くのプログラムは新しいファイルを 666(全員読み書き)で要求し、プロセスごとに設定された umask がそこから権限を削る。
典型的な環境の umask は 022(グループとその他から「書く」を削る)なので、できあがるファイルは 644 になる。

chmod -R で中身を一括変更しても、それは「その瞬間に存在するファイル」に効くだけで、明日生まれるファイルには及ばない。
代表的な例外がディレクトリの setgid ビットで、これを立てると中に作られるファイルが親ディレクトリのグループ名を自動で継承する。

sudo find /var/www/app/storage -type d -exec chmod g+s {} +

ただし setgid が継承させるのはグループ名だけで、グループ欄を「読むだけ」にするか「書ける」にするかのビットまでは決めない。
そこは作成側の既定値のままである。
この隙間が、次の実例で効いてくる。

実例: 2人で書くログファイルの障害

最後に、冒頭のエラーの実物を読み解く。
Laravel アプリの本番運用で、ある朝こう落ちた。

The stream or file ".../storage/logs/laravel-2026-07-02.log" could not be opened
in append mode: Failed to open stream: Permission denied

このサーバーでは、ログを書くアカウントが2人いた。
Web アプリを動かす nginx と、自動デプロイ用の deploy-bot(仮名)である。
Laravel の daily ログは日付ごとに新しいファイルを作るので、その日最初にログを書いた側が所有者になる。

障害当日の朝、たまたまデプロイ処理が先にログを書き、ファイルはこう生まれた。

-rw-r--r-- 1 deploy-bot nginx 1024 laravel-2026-07-02.log

setgid のおかげでグループ名は nginx が継承されている。
しかしログライブラリは権限を指定せずにファイルを作るため、権限は umask に削られた既定値、つまり 644(グループ欄は「読むだけ」)で生まれる。
所有者でもなく、グループ欄からも書けない nginx は、追記しようとして Permission denied で落ちた。

3欄が読めれば、対策も欄の言葉で書ける。
「作成時にグループ欄を『書ける』にしておく」、つまり 664 で作らせればよい。
Laravel なら設定ファイルに1行足すだけである。

'daily' => [
    'driver' => 'daily',
    'path' => storage_path('logs/laravel.log'),
    'permission' => 0664,   // グループにも書き込みを許して作る
    // ...
],

この指定があると、ログライブラリはファイルを作った直後に chmod をかけるため、umask に削られず確実に 664 になる。
これで、デプロイ側が先にその日のファイルを作っても、nginx はグループ欄から書ける。
逆に nginx が先に作ったファイル(所有者もグループも nginx)にデプロイ側からも書きたければ、deploy-bot を nginx グループの名簿に載せておく必要がある。
setgid(グループ名の継承)、0664(グループへの書き込み許可)、名簿(グループへの所属)の3点が揃って、初めて「どちらが先にその日のログを作っても、もう一方が書ける」が成立する。

枠の配分という設計

仕組みを一通り並べたので、設計の話で締める。
1つのファイルが持てるのは所有者1名とグループ1つだけである。
書き込みたい主体が2人いるなら「所有者に1人、グループに1人(または1班)」という枠の配分になるし、読ませたいだけの相手は「その他」の読み取りか、グループの r で足りる。

さきほどのログの例なら、所有者の枠をデプロイ用アカウントが使い、グループの枠を nginx が使った。
DB パスワードの例なら、所有者の枠を管理者が使い、グループの枠を Web アプリが読み取り専用で使い、その他は締め切った。
どのファイルにどの名簿を割り当てるかを決めることが、権限設計のほぼすべてである。

枠が2つで足りない場面のためには ACL(setfacl)という拡張もあり、デフォルト ACL を使えば setgid では継承できない権限ビットまで新規ファイルに継承させられる。
ただし枠の配分で収まるうちは基本の3欄だけで組むほうが、あとから ls -l で読める分だけ運用しやすい。

まとめ

  • サーバーの中では複数のアカウントが働いていて、プログラムにも専用アカウントがある
  • すべてのファイルは「所有者1名、グループ1つ、その他」の3欄でアクセス権を持つ
  • グループは全員ではなく名簿。アカウントは掛け持ちできるが、ファイル側の枠は1つ
  • ubuntu:ubuntu は自動作成された本人専用グループで、実質グループ欄が遊んでいる状態
  • ディレクトリの rwx は意味が変わる。削除の可否は親ディレクトリの w で決まる
  • 権限はファイル作成時に決まる。setgid が継承するのはグループ名だけで、ビットは作成側の既定値が決める
  1. Web サーバー用アカウントの名前は環境によって違う。Debian や Ubuntu の標準パッケージでは www-data、RHEL 系や nginx の公式パッケージでは nginx が使われることが多い。どちらなのかを推測で決めつけると権限設定を間違えるので、ps aux の USER 列で実機の実行ユーザーを確認してから作業するのが安全である。

  2. 3文字目の x は実行権。この記事では話を絞るため、ディレクトリの節で触れる以外は省略する。

  3. 正確には r=4、w=2、x=1 の合計。6 = 4+2 = 読み+書き、7 = 4+2+1 = 全部、である。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?