Linuxサーバーのセキュリティ基礎|最小権限・更新・fail2ban・ログ確認【初心者向け】

前の記事では、sed と awk(=行を列に分けて、集計や取り出しをするコマンド)でログを加工しました。今回は、そのログの読み方も使いながら、サーバーのセキュリティの基礎を整理します。

VPS を借りた次の日、何気なくログを見て青ざめたことがあります。世界中のいろいろな IP アドレスから、「root(=Linux の管理者ユーザー)」「admin」「test」などの名前で、何百回もログインを試された記録が残っていたのです。サーバーは、インターネットにつないだ瞬間から、休まずノックされつづけます。でも、やるべきことを順番に押さえておけば、こわがりすぎる必要はありません。

この記事では、自分のサーバーの状態を見て確かめることを中心に進めます。SSH(=ネットワークの向こうのコンピューターに、暗号化して安全にログインし、操作するしくみ)の設定やファイアウォールの切りかえのように、まちがえるとサーバーに入れなくなる操作は、文章だけで説明します。

💡
この記事でわかること
  • サーバーを守る4つの柱(最小権限・入口を減らす・更新・見張り)
  • 自分の権限と、秘密のファイルの権限を確かめる方法
  • ss で、外に向けて開いている入口を確かめる方法
  • ログイン記録や sudo の記録の見方
  • fail2ban のしくみと、状態の確かめ方
🔰
この記事の前提
  • sudo・apt・chmod を使える
  • systemctl・journalctl でサービスとログを見たことがある
  • SSH の鍵ログインと、VPS の初期設定の流れを知っている

まだの方は、前の記事「Linuxのsed・awk入門|ログを集計・置換するワンライナー【初心者向け】」から読むのがおすすめです。SSH とファイアウォール(=通信を、決めたルールで通したり止めたりする門番)の設定は「VPSを借りたら最初にやること|ユーザー作成・SSHの強化・ufwのチェックリスト【初心者向け】」でくわしく説明しています。

💡
用語メモ
  • 最小権限:人やプログラムに、必要なぶんだけの権限しか渡さないという考え方
  • 待ち受け:プログラムが、決まったポートで通信が来るのを待っている状態。ss では LISTEN と表示されます
  • 管理画面のコンソール:VPS の会社の Web 画面から開ける、サーバーを直接操作できる画面。SSH でつながらなくなったときの非常口になります(ない会社もあります)

用語で迷ったら、連載のまとめ記事の「よく出てくる用語ミニ辞典」でも確かめられます。


結論:「最小権限・入口を減らす・更新・見張り」の4つ

柱 やること 確かめるコマンド
最小権限 root で作業しない。秘密のファイルは自分だけ読める id、sudo -l、ls -l
入口を減らす 使わないポートは閉じる。待ち受けは必要なものだけ sudo ss -tlnp
更新 セキュリティの更新を早めに入れる apt list --upgradable
見張り ログイン失敗をくり返す相手を止める。ログを見る fail2ban-client status、journalctl

サーバーを守る4つの柱

準備:練習用のディレクトリを作る

  • 1
  • 2
  • 3
mkdir -p ~/linux-practice/security-test
cd ~/linux-practice/security-test
pwd

🖥️
この記事の出力例について

ここから先の出力例は、Debian 13での実行例です(ユーザー名 kaito・パソコン名 demo のダミー環境)。UbuntuやWSL(=Windows の中で Linux を動かせる、Microsoft のしくみ)では、表示が少し違うことがあります。

(例)

  • 1
  • 2
kaito@demo:~/linux-practice/security-test$ pwd
/home/kaito/linux-practice/security-test

STEP 1:最小権限を確かめる

まず、自分が誰で、どんな権限を持っているかを確かめます。

  • 1
  • 2
  • 3
  • 4
whoami
id
sudo -l
getent group sudo

(例)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
kaito@demo:~/linux-practice/security-test$ whoami
kaito
kaito@demo:~/linux-practice/security-test$ id
uid=1000(kaito) gid=1000(kaito) groups=1000(kaito),27(sudo),100(users)
kaito@demo:~/linux-practice/security-test$ sudo -l
Matching Defaults entries for kaito on demo:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty

User kaito may run the following commands on demo:
    (ALL : ALL) ALL
kaito@demo:~/linux-practice/security-test$ getent group sudo
sudo:x:27:kaito

sudo のパスワードを聞かれたら、自分のパスワードを入れます(画面には表示されません)。(ALL : ALL) ALL は「どのユーザーとしても、どのコマンドでも実行できる」という意味です。

sudo -l は「自分が sudo で何をしてよいか」の一覧です。sudo グループに入っているユーザーは、何でもできる強い権限を持っています。sudo グループのユーザーは、必要な人だけにしておきましょう。

次に、秘密の情報を入れるファイルの権限を確かめます。API キー(=API を使うときに必要な、合言葉のような文字列)などを書いた .env ファイルを想定した、ダミーのファイルを作ります。

  • 1
  • 2
  • 3
  • 4
echo "API_KEY=dummy-1234567890" > secret.env
ls -l secret.env
chmod 600 secret.env
ls -l secret.env

(例)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
kaito@demo:~/linux-practice/security-test$ echo "API_KEY=dummy-1234567890" > secret.env
kaito@demo:~/linux-practice/security-test$ ls -l secret.env
-rw-rw-r-- 1 kaito kaito 25 Nov  1 04:17 secret.env
kaito@demo:~/linux-practice/security-test$ chmod 600 secret.env
kaito@demo:~/linux-practice/security-test$ ls -l secret.env
-rw------- 1 kaito kaito 25 Nov  1 04:17 secret.env

検証環境は新しいファイルに「グループも書きこめる」設定(umask 002)なので、最初は -rw-rw-r-- でした。最後の r が「ほかの人も読める」という印です。chmod 600 のあとは -rw------- になり、自分だけが読み書きできる状態になりました。

600 は「自分だけ読み書きできる。ほかの人は何もできない」という権限です。ほかの人も読める状態の .env(=APIキーなどの秘密の設定を書いておくファイル)ファイルがないかは、次のように探せます。

  • 1
  • 2
find ~/linux-practice -type f -name "*.env" -perm /o+r
find ~ -type f -perm -o+w 2>/dev/null | head -n 5

(例)

  • 1
  • 2
kaito@demo:~/linux-practice/security-test$ find ~/linux-practice -type f -name "*.env" -perm /o+r
kaito@demo:~/linux-practice/security-test$ find ~ -type f -perm -o+w 2>/dev/null | head -n 5

どちらも何も表示されませんでした。ほかの人が読める .env も、だれでも書きこめるファイルもない、ということです。

🚫
権限まわりで、やってはいけないこと(文章だけで説明します)

「Permission denied が出るから」と、chmod 777 や chmod -R 777 で誰でも読み書きできるようにするのはやめましょう。秘密のファイルが丸見えになり、ほかのプログラムに書きかえられるおそれもあります。root で直接ログインして作業しつづけるのも、まちがいが即サーバー全体の事故になるので避けます。/etc/sudoers を直接編集するのも、書きまちがえると誰も sudo を使えなくなるので、この記事ではやりません。


STEP 2:開いている入口(ポート)を確かめる

サーバーの入口は「ポート(=1台のコンピューターの中で、どのサービスにつなぐかを分ける番号)で待ち受けているプログラム」です。-p をつけると、どのプログラムが待っているかまで表示されます。

  • 1
sudo ss -tlnp

(例)

  • 1
  • 2
  • 3
  • 4
  • 5
kaito@demo:~/linux-practice/security-test$ sudo ss -tlnp
State   Recv-Q  Send-Q   Local Address:Port     Peer Address:Port  Process
LISTEN  0       5            127.0.0.1:3142          0.0.0.0:*
LISTEN  0       20           127.0.0.1:25            0.0.0.0:*      users:(("exim4",pid=384,fd=5))
LISTEN  0       20               [::1]:25               [::]:*      users:(("exim4",pid=384,fd=6))

検証環境には SSH のサーバー(openssh-server)が最初は入っていないため、:22 の行はありませんでした。本物の VPS では、ふつう 0.0.0.0:22 や [::]:22 の sshd の行が表示されます。

0.0.0.0 と [::] は、右側の Peer Address(相手のアドレス)の欄にだけ出ています。これは「どの相手からでも受け付ける」という意味で、入口の場所を表す Local Address はすべて 127.0.0.1 か [::1] です。つまり、外から見える入口はありません。127.0.0.1:3142 は、検証環境で使っている apt のダウンロード用の中継のもので、読者の環境では表示されません。:25 の2行は、Debian に最初から入っているメール配送のプログラム(exim4)です。

Debian 13での実行画面

Local Address 意味
0.0.0.0:ポート や [::]:ポート 外からも見える入口
127.0.0.1:ポート 自分の中だけの入口(外からは見えない)

外から見える入口に、知らないプログラムがあったら要注意です。何のプログラムかを調べ、使っていないなら止めることを考えます。動いているサービスの一覧も見ておきましょう。

  • 1
systemctl list-units --type=service --state=running --no-pager

(例)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
kaito@demo:~/linux-practice/security-test$ systemctl list-units --type=service --state=running --no-pager
  UNIT                     LOAD   ACTIVE SUB     DESCRIPTION
  console-getty.service    loaded active running Console Getty
  cron.service             loaded active running Regular background program processing daemon
  dbus.service             loaded active running D-Bus System Message Bus
  exim4.service            loaded active running exim Mail Transport Agent
  systemd-journald.service loaded active running Journal Service
  systemd-logind.service   loaded active running User Login Management
  user@1000.service        loaded active running User Manager for UID 1000

Legend: LOAD   → Reflects whether the unit definition was properly loaded.
        ACTIVE → The high-level unit activation state, i.e. generalization of SUB.
        SUB    → The low-level unit activation state, values depend on unit type.

7 loaded units listed.

検証環境では、動いているサービスは7つでした。SSH のサーバーが入っていないので、ssh.service はありません。本物の VPS では、ssh.service などの行も並びます。

💡
ss で見る「開いている入口」と、ufw で見る「通してよい入口」は別のもの

sudo ss -tlnp でわかるのは、「どのプログラムが、どこで待っているか」です。外から本当に届くかどうかは、そのうえで、ファイアウォールが通すかどうかで決まります。VPS では、次の3つを組み合わせて見ます。

  • 待ち受け:sudo ss -tlnp の Local Address(この記事の STEP 2)
  • ufw:sudo ufw status で、許可しているポートを見るだけにします(VPS の初期設定の記事)
  • VPS の管理画面のファイアウォール:VPS の会社によってはあります(会社により異なります)

注意したいのが Docker(=アプリを、必要なものごと「コンテナ」という箱に入れて動かす道具)です。docker run -p 5678:5678 のように公開したポートは、ufw(=Debian・Ubuntu で使える、ファイアウォールをかんたんに設定するための道具)で許可していなくても外から届くことがあります。そのため、n8n(=画面上で「ノード」というブロックをつないで、自動化の流れを作れるツール)などは -p 127.0.0.1:5678:5678 のように、自分の中だけで待つ形にしておくのが安全です(Docker 入門の記事、VPS で n8n を動かす記事)。

どこか1つだけを見て「閉じている」と思いこまないことが、「入口を減らす」の柱のいちばん大事なところです。


STEP 3:更新を確かめる

セキュリティの弱点は、毎日のように見つかり、直されています。更新を放っておくと、直された弱点をねらわれます。

  • 1
  • 2
  • 3
sudo apt update
apt list --upgradable
systemctl status unattended-upgrades --no-pager

(例)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
kaito@demo:~/linux-practice/security-test$ sudo apt update
Get:1 http://deb.debian.org/debian trixie InRelease [140 kB]
Get:2 http://deb.debian.org/debian trixie-updates InRelease [47.3 kB]
Get:3 http://deb.debian.org/debian-security trixie-security InRelease [43.4 kB]
Fetched 231 kB in 0s (1877 kB/s)
All packages are up to date.
kaito@demo:~/linux-practice/security-test$ apt list --upgradable
kaito@demo:~/linux-practice/security-test$ systemctl status unattended-upgrades --no-pager
Unit unattended-upgrades.service could not be found.

All packages are up to date. のとおり、更新できるものはなく、apt list --upgradable は何も表示されませんでした。検証環境には unattended-upgrades が最初は入っていないため、Unit unattended-upgrades.service could not be found. と表示されています。

自動アップデートのしくみ(unattended-upgrades)は、「Linuxのバックアップと更新|rsync・tar・自動アップデートで守る【初心者向け】」で説明しています。


STEP 4:ログイン記録と sudo の記録を見る

誰が、いつログインしたか、誰が sudo を使ったかを見ます。前の記事の awk も使えます。

  • 1
  • 2
  • 3
last -n 5
sudo journalctl -u ssh --since "1 day ago" --no-pager | tail -n 10
sudo journalctl _COMM=sudo --since today --no-pager | tail -n 5

(例)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
kaito@demo:~/linux-practice/security-test$ last -n 5
-bash: last: command not found
kaito@demo:~/linux-practice/security-test$ sudo journalctl -u ssh --since "1 day ago" --no-pager | tail -n 10
-- No entries --
kaito@demo:~/linux-practice/security-test$ sudo journalctl _COMM=sudo --since today --no-pager | tail -n 5
Nov 01 04:17:27 demo sudo[942]:    kaito : TTY=pts/0 ; PWD=/home/kaito/linux-practice/security-test ; USER=root ; COMMAND=/usr/bin/journalctl -u ssh --since '1 day ago' --no-pager
Nov 01 04:17:27 demo sudo[942]: pam_unix(sudo:session): session opened for user root(uid=0) by (uid=1000)
Nov 01 04:17:27 demo sudo[942]: pam_unix(sudo:session): session closed for user root
Nov 01 04:17:29 demo sudo[947]:    kaito : TTY=pts/0 ; PWD=/home/kaito/linux-practice/security-test ; USER=root ; COMMAND=/usr/bin/journalctl _COMM=sudo --since today --no-pager
Nov 01 04:17:29 demo sudo[947]: pam_unix(sudo:session): session opened for user root(uid=0) by (uid=1000)

検証環境には last コマンドが最初は入っていないため、command not found になりました(日本語の環境では「コマンドが見つかりません」)。SSH のサーバーも入っていないので、journalctl -u ssh は -- No entries --(記録なし)です。

journalctl _COMM=sudo には、直前の2つの sudo journalctl の記録だけが出ています。kaito が、どの場所(PWD)で、どのコマンド(COMMAND)を root として実行したかがわかります。

本物の VPS では、journalctl -u ssh に「Failed password for invalid user admin from …」のような行がたくさん並びます。これが、冒頭で話した「ノック」です。失敗した相手の IP アドレスを数えるなら、次のように書けます(本物の VPS で試すときの例です)。

  • 1
sudo journalctl -u ssh --since today --no-pager | grep "Failed password" | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -n 5

(例)

  • 1
kaito@demo:~/linux-practice/security-test$ sudo journalctl -u ssh --since today --no-pager | grep "Failed password" | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -n 5

検証環境ではログインの失敗がないので、何も表示されませんでした。

$(NF-3) は「後ろから4番目の列」という意味です。この行では、IP アドレスがそこに入っています。

VPS の初期設定の記事でパスワードログインを止めていると、「Failed password」の行はほとんど出なくなり、かわりに「Invalid user … from …」や「Connection closed by …」のような行が並ぶことがあります。何も出ないときは、grep "Failed password" の部分を grep "Invalid user" に、$(NF-3) を $(NF-2) に変えて数えます。grep -i(大文字と小文字を区別しない)にすると、「Connection closed by invalid user …」の行まで入ってしまい、数が2倍になります。列の番号は、まず1行を目で見て確かめてから決めましょう。

「Invalid user」の行を、実際に数える(検証)

検証では、検証用のコンテナ(=アプリと、動かすのに必要なものをひとまとめにした箱)の中だけで SSH のサーバー(openssh-server)を動かし(記録の前に入れました。外からはつながりません)、自分のパソコンの中(127.0.0.1)から、ない名前の admin と nosuchuser で1回ずつログインを試しました。BatchMode=yes は「パスワードを聞かずにあきらめる」という意味で、Permission denied で終わるのが正しい結果です。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
systemctl is-active ssh
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null admin@127.0.0.1
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null nosuchuser@127.0.0.1
sudo journalctl -u ssh --since today --no-pager | tail -n 8
sudo journalctl -u ssh --since today --no-pager | grep "Failed password" | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -n 5
sudo journalctl -u ssh --since today --no-pager | grep -i "invalid user"
sudo journalctl -u ssh --since today --no-pager | grep -i "invalid user" | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -n 5
sudo journalctl -u ssh --since today --no-pager | grep "Invalid user" | awk '{print $(NF-2)}' | sort | uniq -c | sort -rn | head -n 5

(例)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
kaito@demo:~/linux-practice/security-test$ systemctl is-active ssh
active
kaito@demo:~/linux-practice/security-test$ ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null admin@127.0.0.1
Warning: Permanently added '127.0.0.1' (ED25519) to the list of known hosts.
admin@127.0.0.1: Permission denied (publickey,password).
kaito@demo:~/linux-practice/security-test$ ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null nosuchuser@127.0.0.1
Warning: Permanently added '127.0.0.1' (ED25519) to the list of known hosts.
nosuchuser@127.0.0.1: Permission denied (publickey,password).
kaito@demo:~/linux-practice/security-test$ sudo journalctl -u ssh --since today --no-pager | tail -n 8
Nov 01 06:20:03 demo sshd[859]: Server listening on 0.0.0.0 port 22.
Nov 01 06:20:03 demo sshd[859]: Server listening on :: port 22.
Nov 01 06:22:05 demo sshd-session[1117]: Invalid user admin from 127.0.0.1 port 60786
Nov 01 06:22:05 demo sshd-session[1117]: Connection closed by invalid user admin 127.0.0.1 port 60786 [preauth]
Nov 01 06:22:07 demo sshd-session[1120]: Invalid user nosuchuser from 127.0.0.1 port 60796
Nov 01 06:22:07 demo sshd-session[1120]: Connection closed by invalid user nosuchuser 127.0.0.1 port 60796 [preauth]
kaito@demo:~/linux-practice/security-test$ sudo journalctl -u ssh --since today --no-pager | grep "Failed password" | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -n 5
kaito@demo:~/linux-practice/security-test$ sudo journalctl -u ssh --since today --no-pager | grep -i "invalid user"
Nov 01 06:22:05 demo sshd-session[1117]: Invalid user admin from 127.0.0.1 port 60786
Nov 01 06:22:05 demo sshd-session[1117]: Connection closed by invalid user admin 127.0.0.1 port 60786 [preauth]
Nov 01 06:22:07 demo sshd-session[1120]: Invalid user nosuchuser from 127.0.0.1 port 60796
Nov 01 06:22:07 demo sshd-session[1120]: Connection closed by invalid user nosuchuser 127.0.0.1 port 60796 [preauth]
kaito@demo:~/linux-practice/security-test$ sudo journalctl -u ssh --since today --no-pager | grep -i "invalid user" | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -n 5
      2 from
      2 127.0.0.1
kaito@demo:~/linux-practice/security-test$ sudo journalctl -u ssh --since today --no-pager | grep "Invalid user" | awk '{print $(NF-2)}' | sort | uniq -c | sort -rn | head -n 5
      2 127.0.0.1

journal には、1回の失敗につき「Invalid user admin from 127.0.0.1 port 60786」と「Connection closed by invalid user admin 127.0.0.1 port 60786 [preauth]」の2行が出ました。行の名前のところが sshd ではなく sshd-session になっているのは、Debian 13 の SSH では、ログインの受け付けを sshd-session というプログラムが行うためです(待ち受けの行は sshd のままです)。

Failed password で数える形は、パスワードを送っていないので何も出ませんでした。grep -i "invalid user" では4行が拾われ、$(NF-3) で数えると 2 from と 2 127.0.0.1 という、まちがった結果になりました。「Invalid user … from 127.0.0.1 port 60786」の行では、後ろから4番目が from だからです。grep "Invalid user" と $(NF-2)(後ろから3番目の列)にすると、2 127.0.0.1(127.0.0.1 から2回)と正しく数えられました。

Debian 13での実行画面

Debian 13での実行画面

STEP 5:fail2ban で見張りを置く

fail2ban は、ログを見張って、短い時間に何度もログインに失敗した相手を、一定時間ブロックするソフトです。

  • 1
  • 2
  • 3
  • 4
sudo apt install fail2ban
systemctl status fail2ban --no-pager
sudo fail2ban-client status
sudo fail2ban-client status sshd

(例)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
kaito@demo:~/linux-practice/security-test$ sudo apt install fail2ban
Installing:
  fail2ban

Installing dependencies:
  python3-autocommand       python3-jaraco.text     python3-pyinotify   python3-typing-extensions
  python3-inflect           python3-more-itertools  python3-setuptools  python3-zipp
  python3-jaraco.context    python3-pkg-resources   python3-systemd     whois
  python3-jaraco.functools  python3-pyasyncore      python3-typeguard

Suggested packages:
  system-log-daemon  monit  sqlite3  python-pyinotify-doc  python-setuptools-doc

Summary:
  Upgrading: 0, Installing: 16, Removing: 0, Not Upgrading: 0
  Download size: 1861 kB
  Space needed: 9106 kB / 107 GB available

Continue? [Y/n] Y
…(途中省略)…
Setting up fail2ban (1.1.0-8) ...
Created symlink '/etc/systemd/system/multi-user.target.wants/fail2ban.service' → '/usr/lib/systemd/system/fail2ban.service'.
…(途中省略)…
Processing triggers for man-db (2.13.1-1) ...
kaito@demo:~/linux-practice/security-test$ systemctl status fail2ban --no-pager
● fail2ban.service - Fail2Ban Service
     Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; enabled; preset: enabled)
     Active: active (running) since Sun 2026-11-01 04:17:36 JST; 10s ago
 Invocation: 8d8055ed3c874f48b49f815802b42455
       Docs: man:fail2ban(1)
   Main PID: 1290 (fail2ban-server)
      Tasks: 5 (limit: 19197)
     Memory: 12.2M (peak: 12.9M)
        CPU: 85ms
     CGroup: /system.slice/fail2ban.service
             └─1290 /usr/bin/python3 /usr/bin/fail2ban-server -xf start
kaito@demo:~/linux-practice/security-test$ sudo fail2ban-client status
Status
|- Number of jail:      1
`- Jail list:   sshd
kaito@demo:~/linux-practice/security-test$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     0
|  `- Journal matches:  _SYSTEMD_UNIT=ssh.service + _COMM=sshd
`- Actions
   |- Currently banned: 0
   |- Total banned:     0
   `- Banned IP list:

fail2ban に、必要な部品15個がいっしょに入り、合わせて16個になりました。途中の長い表示は省略しています。入れただけで active (running)・enabled(自動で起動)になり、sshd の jail が1つ動いています。

検証環境では SSH へのログインの失敗がないので、Currently failed も Currently banned も 0 で、Banned IP list は空です。Journal matches の行は、「ssh のサービスのログ(journal)を見張っている」という意味です。

Debian 13での実行画面

fail2ban の「jail(牢屋)」は、見張るサービスごとの設定です。Debian では、最初から SSH 用の sshd の jail が有効になっています。おもな設定を見てみましょう(見るだけです)。

  • 1
  • 2
cat /etc/fail2ban/jail.d/defaults-debian.conf
grep -E "^(bantime|findtime|maxretry) " /etc/fail2ban/jail.conf

(例)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
kaito@demo:~/linux-practice/security-test$ cat /etc/fail2ban/jail.d/defaults-debian.conf
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]

[sshd]
backend = systemd
journalmatch = _SYSTEMD_UNIT=ssh.service + _COMM=sshd
enabled = true
kaito@demo:~/linux-practice/security-test$ grep -E "^(bantime|findtime|maxretry) " /etc/fail2ban/jail.conf
bantime  = 10m
findtime  = 10m
maxretry = 5
…(途中省略)…

Debian 13 の fail2ban の sshd の jail は、backend = systemd で、ファイルではなく journal(journalctl で見るログ)を読んで見張ります。ブロックには nftables(Linux のファイアウォールのしくみ)を使います。

grep の最初の3行が、全体の基本の値(bantime = 10m・findtime = 10m・maxretry = 5)です。「10分のあいだに5回失敗したら、10分ブロックする」という意味です。そのあとにも、ほかの jail 用の値(bantime = 48h など)がたくさん続くので、ここでは省略しています。

設定 意味
findtime この時間のあいだに
maxretry この回数失敗したら
bantime この時間ブロックする
ignoreip ブロックしない相手(自分の IP など)

🚫
この記事では、実際にブロックさせたり設定を変えたりしません(文章だけで説明します)

fail2ban の設定を変えるときは、jail.conf を直接書きかえず、/etc/fail2ban/jail.local を作って上書きするのが決まりです。ただし、bantime を長くしたり、ignoreip を書きまちがえたりすると、自分自身がブロックされて、サーバーに入れなくなることがあります。わざとログインに失敗してブロックを試すのも、本物のサーバーではやめましょう。もしブロックされたら、VPS の管理画面のコンソールから入り、sudo fail2ban-client set sshd unbanip 自分のIP で解除します。また、ファイアウォール(ufw)を有効にする操作や、SSH の設定ファイル(VPS の初期設定の記事では /etc/ssh/sshd_config.d/ の中のファイル)でパスワードログインや root ログインを止める操作は、まちがえると接続が切れて戻れなくなるので、VPS の初期設定の記事の手順どおりに、管理画面のコンソールを開いた状態で行ってください。

🤖 詰まったらAIに聞こう:このサーバーの守りは足りている?

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
Linuxの初心者です。VPSのセキュリティを確認しています。
次の結果を見て、危なそうなところと、優先して直す順番を教えてください。
サーバーに入れなくなるおそれがある操作には、必ず注意を書いてください。

【OS】例:Debian 13
【sudo ss -tlnp の結果】ここに貼る(IPアドレスはダミーに置き換える)
【apt list --upgradable の結果】ここに貼る
【sudo fail2ban-client status sshd の結果】ここに貼る(Banned IP list は伏せる)
【気になっていること】例:知らないポートが開いている

🔒
AIに貼る前に:秘密情報やパスワードは貼らない

セキュリティの相談では、とくに貼ってはいけないものが多くなります。.env の中身・秘密鍵・パスワード・API キーは、どんな場合も貼らないでください。サーバーの本物の IP アドレス、ドメイン名(=IP アドレスの代わりに使う、example.com のような名前)、ユーザー名、SSH のポート番号も、203.0.113.10 のようなダミーに置き換えましょう。「どこが守りの弱いところか」がわかる情報は、それ自体が攻撃のヒントになります。


後片づけ

練習用のディレクトリを消します。fail2ban は、本物の VPS では入れたままにしておくのがおすすめです。練習用のパソコンで、もう使わない場合は消してかまいません。

  • 1
  • 2
  • 3
  • 4
cd ~/linux-practice
rm -r security-test
ls
sudo apt purge fail2ban

(例)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
kaito@demo:~/linux-practice/security-test$ cd ~/linux-practice
kaito@demo:~/linux-practice$ rm -r security-test
kaito@demo:~/linux-practice$ ls
archive-test  chmod-test  edit-test  find-test  grep-test  link-test  perm-test  redirect-test
kaito@demo:~/linux-practice$ sudo apt purge fail2ban
The following packages were automatically installed and are no longer required:
  python3-pyasyncore  python3-pyinotify  python3-systemd  whois
Use 'sudo apt autoremove' to remove them.

REMOVING:
  fail2ban*

Summary:
  Upgrading: 0, Installing: 0, Removing: 1, Not Upgrading: 0
  Freed space: 2244 kB

Continue? [Y/n] Y
(Reading database ... 27213 files and directories currently installed.)
Removing fail2ban (1.1.0-8) ...
Processing triggers for man-db (2.13.1-1) ...
(Reading database ... 26929 files and directories currently installed.)
Purging configuration files for fail2ban (1.1.0-8) ...

security-test がなくなりました。ほかのフォルダは、これまでの記事の練習で作ったものです。purge は、設定ファイル(/etc/fail2ban)もいっしょに消します。消えたことは、次のように確かめられます。

(例)

  • 1
  • 2
kaito@demo:~/linux-practice$ ls /etc/fail2ban
ls: cannot access '/etc/fail2ban': No such file or directory

日本語の環境では ls: '/etc/fail2ban' にアクセスできません: そのようなファイルやディレクトリはありません と表示されます。

The following packages were automatically installed and are no longer required は、「いっしょに入った部品が、もう使われていません」というお知らせです。消したいときは、表示のとおり sudo apt autoremove で消せます。


よくある質問

Q. last が command not found になります。

Debian 13 では、last は最初から入っていません。Debian 13 のリリースノート(5.1.9)では、util-linux が last と lastb を、login が lastlog を、もう用意しなくなったと案内されています。かわりに、last は wtmpdb(libpam-wtmpdb といっしょに入れる)、lastlog は lastlog2、lastb は lslogins --failed を使います。同じように、ファイルを開いているプログラムを探す lsof も、Debian 13 には最初から入っていません。

検証では、sudo apt install wtmpdb で入れてから last を打ちました(入れるときの表示は省いています。いっしょに libpam-wtmpdb も入りました)。

  • 1
  • 2
  • 3
  • 4
  • 5
last -n 5
apt-cache show wtmpdb | grep -E '^(Package|Version|Description-en):'
sudo apt install wtmpdb
command -v last; readlink -f "$(command -v last)"
last -n 5

(例)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
kaito@demo:~/linux-practice/security-test$ last -n 5
-bash: last: command not found
kaito@demo:~/linux-practice/security-test$ apt-cache show wtmpdb | grep -E '^(Package|Version|Description-en):'
Package: wtmpdb
Version: 0.73.0-3+deb13u1
Description-en: utility to display login/logout/reboot information
kaito@demo:~/linux-practice/security-test$ command -v last; readlink -f "$(command -v last)"
/usr/bin/last
/usr/bin/wtmpdb
kaito@demo:~/linux-practice/security-test$ last -n 5
open_database_ro: Cannot open database (/var/log/wtmp.db): unable to open database file

入れると /usr/bin/last ができ、中身は wtmpdb になっていました。ただし、入れた直後の last -n 5 は「Cannot open database (/var/log/wtmp.db)」(記録のデータベースを開けません)というエラーになりました。入れてすぐに last を使えるとは限らない、ということです(検証では、このあとの動きは確かめていません)。入れなくても、この記事の STEP 4 の journalctl -u ssh でログインの記録は見られます。

Debian 13での実行画面

Q. ufw があれば、fail2ban はいりませんか?

役割がちがいます。ufw は「どのポートを通すか」を決める門で、SSH のポートは自分が入るために開けておく必要があります。fail2ban は、その開いている SSH の門の前で、何度も失敗する相手を一定時間止める見張りです。鍵ログインだけにしていても、記録が増えすぎるのを防げるので、入れておくと安心です。

Q. WSL でもこの記事のことをやる必要はありますか?

WSL は、ふつうはインターネットから直接つながる場所ではないので、fail2ban や SSH の守りが必要になる場面は少なめです。ただし、WSL の中でサーバーを動かして外に見せる設定をしたときは別です。環境により異なります。.env を chmod 600 にする、chmod 777 にしない、といった最小権限の考え方は、WSL でもそのまま大切です。


まとめ

  • サーバーを守る柱は「最小権限・入口を減らす・更新・見張り」の4つ
  • 秘密のファイルは chmod 600。chmod 777 はしない
  • sudo ss -tlnp で外から見える入口(0.0.0.0)を確かめ、知らないものは調べる
  • apt list --upgradable と自動アップデートで、更新を放っておかない
  • journalctl -u ssh と last で、ログインの記録を見る
  • fail2ban は、何度も失敗する相手を一定時間ブロックする見張り。設定の変更は自分を締め出さないよう慎重に

次の記事では、ここまでの知識をまとめて使う、「動かない」ときの切り分けフローを学びます。権限・PATH・ログ・ネットワークの順に見ていく方法です。


次に読む記事


関連する記事