この記事について
サーバーでこういうエラーに出会ったとき、何が起きているか説明できるだろうか。
Failed to open stream: Permission denied
「とりあえず chmod 777 したら直った」で切り抜けてきた人は多いと思う。
実際それで動くのだが、777 は「サーバー上の誰にでも書き換えを許す」という意味で、後述するとおり本番環境では事故のもとになる。
この記事では、ls -l の出力を読み解けるようになるところまで、ユーザーとグループの仕組みを前提知識なしで説明する。
題材として、実際に本番運用で踏んだ「ログファイルに書けなくなる障害」を最後に読み解く。
サーバーの中には複数のアカウントがいる
Linux サーバーは1台のコンピュータだが、中では複数のアカウント(ユーザー)が別々の仕事をしている。
会社の社員だと思えばよい。
| アカウントの例 | 仕事 |
|---|---|
ubuntu |
管理者。人間が SSH でログインするときに使う |
nginx や www-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人だけの名簿」で、実質は本人専用の状態を表す。
グループ欄が仕事をしていないファイルは、だいたいこの形をしている。
逆に言えば、グループ欄に deploy や nginx のような意味のある名簿が入っているファイルは、誰かが設計して共有を組んだファイルである。
数字の読み方
chmod 644 のような数字は、3つの欄の設定を1桁ずつ並べたものである。
読み書きだけに絞れば、6 が読み書き、4 が読むだけ、0 が何も許さない、と覚えれば足りる3。
| 数字 | 所有者 | グループ | その他 | 用途の例 |
|---|---|---|---|---|
777 |
読み書き | 読み書き | 読み書き | 誰でも書ける。とりあえず動くが事故のもと |
644 |
読み書き | 読むだけ | 読むだけ | 多くのツールの既定値 |
664 |
読み書き | 読み書き | 読むだけ | グループの仲間と共同で書く |
640 |
読み書き | 読むだけ | なし | 秘密のファイルを特定の係にだけ読ませる |
たとえば DB のパスワードが書かれた .env を 640 の ubuntu: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 が継承するのはグループ名だけで、ビットは作成側の既定値が決める