etuts+

CentOS 7 : /tmp 直下にファイルが書き込めない

/tmp 直下にファイルが書き込めないのは systemd でよくあるトラブル

CGI の mkstemp() とか php の file_put_contents()fopen() のようなファイル操作系の機能を使って /tmp にファイルを書き込もうとすると書き込めなくて困ってる人も少なくないと思います。

この事象は、CentOS 7 に限らず、昔から Fedora 系のような systemd でもよく発生するトラブルです。

systemd の仕様なので、トラブルって言うのもあれなんですが、実際には /tmp 直下ではなく、他のディレクトリに書き込まれているけど、それに気が付いてないだけかもしれません。

systemd では、インターネットからのリクエストによって /tmp 領域が使われたり、/tmp 直下のファイルが他のプロセスやアプリケーションによって操作されるのは危険。

セキュリティ的に良くないということで、登録したサービスに関しては/tmp/var/tmp 下にサービス専用のテンポラリ・ディレクトリが提供されるようになっています。

CentOS 7 で systemd が採用されたため、この仕様を把握しておらず、/tmp にファイルを書き込もうとするけど、確認してみると /tmp 下にファイルが存在しないから誤解されるケースが多いのではないかと思います。

/tmp にファイルが書き込めるかどうかテストしてみる

実際に、/tmp にファイル書き込みが出来るかどうか確認して見ましょう。

テスト その1. ブラウザ経由で /tmp にファイルを書き込んでみる

まずは、ドキュメントルートである /var/www/html 下に以下のファイルを作成し、ブラウザからアクセスしてみます。

/var/www/html/write.php
1
2
3
4
5
6
7
8
<?php
// ファイルがなければ新規作成 + 一応、排他制御しておく
if ( file_put_contents('/tmp/test.txt', 'test', FILE_APPEND | LOCK_EX) ) {
    echo 'OK';
} else {
    echo 'NG';
}
?>

ウェブページ上の結果は、OK でしたが、ファイルが実際に存在するか確認して見るとファイルが存在しないと怒れます。

/tmp だし、パーミッション的にも問題なさげだし。 これはおかしいですね。

/tmp にファイルが書き込まれているか確認
1
2
3
4
5
# ls -l /tmp/test.txt
ls: /tmp/test.txt にアクセスできません: そのようなファイルやディレクトリはありません

# ls -ld /tmp
drwxrwxrwt. 10 root root 4096  7月 14 10:12 /tmp

テスト その2. コマンドでソースファイルを実行し、/tmp にファイルを書き込んでみる

じゃ、ブラウザ経由ではなく、直接コマンドでソースコードを実行して見ると、そりゃそうだよねって感じです。

問題なく、/tmp にファイルが作られています。

/var/www/html/write.php
1
2
3
4
5
6
7
<?php
if ( file_put_contents('/tmp/test.txt', 'test', FILE_APPEND | LOCK_EX) ) {
    echo 'OK';
} else {
    echo 'NG';
}
?>
/tmp にファイルが書き込まれているか確認
1
2
3
4
5
6
7
8
# php -f write.php
OK

# ls -l /tmp/test.txt
-rw-r--r-- 1 root root 4  8月 12 15:57 /tmp/test.txt

# cat /tmp/test.txt
test

テスト その3. ブラウザ経由で /work にファイルを書き込んでみる

仮に /tmp ではなく、こんな感じで /work という新しいディレクトリを作って、所有権・権限を与えた上で、ブラウザから接続して見るとどうなるんでしょう。

/work 作成、及び権限付与
1
2
3
4
# mkdir /work

# chown nginx:nginx /work
# chmod 750 /work
/var/www/html/write.php
1
2
3
4
5
6
7
<?php
if ( file_put_contents('/work/test.txt', 'test', FILE_APPEND | LOCK_EX) ) {
    echo 'OK';
} else {
    echo 'NG';
}
?>

ブラウザに表示された結果は、OK。 ファイルが作成されたか確認してみると今度はちゃんと作成されていることが分かります。

/work にファイルが書き込まれているか確認
1
2
3
4
5
6
# ls -l /work/
合計 4
-rw-r--r-- 1 nginx nginx 4  8月 12 16:11 test.txt

# cat /work/test.txt
test

これで /tmp に何か原因があるのではないかと推測できます。

/tmp にファイルが書き込めない原因

例えば OS が CentOS 7 (systemd) で、ウェブサーバが apache か nginx かはともかく、大体こんな感じでサービス用の設定ファイルを作ってると思います。 (以下は nginx.service の例)

/usr/lib/systemd/system/nginx.service
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ls -l /etc/systemd/system/multi-user.target.wants/nginx.service
lrwxrwxrwx 1 root root 37  7月 17  2018 /etc/systemd/system/multi-user.target.wants/nginx.service -> /usr/lib/systemd/system/nginx.service

# ls -l /usr/lib/systemd/system/nginx.service
-rw-r--r-- 1 root root 731  7月 14 08:47 /usr/lib/systemd/system/nginx.service

# cat /usr/lib/systemd/system/nginx.service
[Unit]
Description=nginx - high performance web server
Documentation=http://nginx.org/en/docs/
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/var/run/nginx.pid

ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID

PrivateTmp=true

[Install]
WantedBy=multi-user.target

サービスファイルが見つからない

サービスファイルが見つからない時には、以下のように find で探して見てください。

# find / -name "*.service" | egrep "(nginx|apache|httpd)"
/usr/lib/systemd/system/httpd.service
/usr/lib/systemd/system/nginx.service
/usr/lib/systemd/system/nginx-debug.service
・・・

一応 新規でサービスファイルを作成したとして、設定適用コマンドも実行しておきましょう。

設定反映
01
02
03
04
05
06
07
08
09
10
11
# systemctl daemon-reload
# systemctl restart nginx

# ls -l /tmp
合計 4
drwx------ 3 root root 4096  8月  4 22:36 systemd-private-ccf18adb045548ffa3942a31e47eda88-nginx.service-p0QriJ

# ls -l /var/tmp/
合計 8
drwxr-xr-x 2 abrt    abrt    4096  7月 16  2018 abrt
drwx------ 3 root    root    4096  8月  4 22:36 systemd-private-ccf18adb045548ffa3942a31e47eda88-nginx.service-dv9Evp

ここで PrivateTmp=true になっていると上記のように、サービス専用の tmp ディレクトリ (名前空間) が自動的に生成されます

他のサービスも同じように、PrivateTmp=true になっているとサービス (プロセス) 毎に tmp ディレクトリが分離されるようになります。

こうすることで、他のプロセスからは見えない・干渉を受けない プライベートな tmp ディレクトリが使えるようになり、セキュリティを確保しています。

また、tmp ディレクトリのサービス名 nginx.service 前後に付く文字列は、プロセスによって異なる・サービスを再起動する度に変わります。

  • /tmp/systemd-private-○○○○-nginx.service-○○○○
  • /var/tmp/systemd-private-○○○○-nginx.service-○○○○

一番最初に冒頭でテストした テスト その1. ブラウザ経由で /tmp にファイルを書き込んでみる のところで ブラウザからの表示結果は OK だったのに対し、/tmp の下を確認してみるとファイルが存在しなかった のは、実際には /tmp ではなく、/tmp/systemd-private-○○○○-nginx.service-○○○○/tmp の下に書き込まれていることになります。

ls : サービス専用の tmp ディレクトリ確認
1
2
3
# ls -l /tmp/systemd-private-ccf18adb045548ffa3942a31e47eda88-nginx.service-p0QriJ/tmp/
合計 4
-rw-r--r-- 1 nginx nginx 4  8月 12 15:54 test.txt

ちなみに、PrivateTmp デフォルト値は、false です。

/tmp にファイルが書き込めるようにする

解決方法は、以下のように単純に PrivateTmp=true から PrivateTmp=false にすると /tmp 直下にファイルが書き込めるようになります。

/usr/lib/systemd/system/nginx.service
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
[Unit]
Description=nginx - high performance web server
Documentation=http://nginx.org/en/docs/
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/var/run/nginx.pid

ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID

#PrivateTmp=true
PrivateTmp=false

[Install]
WantedBy=multi-user.target
設定反映
1
2
# systemctl daemon-reload
# systemctl restart nginx

これで一応 /tmp 直下にファイル書き込みが出来るようになります。

ただ、ネット上でも /tmp 下にファイルが書き込めなくて、上記のように PrivateTmp=false にして解決している人が多いというかほとんどでしたが、PrivateTmp=false にすることはセキュリティレベルを下げること なので、PrivateTmp は無効にしないで true のままにしておくことをオススメします。 (tmp ディレクトリをより安全に使うための systemd でもあるので)

systemd で各サービスの /tmp をより安全に使うための代案

PrivateTmp は無効にしたくないし、有効のまま使いたい人も中にはいると思います。

PrivateTmp=true になっていると一番困るのは、
  • ディレクトリ名が長すぎて分かりづらい = cd とかコマンド入力しづらい
  • サービス再起動時にディレクトリ名 (パス) がコロコロ変わるので面倒くさい

なので、この部分をより分かりやすく使えるようにすれば、良いのではないかと。

ということで、ここからは PrivateTmp=true のままにして、各サービス専用の tmp ディレクトリをより使いやすくする方法についてご紹介します。

その1. 環境変数に tmp パス登録 (静的パス取得)

ディレクトリ名が長くて cd で入りづらいので、どうするかと言うと環境変数にこんな感じでパスを登録します。

~/.bash_profile
1
2
3
4
・・・
nginx_tmp="/tmp/$(ls -1 /tmp | fgrep -w 'nginx.service')/tmp"
php_fpm_tmp="/tmp/$(ls -1 /tmp | fgrep -w 'php-fpm.service')/tmp"
・・・
設定反映
1
# source .bash_profile
tmp ディレクトリパス確認
1
2
3
4
5
# echo $nginx_tmp
/tmp/systemd-private-ccf18adb045548ffa3942a31e47eda88-nginx.service-p0QriJ/tmp

# echo $php_fpm_tmp
/tmp/systemd-private-ccf18adb045548ffa3942a31e47eda88-php-fpm.service-PFgPqh/tmp

これで各サービスの tmp ディレクトリの参照が出来るようになりますが、サービス毎にパス登録が必要なのとサービスを再起動する度に tmp ディレクトリ名も変わるので、その都度 source ~/.bash_profile を実行してあげる必要があり、ちょっと行けてない気がします。

サービス再起動後に環境変数の再登録が必要
1
2
3
4
5
6
7
8
9
# systemctl restart nginx

# ls -ld $nginx_tmp
ls: /tmp/systemd-private-ccf18adb045548ffa3942a31e47eda88-nginx.service-p0QriJ/tmp にアクセスできません: そのようなファイルやディレクトリはありません

# source ~/.bash_profile    #### 環境変数再適用

# ls -ld $nginx_tmp
drwxrwxrwt 2 root root 4096  8月 12 17:51 /tmp/systemd-private-ccf18adb045548ffa3942a31e47eda88-nginx.service-isSkIG/tmp

ちなみに、.bashrc に alias でコマンド登録してもサービス再起動後に source ~/.bashrc の実行が必要となるため、状況は .bash_profile と変わりません。

その2. 特定サービスの tmp パス取得用のスクリプト作成 (動的パス取得)

結局のところ、サービスの tmp ディレクトリを動的に取得しないといけなくなるので、以下のように簡単なスクリプトを用意しておくと便利です。

名付けて lstmp としましょうか。 中身は至ってシンプルです。

パラメータは一つ。 サービス名だけ受け取るようにして、/tmpgrep をかけて、サービス名が見つかったらパスを取得。 そうでなければ、_usage を出力して exit します。

/usr/local/bin/lstmp
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash

function _usage()
{
   echo "$0 <service name>"
   exit 1
}

if [ $# -ne 1 ]; then
   _usage
fi

SERVICE="$1"

#TMP_DIR="/var/tmp/$(ls -1 /var/tmp | fgrep -w ${SERVICE})/tmp"
TMP_DIR="/tmp/$(ls -1 /tmp | fgrep -w ${SERVICE})/tmp"

if [ -d "$TMP_DIR" ]; then
   echo "PATH : $TMP_DIR"
   ls -l $TMP_DIR
else
   _usage
fi
権限付与
1
2
3
4
# chmod 755 /usr/local/bin/lstmp

# lstmp
usage: /usr/local/bin/lstmp <service name>
実行してみる
1
2
3
4
5
6
7
8
9
# lstmp nginx
PATH : /tmp/systemd-private-ccf18adb045548ffa3942a31e47eda88-nginx.service-isSkIG/tmp
合計 0

# systemctl restart nginx

# lstmp nginx
PATH : /tmp/systemd-private-ccf18adb045548ffa3942a31e47eda88-nginx.service-2QS0N9/tmp
合計 0
その他、実行結果
01
02
03
04
05
06
07
08
09
10
11
# lstmp php-fpm
PATH : /tmp/systemd-private-ccf18adb045548ffa3942a31e47eda88-php-fpm.service-GqqSYK/tmp
合計 0

# lstmp httpd
PATH : /tmp/systemd-private-ccf18adb045548ffa3942a31e47eda88-httpd.service-KbTWDf/tmp
合計 0

# lstmp chronyd
PATH : /tmp/systemd-private-ccf18adb045548ffa3942a31e47eda88-chronyd.service-Rx9Jyh/tmp
合計 0

一応 PATH 情報も出したほうが cd しやすいし、サービス再起動してからも ちゃんと新しいパスを拾ってくれるようになったのでこの程度で十分ではないかと思います。

終わりに

動的ウェブページだと php-fpm 使っているケースが多いと思いますが、php-fpm 動かしてる場合には、書き出したファイルが nginx とか httpd の tmp ではなく、php-fpm の tmp に入ることになるので、ご注意を。

以上、CentOS 7 : /tmp 直下にファイルが書き込めない でした。

この記事をシェアする

コピー & ペースト

 この記事のタイトルと URL をコピーする
CentOS 7 : /tmp 直下にファイルが書き込めない
https://server.etutsplus.com/systemd-why-can-not-write-tmp-directory/
 この記事の HTML リンクをコピーする
<a href="https://server.etutsplus.com/systemd-why-can-not-write-tmp-directory/" title="CentOS 7 : /tmp 直下にファイルが書き込めない" target="_blank">CentOS 7 : /tmp 直下にファイルが書き込めない - eTuts+ Server Tutorials</a>

コメント

コメントをどうぞ

Tutorial 詳細

/tmp 書き込めない所要時間:30分以内試験環境:CentOS 7.4 (64bit)関連カテゴリー: