iOS&Androidスマートフォン開発(Swift/Kotlin/React-Native) および Next.js/LaravelをはじめとしたWeb開発ならば、ぜひ創業13年の弊社にお任せください。ワンストップでお届けします。お問合せはこちらから

ClamAVをAmazon Linux2023サーバに組み込んでウィルスチェックする

  2026年08月21日 03:40 / - PV

yuku_tasのアイコン

こんにちは、Webサービス開発者のyuku_tasです。

簡単なプロフィール: MoldSpoon Inc.代表。開業から15年、業界に携わって20年。Webサイト/iOS/Androidアプリ開発から、データ分析業務・コンサル業務、ベンチャーから保険会社まで様々な案件を個人会社の形で請け負ってまいりました。詳しくはこちら

図書館で本を予約したら、タイミングが被ってしまい一気に5冊も届いてしまいました...。

全部読み切れるかな... そんなyuku_tasです。

Hackerの皆様はいかがお過ごしでしょうか。

経緯

Linux用のアンチウィルスソフト ClamAVPHPプロジェクトに組み込みたくなりました。

Laravelをよく使うので、Validationでついでにウィルスチェックできる clamav-validatorを見つけて、試行錯誤してみたのですが 動く感じがせず。

Github Issueでは 「これは打ち捨てられてるレポジトリじゃないか?」言われてしまっていました...。


ないものは仕方ありません。ClamAVをライブラリを経由せず直接利用し、アップロードされたファイルをその場でスキャンして、不備がある場合は削除する。という設計思想で運用しようと思います。

実装

clamav本体をインストール

私のLinuxサーバのカーネルは

  • Amazon Lightsail
  • Amazon linux 2023
  • vCPU 2コア
  • メモリ 2GB

ですが、以下のコマンドで入りました。Amazon Linux2やCentOSならばyum でインストールしましょう。

sudo dnf update
sudo dnf install -y clamav clamav-update clamd
clamdが今回の方法のポイント

本体はあくまでclamavですが、clamdデーモンとして常時待機 させ、clamscan [file or dir]で、clamd経由でファイルスキャンを行う。という形で運用したいと思います。

定義ファイルを最新に

ウィルスをブロックするための定義ファイルを以下のコマンドで 最新のものに更新しておきます。

sudo freshclam

定義ファイルを自動更新にしておく

cronで以下を設定します。わたしは、ログイン中のsshユーザからcrontab -eで入れました。

N * * * *   /usr/bin/freshclam --quiet

Nのところは、3~57の間にして欲しいと。10の倍数は選択しないで欲しいと公式ドキュメントにも書いてあります 理由は、00分に実行されるスクリプトが多いので、とのことでした。そちらに従って、適当(ちゃんとした意味で)な数字を設定します。

to check for a new database every hour. N should be a number between 3 and 57 of your choice. Please don’t choose any multiple of 10, because there are already too many clients using those time slots.

1時間ごとに新しいデータベースをチェックすることになります。Nは、3から57の間で好きな数字を選んでください。10 の倍数は選択しないでください。これらのタイムスロットを使用するクライアントがすでに多すぎるからです。

freshclam.confを修正

/etc/freshclam.confを変更します。

下記の部分を探し、

# Send the RELOAD command to clamd.
# Default: no
#NotifyClamd /path/to/clamd.conf
NotifyClamd /etc/clamd.d/scan.conf

こちらのように修正して、定義ファイルの更新後に、clamd(デーモンスクリプト)に設定がすぐに伝わるように します。

/etc/clamd.d/scan.confを修正

# ログ出力設定
LogFile /var/log/clamd.scan
LogFileMaxSize 2M
LogSyslog yes
LogRotate yes

# TCPのソケットを使う
#LocalSocket /run/clamd.scan/clamd.sock

TCPSocket 3310
TCPAddr 127.0.0.1

このように設定してログを出したり、clamdが待ち受けるポートやIPなどを定義しておきます。

他、コメントアウトしてある箇所はそのまま変更なしでセーブします。

clamdの設定をしておく

下記がsystemdで管理されているclamdのユニット設定ファイルです。起動後、こう動きなさいというレシピみたいなもの ですね。

/usr/lib/systemd/system/clamd@.service

これをviで開いて、以下のように変更しておきます。(変更点のみ書きます)

sudo vi /usr/lib/systemd/system/clamd@.service

[Unit]
...

[Service]
...
TimeoutStartSec=600
MemoryLimit=512M
CPUQuota=30%

[Install]
...

後述しますが clamscanの処理が重いので、clamdは軽く動くようにメモリの上限値・CPUの上限値を指定して起動する ことにします。

clamdを立ち上げる

ここまで設定を行ってきたclamdsystemctlで起動時に立ち上がるプログラムとして登録し、実際にここで開始しておきます。

sudo systemctl enable clamd@scan
sudo systemctl start clamd@scan

これで、ファイルスキャンが実行できる準備が整いました。

実行してみたが、メモリを使いすぎる

こちらからテスト用のウィルス定義ファイルeicar.comをダウンロードしてFTPを経由して設置します。

もしくは、下記のcurlコマンドを実行して、Linuxサーバに直接落としてくることも可能です。

 curl -O https://secure.eicar.org/eicar.com

定義ファイルを入れたディレクトリに移動し、clamscanコマンドを実行したところマシンが固まってしまいました。

cd /path/to/eicar.com/exist
clamscan eicar.com

どうも、このインスタンスの2GB ではメモリが足りないようです。

スケールアップして、メモリ4GBのマシンで再度試す

そのままでは何もできないので、実験用にマシンをスケールアップしました。

その結果、実行中に3GBくらいはclamscanの実行にメモリを使ってしまう感じですが、30秒くらいで結果が出ました。

$ clamscan eicar.com
/home/ec2-user/work/eicar.com: Win.Test.EICAR_HDB-1 FOUND

----------- SCAN SUMMARY -----------
Known viruses: 8683058
Engine version: 0.103.9
Scanned directories: 0
Scanned files: 1
Infected files: 1
Data scanned: 0.00 MB
Data read: 0.00 MB (ratio 0.00:1)
Time: 27.969 sec (0 m 27 s)
Start Date: 2024:01:23 02:32:20
End Date:   2024:01:23 02:32:48

成功?してウィルスとして判定されたようです。

PHPからウィルススキャンソフトとして使う時のユースケースは?

clamscanコマンドをシェルなどを経由して叩き、標準出力に出た結果を受け取って、preg_matchなどの正規表現で判定する形になるかな、と思っています。

例えば、Laravelのファイル置き場はstorage/app/data以下などになりますので、

clamscan /storage/app/data/upload/xxxx.zip

呼び出すシェルスクリプトをLaravelのバッチから呼ぶといったイメージでしょうか。「Infected files」が1以上ならウィルスとして削除する、という判定はできるでしょう。

本当はkissit/php-clamav-scanのようなライブラリがあるといいのですが 残念ながらメンテされていないようですので、直接呼ぶ方向になりそうです。

今回は実験まで

ここまでの経緯でご説明した通り、2GBのマシンで動かないのはキツく、気軽に導入するわけには行かなそうでした。よって、別の方法でウィルススキャンする方法を考えるようにしました。

(→ その後、clamdscanを使えばこの問題は解決できることが分かりました。次の【2026年追記】をご覧ください。)

【2026年追記】clamdscanを使えば2GBマシンでも実用できる

記事公開後に運用・調査を重ねた結果、「clamscanが重い」問題への正しい答えはclamdscanだった ので追記します。

clamscanとclamdscanの違い

本文で実行したclamscanは、実行するたびに800万件以上のウィルス定義DBをメモリに読み込むコマンドです。1回のスキャンに3GB近いメモリと30秒近い時間がかかったのはこれが原因で、マシンスペックの問題ではありませんでした。

一方clamdscanは、すでに起動しているclamdデーモンにファイルを渡してスキャンさせるコマンドです。定義DBはclamdが常駐メモリに1回だけ読み込んでおり、スキャンのたびに読み直すことはありません。

コマンド定義DBの読み込み1スキャンあたりの負荷
clamscan実行のたびに読み込むメモリ約3GB・数十秒
clamdscanclamdが常駐分を使い回すほぼゼロ・通常は数秒以内

本文の構成ではせっかくclamdを立ち上げていたのに、スキャンにclamscanを使っていたため、デーモンの意味がなくなっていた訳です...。

使い方

clamdscan --fdpass /path/to/upload/file.zip

--fdpassは、ファイルディスクリプタごとclamdに渡すオプションです。clamdはclamscanユーザーなど別ユーザーで動くことが多く、スキャン対象ファイルへの読み取り権限がなくてエラーになる事故を防げるので、基本付けておくのがおすすめです。

判定は終了コードで行えます。

  • 0 ... ウィルスなし
  • 1 ... ウィルス検出
  • 2 ... スキャン自体のエラー(clamdが起動していない等)

clamdの常駐メモリも減らせる

clamd自体も定義DBの分(1GB強)のメモリを常駐で使いますが、/etc/clamd.d/scan.confに以下を追加すると 定義DB更新時のピークメモリを抑えられます。

ConcurrentDatabaseReload no

デフォルトでは定義DBの再読み込み時に新旧2つのDBを同時にメモリへ持つため、瞬間的に倍のメモリを消費します。この設定を入れると再読み込み中のスキャンは待たされますが、メモリ2GBのマシンで運用するなら実質必須の設定です。

それでも足りない場合は、スワップを追加しておくと定義DB更新時のOOM killerによるclamd強制終了を防げます。

Laravelからの呼び出し例

本文で「シェルを経由して叩く」と書いた部分も、Laravel 10以降ならProcessファサードでシンプルに書けます。

use Illuminate\Support\Facades\Process;

$result = Process::run([
    'clamdscan', '--fdpass', '--no-summary',
    storage_path('app/data/upload/uploaded.zip'),
]);

if ($result->exitCode() === 1) {
    // ウィルス検出。ファイルを削除し、ユーザーにエラーを返す
} elseif ($result->exitCode() !== 0) {
    // スキャン自体の失敗(clamdが落ちている等)。fail-close にするのが安全
}

ポイントは、スキャンできなかった場合(終了コード2)を「安全」と扱わないことです。clamdが落ちている間にアップロードされたファイルを素通しにしないよう、エラー時は受け付けない設計(fail-close)をおすすめします。

参考URL

以下のサイト様を参考にさせていただきました。(他にもあるかもです、気づき次第追加させていただきます。)

https://www.fumibu.com/use_clam-anti-virus_debian-package/#toc4
https://souiunogaii.hatenablog.com/entry/clamd-memory-limit
http://safe-linux.homeip.net/mail/clamav/clamd.html
https://blog.kamata-net.com/archives/12891.html

まとめ

実行ユーザーは今回は実用しないということでrootでやってしまいましたが、商用利用する場合は clamavユーザーなどを作り、rootで実行させないようにした方が安全だと公式ドキュメントに書いてありました。 実際に使う機会があればその方向でユーザーを作る形で考えたいと思います。

その場合、パーミッションの設定は残念ながらちょっと面倒そうでしたね。(追記の通りclamdscan --fdpassを使えば、この権限まわりの面倒もかなり軽減されます。)

当初「2GBのマシンでは実用が厳しい」と結論づけましたが、clamdを常駐させてclamdscanでスキャンする構成に すれば、2GBクラスのマシンでも十分実用できます。 これから導入される方は最初からその構成で組んでください。


この記事が何かのお役に立てれば幸いです。
最後までお読みいただきありがとうございました!

Share Me!