VPSは「借りた瞬間」から攻撃対象になる

VPS(仮想専用サーバー。1台の物理サーバーを分割して専有的に使えるサービス)を契約すると、グローバルIPアドレスが割り当てられ、インターネットから直接到達できる状態になります。SSHの標準ポート(22番)には、契約から数分後には自動化されたログイン試行が届き始めるのが普通です。攻撃者が狙っているのは「あなた」ではなく、設定が甘いまま放置されたサーバー全般です。個人開発の検証環境でも、乗っ取られれば踏み台や暗号資産マイニングに使われます。

ここでは特定のVPS事業者に依存しない汎用手順を、Ubuntu系(Ubuntu 22.04 / 24.04 想定)を主軸に整理します。CentOS Stream・AlmaLinux・Rocky LinuxなどRHEL系の場合の違いも要所で補足します。

作業前に確認しておくこと

この記事の手順は、途中で操作を誤ると自分自身がSSHでログインできなくなる可能性があります。着手前に次の2点を必ず押さえてください。

  • コンソール接続手段の確認:多くのVPS事業者は管理画面からブラウザ経由でサーバーの画面に入れる機能(コンソール、VNC、シリアルコンソールなど名称は事業者により異なる)を提供しています。SSHが使えなくなったときの唯一の救済手段なので、先に一度ログインできることを確かめておきます
  • SSHセッションをもう1つ開いておく:設定変更の検証は、いま繋がっているセッションを閉じずに、別ターミナルから新規接続して行います。失敗しても既存セッションが生きていれば設定を戻せます。

手順1: OSを最新の状態にする

OSイメージは作成時点で止まっているため、既知の脆弱性が残っています。まず全パッケージを更新します。

sudo apt update && sudo apt upgrade -y
sudo reboot   # カーネル更新があった場合

RHEL系は sudo dnf upgrade -y です。

手順2: 作業用ユーザーを作り、sudo権限を与える

rootで日常作業をすると、操作ミスがそのまま致命傷になります。また「rootという名前は全サーバー共通」なので、総当たり攻撃の第一候補です。一般ユーザーを作り、必要なときだけ sudo で昇格する形にします。

sudo adduser deploy
sudo usermod -aG sudo deploy   # RHEL系は -aG wheel

よくある失敗:sudoグループへの追加を忘れたままrootログインを無効化し、管理操作が一切できなくなるパターン。次の手順に進む前に、新ユーザーで sudo whoamiroot を返すか確認してください。

手順3: SSH鍵認証に切り替える

パスワード認証は、総当たりで破られる可能性が原理的に残ります。公開鍵認証(手元に秘密鍵、サーバーに公開鍵を置く方式)に切り替えます。鍵の生成はサーバーではなく手元のPCで行います。

# 手元のPCで実行
ssh-keygen -t ed25519 -C "my-vps"
ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]

ssh-copy-id が使えない環境では、サーバー側で手動配置します。パーミッションが緩いと鍵認証は無視されるため、権限指定は必須です。

mkdir -p ~/.ssh && chmod 700 ~/.ssh
vi ~/.ssh/authorized_keys      # 公開鍵を1行で貼る
chmod 600 ~/.ssh/authorized_keys

ここでいったんログアウトし、鍵だけで新規ログインできることを確認します。これを飛ばして次に進むのが、締め出し事故の最大の原因です。

手順4: rootログインとパスワード認証を止める

鍵でのログインが確認できたら、SSHサーバーの設定を締めます。Ubuntu 22.04以降の設定ファイルは冒頭に Include /etc/ssh/sshd_config.d/*.conf があり、同じ項目は「最初に読まれた値」が有効になります。クラウド向けイメージには 50-cloud-init.confPasswordAuthentication yes が入っていることがあり、メインの設定ファイルをいくら編集しても効かない、という混乱がよく起きます。

確実なのは、ファイル名が辞書順で先に来る設定ファイルを作る方法です。

sudo vi /etc/ssh/sshd_config.d/00-hardening.conf
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no

反映前に必ず構文チェックを行います。

sudo sshd -t && sudo systemctl restart ssh
sudo sshd -T | grep -Ei "permitrootlogin|passwordauthentication"

sshd -T は実際に適用される最終値を表示するので、意図どおりか確認できます。再起動後は既存セッションを閉じずに別ターミナルから接続テストしてください。

手順5: SSHポートをどう扱うか

22番以外への変更は「隠蔽」であって認証強化ではありません。鍵認証のみにしていれば安全性への寄与は限定的です。ただし、ログに流れ込む無差別スキャンのノイズが激減し、本当に見るべき記録が読みやすくなるという実務的な利点はあります。方針としては次のとおりです。

  • 鍵認証+ファイアウォールが済んでいるなら、22番のままでも実害は小さい
  • ログを静かにしたい場合のみ、1024〜65535の未使用ポートへ変更する

変更する場合の順序は「ファイアウォールで新ポートを開ける → SSH設定を変える → 新ポートで接続確認 → 旧ポートを閉じる」です。逆順にすると確実に締め出されます。なおUbuntu 24.04以降はSSHがsocket起動になっておりsshd_configPort だけでは変わりません。

sudo systemctl edit ssh.socket
# [Socket]
# ListenStream=
# ListenStream=2222
sudo systemctl daemon-reload && sudo systemctl restart ssh.socket

RHEL系ではSELinuxが有効なため、別途 sudo semanage port -a -t ssh_port_t -p tcp 2222 が必要です。

手順6: ファイアウォールで最小限のポートだけ開ける

「動いているサービスの分だけ開ける」が原則です。Ubuntu系は ufw が扱いやすいです。

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH          # ポート変更済みなら sudo ufw allow 2222/tcp
sudo ufw allow 80,443/tcp       # Webを公開する場合のみ
sudo ufw enable
sudo ufw status verbose

RHEL系の firewalld では次のようになります。

sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

よくある失敗:SSHを許可する前に ufw enable を実行して即座に切断される事故。ufw は有効化時に確認を求めますが、意味を読まずにyesと答えがちです。順序を守り、有効化後は必ず別セッションで疎通確認をしてください。データベース(3306、5432など)は外部公開せず、必要ならSSHポートフォワードや内部ネットワーク経由で接続します。

手順7: fail2banで総当たりを抑える

fail2banはログを監視し、短時間に失敗を繰り返すIPアドレスを一時的に遮断します。鍵認証のみにしていれば侵入リスクは低いものの、無駄なリソース消費とログ肥大を抑えられます。

sudo apt install fail2ban -y
sudo vi /etc/fail2ban/jail.local
[sshd]
enabled = true
port    = ssh
maxretry = 5
findtime = 10m
bantime  = 1h
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd

Ubuntu 24.04以降は認証ログが /var/log/auth.log ではなくjournaldに記録されるため、jailが起動しない場合は backend = systemd の指定を追加してください。自分のIPを誤ってBANしたときは sudo fail2ban-client set sshd unbanip 203.0.113.5 で解除できます。固定IPから作業しているなら ignoreip に登録しておくと安全です。

手順8: セキュリティ更新を自動化する

手動更新は必ず止まります。セキュリティ更新だけでも自動適用しておきましょう。

sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
sudo unattended-upgrades --dry-run --debug

RHEL系は dnf-automatic を導入し、/etc/dnf/automatic.confapply_updates = yes にして sudo systemctl enable --now dnf-automatic.timer です。カーネル更新は再起動しないと反映されないため、再起動タイミングの方針(手動で月次、あるいは自動再起動を許可するか)も決めておきます。

手順9: タイムゾーンとNTPを整える

時刻がずれているとログの時系列が追えず、証明書の検証やAPI連携にも影響します。

sudo timedatectl set-timezone Asia/Tokyo
timedatectl status   # System clock synchronized: yes を確認

時刻同期が no のままなら sudo systemctl enable --now systemd-timesyncd(またはchronyの導入)を行います。なお、複数拠点のログを突き合わせる運用ではUTCのまま揃える選択もあります。

手順10: ログの見方を覚えておく

設定して終わりではなく、たまに覗く習慣が重要です。

sudo journalctl -u ssh --since "1 day ago" | grep -i failed
last          # 成功したログイン履歴
sudo lastb    # 失敗したログイン履歴
sudo fail2ban-client status sshd
sudo ss -tulpn   # 待ち受け中のポートとプロセス

特に ss -tulpn は有用で、意図せず外部公開されているサービスを発見できます。0.0.0.0[::] で待ち受けているものは全インターフェース公開、127.0.0.1 ならローカル限定です。

事業者ごとの差分で気をつける点

  • コンソール接続の可否と操作性:提供形態は事業者ごとに異なります。日本語キーボード配列が使えない、コピー&ペーストができないなどの制約もあるため、緊急時に慌てないよう事前に触っておきます。
  • サーバー外部のファイアウォール機能:管理画面側でパケットフィルタやセキュリティグループを提供している事業者があります。OS内のufwで開けていても外側で塞がれていれば通りませんし、その逆もあります。通信不良時は両方を確認してください。
  • 初期状態の差:イメージによってはrootパスワードログインが有効、あるいは最初から鍵のみという場合があります。sshd -T で現状を確認してから作業するのが確実です。

初期設定チェックリスト

項目確認方法
コンソール接続を試した管理画面から実際にログイン
全パッケージを更新したapt upgrade 実行済み
sudo可能な一般ユーザーがあるsudo whoami が root を返す
鍵認証でログインできる新規セッションで接続成功
rootログイン・パスワード認証を無効化sudo sshd -T | grep permitrootlogin
ファイアウォールが有効sudo ufw status verbose
開放ポートが必要最小限sudo ss -tulpn
fail2banが稼働sudo fail2ban-client status sshd
自動セキュリティ更新が有効unattended-upgrades --dry-run
時刻が同期しているtimedatectl status

ここまでで、無差別攻撃に対する基本的な防御は整います。ディストリビューションのバージョンによってコマンドや既定値は変わるため、実行前に man や各ディストリビューション・VPS事業者の公式ドキュメントで最新の情報を確認してください。そのうえで、バックアップやスナップショットの取得方針、Webアプリを載せる場合のTLS証明書の設定へと進めるとよいでしょう。