Permission denied を安全に直す:Linux 権限の診断と最小変更

よるほろぐ編集部

· 更新: 2026年7月10日 · 約 14 分

Permission denied は、「とりあえず chmod を実行する」合図ではありません。実行したプロセスの ID、対象と親ディレクトリの所有者・モード、ACL(Access Control List)のどれが要求を拒んだかを確認してから、必要な権限だけを変えます。この順序なら、秘密鍵を公開してしまったり、関係ないファイルまで実行可能にしたりせずに済みます。

この記事では、エラーの対象を 観察 → 原因分類 → 最小変更 → 再確認 の順に扱います。ここでいう「最小」は、対象パス、対象の権限クラス、必要な操作をそれぞれ狭くすることです。sudo で一度通ったことは、実際に動かすサービスや利用者が通ることの証明にはなりません。

まずはローカルの練習場所を作る

本番のホームディレクトリや Web ルートを練習に使わず、一時ディレクトリに閉じ込めます。次の例はサブシェルで実行され、終了時に作成物を削除します。getfacl が見つからない環境では ACL の表示だけを省略します。コマンドが無いことは、ACL が存在しないことを意味しません。

bash <<'BASH'
set -eu

lab_dir="$(mktemp -d "${TMPDIR:-/tmp}/permissions-lab.XXXXXX")"
trap 'rm -rf -- "$lab_dir"' EXIT
cd -- "$lab_dir"

install -d -m 700 private
printf '%s\n' 'practice only' > private/note.txt

id
stat -c 'mode=%A (%a) owner=%U group=%G path=%n' -- private private/note.txt
namei -l -- "$PWD/private/note.txt"
if command -v getfacl >/dev/null 2>&1; then
  getfacl -p -- private/note.txt
fi
BASH

mktemp が作った絶対パスだけを trap の削除対象にし、-- でパスをオプションとして解釈しないようにしています。実務では削除を伴うコマンドを権限修正と同じ端末履歴に混ぜず、対象パスを表示してから実行する習慣も有効です。

Permission denied を観察して原因を分ける

エラーを出したコマンド、完全なパス、実行したアカウントを記録します。そのうえで、次の四つを 変更せずに 実行します。サービスが失敗しているなら、対話ログイン中の自分ではなく、そのサービスが実行されるアカウントとパスで確認します。

target="/srv/example-app/private/config.env"

id
stat -c 'mode=%A (%a) owner=%U group=%G path=%n' -- "$target"
namei -l -- "$target"
getfacl -p -- "$target"

id は実効ユーザー、主グループ、補助グループを示します。基本のモードでは、プロセスが所有者なら所有者の u、そうでなければ一致するグループの g、どちらでもなければ o の権限が評価されます。補助グループも重要です。statrwx と八進数、所有者、グループを一行で比較するために使います。

namei -l はパスを構成する各ディレクトリを表示します。目的のファイルが rw-r----- でも、途中のディレクトリに必要な検索権限(x)が無ければ到達できません。Linux の stat() も、対象ファイル自体の権限とは別に、パス途中の全ディレクトリに検索権限が必要であることを示しています。

getfacl は通常の rwx だけでは分からない ACL と、ディレクトリの default ACL を表示します。named user または named group を含む ACL では mask:: が実効権限の上限になります。たとえば user:ci-reader:rw- と表示されても、mask::r-- なら書き込みは実効権限ではありません。ACL が疑われるときは、ls -l の一行だけで判断しません。

観察結果原因の分類先に検討する最小変更
id のユーザー・グループが所有者情報と一致しない実行主体または所有者・グループの不一致正しい実行アカウント、対象一件だけのグループまたは所有者
ファイルの必要な rwx が無い対象ファイルのモード不足必要なクラスに必要なビットだけを chmod
namei -l の途中に検索できないディレクトリがある親ディレクトリを通過できないその親ディレクトリに必要なクラスの x だけを追加
getfacl に named entry や制限された mask:: があるACL による許可不足または制限ACL の対象アカウント一件だけを変更し、mask を再確認

読み取り、書き込み、実行をファイルとディレクトリで同じ意味として扱わないことが重要です。

ビット通常ファイルディレクトリ
r内容を読めるエントリー名を一覧できる
w内容を書き換えられるエントリーを作成・削除・名前変更できる。通常は x も必要
xプログラムやスクリプトとして実行できる検索・通過でき、既知の名前に到達できる

削除できるかは、削除対象ファイルの書き込みビットではなく、主に 親ディレクトリwx で決まります。共有ディレクトリでは sticky bit など追加の規則もあり得るため、まず親を確認します。

chmod は「足す」か「正確に置き換える」かを選ぶ

symbolic mode は既存の不要なビットを変えず、特定の権限だけを調整したい場合に向いています。次の例は、所有者にだけ実行権限を追加します。

chmod u+x -- ./deploy.sh
stat -c '%A (%a) %n' -- ./deploy.sh

ugo はそれぞれ所有者、所有グループ、その他を表し、+-= は追加、削除、指定クラスの置き換えです。chmod g-w,o-rwx -- ./config.env のように、公開してはいけない設定ファイルから特定の権限だけを外す用途にも使えます。

octal mode は、対象が 一件であり、最終状態を明確に決められる 場合に使います。r=4w=2x=1 を所有者・グループ・その他の順に並べるため、600 は所有者だけが読み書きできる状態です。

chmod 600 -- ~/.ssh/id_ed25519
chmod 700 -- ~/.ssh
stat -c '%A (%a) %n' -- ~/.ssh ~/.ssh/id_ed25519

SSH 秘密鍵は秘密情報です。鍵と .ssh に対して個別に最終モードを確認し、ディレクトリ全体への再帰変更はしません。再帰的に同じ数値を当てると、通常ファイルまで実行可能にしたり、秘密の設定を他者が読めるようにしたりします。実行権限が必要なのが一つのスクリプトなら、そのスクリプト一件へ chmod u+x を行う方が安全です。公開用のツリー全体を変更する必要が本当にある場合でも、先に対象を棚卸しし、ディレクトリと通常ファイルを別々に検討してください。

所有者・グループ・ACL は役割を狭くして扱う

chown は所有者とグループを変更するコマンドです。権限エラーの理由が、期待する実行アカウントと対象一件の所有者不一致だと確認できた場合だけ、対象を一件に限定します。たとえばアプリケーションが書き込む設計なのは専用のアップロード用ディレクトリだけなら、Web ルート全体ではなくそのディレクトリだけを対象にします。

sudo chown app-runtime:app-maintainers -- /srv/example-app/var/uploads
stat -c 'mode=%A (%a) owner=%U group=%G path=%n' -- /srv/example-app/var/uploads

app-runtimeapp-maintainers は役割を表す例です。実行アカウント、対象パス、所有すべき理由を運用設計で確認してから置き換えてください。ソースコードや静的コンテンツまで同じ所有者に移す必要があるとは限りません。所有者を広いパスに再帰変更する代わりに、書き込みが必要な専用ディレクトリを分離すると、変更範囲も事故の影響も小さくなります。

既存の所有者やグループを変えず、一つのアカウントにだけ例外を与える必要があるなら ACL が候補です。最初に結果を試算し、実際の変更後も表示を比較します。

sudo setfacl --test -m u:ci-reader:r-- -- /srv/example-app/var/uploads/report.txt
sudo setfacl -m u:ci-reader:r-- -- /srv/example-app/var/uploads/report.txt
getfacl -p -- /srv/example-app/var/uploads/report.txt

setfacl--test は変更せずに結果を表示します。実際に適用した後は、named entry だけでなく mask::#effective: も確認します。ディレクトリの default ACL は、将来その中に作られるオブジェクトの初期 ACL に影響します。これは共有場所を継続的に運用する場合に有用ですが、作成時の umask の見え方も変えるため、単発の権限エラーを直す目的で安易に追加しません。

umask で新規作成時の公開範囲を決める

umask は、これから作るファイルとディレクトリの作成モードから権限を取り除くマスクです。既存ファイルを修正するものではありません。多くの一般ファイルが要求する 0666 とディレクトリが要求する 0777 を前提にすると、umask 027 のプロセスで作るものはそれぞれ 06400750 になります。プログラムがより狭いモードを要求する場合は、さらに狭くなることがあります。

(
  umask 027
  touch private-note
  mkdir private-dir
  stat -c '%A (%a) %n' -- private-note private-dir
)

丸括弧の中で実行すれば、その umask は現在のシェルには残りません。親ディレクトリに default ACL がある場合は、その ACL が新規オブジェクトの初期 ACL に使われ、umask だけの計算とは異なるため、作成先の getfacl も確認します。

sudo は権限エラーを隠す手段にしない

sudo で成功しても、通常アカウントやサービスの失敗原因は残ります。まずそのプロセスが必要とする最小権限を直し、管理操作が本当に必要なときだけ sudo を使います。sudoers でパスワードなしに任意のコマンドを root として実行できるルールを追加することは、権限エラーの解決策にしません。

特定の運用グループに特定の管理操作だけを委譲する必要があるなら、管理者が実行ファイルの絶対パスと引数を限定し、visudo で構文を検証します。次は形を示す例であり、サービス名とコマンドのパスは実環境で確認して置き換えます。認証を省略する指定は含めません。

# /etc/sudoers.d/app-operators
%app-operators ALL=(root) /usr/bin/systemctl reload example-app.service
sudo visudo -f /etc/sudoers.d/app-operators
sudo -l

このように対象を一コマンドに限定しても、実行権限の委譲には変わりありません。サービスの再読み込みが必要な運用上の理由、操作できるグループ、監査方法を決めてから追加します。日常のファイル読み書きのために特権化するのではなく、ファイル側の所有者・グループ・ACL を正すのが基本です。

変更後は同じ条件で再確認する

最後に、最初に失敗したコマンドを同じ実行アカウント、同じパス、同じ操作で再実行します。成功したら stat、親ディレクトリを含む namei -l、ACL を使った場合は getfacl をもう一度取り、意図しない wx が増えていないことを確認します。成功しない場合は次の大きな chmod を重ねず、観察結果へ戻って原因分類をやり直します。

SSH 接続で鍵の配置やサーバー側設定も扱う場合は、鍵認証で安全にログインする SSH 入門 を参照してください。権限確認をシェルスクリプトに組み込む前には、シェルスクリプトの変数・条件分岐・ループ でクオートと条件分岐の基礎を押さえると、安全な自動化につながります。

出典

共有: X でシェア はてブ

編集・検証

よるほろぐ編集部

Linux・コンテナ・CLI・開発環境の実践ガイドを編集・検証します。

編集方針を読む