Communityコーディング&開発github.com

igor-batrakov/new-vps-setup

段階的な安全なガイド(エージェントスキル+チェックリスト)による、初心者向けの新しいUbuntu VPSの本番環境設定:ハードニング、自動更新、UFW、fail2ban、Docker、nginx+HTTPS、resticによる自動バックアップ、監視。

new-vps-setup とは?

このスキルは、クリーンなUbuntu 22.04/24.04 LTSサーバーを本番環境向けにセットアップし、可能な限り自律的に運用できるようにするための、ステップバイステップのガイドです。システム管理の経験がない初心者を対象としており、安全なデフォルト設定の採用、自動化による運用の自律性、各ステップの結果確認を重視します。具体的には、SSHのハードニングやfail2banによるブルートフォース対策、UFWファイアウォールの設定、Dockerのセキュアな利用方法、nginxとLet's Encryptを使ったHTTPS対応、resticを使用したオフサイトバックアップの自動化、そして監視について網羅的に解説します。各コマンドや設定ファイルの例を示しながら、なぜその設定が必要かの理由も説明するため、初心者が理解しながら進められるよう設計されています。

対応~Claude Code~Codex CLI~Cursor
npx skills add igor-batrakov/new-vps-setup

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

新しいVPSの本番環境セットアップ(初心者向け)

概要

このスキルは、クリーンなUbuntuサーバーを本番環境にセットアップし、その後可能な限り自律的に運用できるようにするためのステップバイステップガイドです。対象読者はシステム管理の経験がない方です。そのため、以下の点に重点を置いています。

  • 「プロのやり方」ではなく、安全なデフォルト設定。 NOPASSWD-sudoや、便利だからといってチェックを無効にすることは一切しません。専門家の効率化が、初心者には危険となり得ます。
  • 自動化による自律運用。 セキュリティ更新、バックアップ、監視は自動で動作するようにします。ユーザーはアラートにのみ対応します。
  • 各ステップは確認済み。 変更後は結果を確認し、問題がなければ次のステップに進みます。

対象OS: Ubuntu 22.04 / 24.04 LTS。他のディストリビューションではコマンドが異なります。

黄金のルール(変更を行う前には必ず)

  1. 設定ファイルのバックアップ: sudo cp /path/config /path/config.bak.$(date +%F-%H%M)
  2. 状態の確認: sudo systemctl status <service> / sudo docker ps
  3. 変更後は結果を確認する(ログ、ステータス、テスト接続)。
  4. 現在のSSHセッションは決して閉じないで、新しいセッションでアクセスとsudoが機能することを確認してからにしてください。リモートサーバーでSSHが切れると、プロバイダーのコンソール(VNC)に頼ることになります。

本番環境のセキュリティ原則(すべてのステップで遵守):

  • シークレット(APIキー、トークン、パスワード)は、.envファイルまたは環境変数にのみ保存し、コード内には決して記述しないこと。
  • .envファイルは必ず.gitignoreに追加する(そうしないと、初回コミット時にgitにシークレットが漏洩します)。
  • データベースはインターネットに公開せず、localhostまたはプライベートネットワーク/VPNを介してのみアクセス可能にする。
  • サーバー上でデバッグモードを有効にしない:DEBUG=False、NODE_ENV=production。
  • アプリケーションはrootで実行せず、専用のユーザーまたはコンテナを使用する。
  • 不要なポートを開放しない(管理画面、ダッシュボード、メトリクスは閉じるか、信頼できるIPのみ許可する)。
  • セキュリティチェックを無効にして利便性を優先しない(verify=False、allowAllはローカル専用)。
  • SSHはパスワードではなく、キーのみを使用する。

0. 診断 — 現在の状態を確認

最初に実行すること。 ホスティング事業者は、部分的な設定(cloud-init、ufwなど)が施されたサーバーを提供することがよくあります。

# システム情報
lsb_release -a && uname -r
free -h && df -h && nproc

# 現在のユーザーと権限
whoami && id

# SSH設定(ファイルだけでなく、有効な値を確認!)
sudo sshd -T | grep -iE 'permitrootlogin|passwordauthentication|pubkeyauthentication|^port '

# ファイアウォール
sudo ufw status verbose

# リッスン中のポート
sudo ss -tlnp

# 自動アップデート(有効?)
systemctl status unattended-upgrades 2>/dev/null | head -5
cat /etc/apt/apt.conf.d/20auto-upgrades 2>/dev/null

# Docker / fail2ban
sudo docker ps 2>/dev/null
sudo fail2ban-client status 2>/dev/null

診断後、見つかった情報をユーザーに報告し、確認を得るまでは設定を開始しないでください。


1. 初期設定とセキュリティ

セクション0と照合し、一部が既に実施済みの可能性があります。二重に実行しないでください。

1.1 システムの更新

sudo apt update && sudo apt upgrade -y
sudo apt autoremove -y
# カーネルが更新された場合は、再起動が必要になる可能性があります(出力を確認)。意識的に再起動してください:
# sudo reboot   # 約30~60秒後に再接続

1.2 専用のsudoユーザー(rootでは作業しない)

sudo adduser <USERNAME>            # パスワードを設定 — 強力なものを選び、パスワードマネージャーに保存する
sudo usermod -aG sudo <USERNAME>

初心者には、sudoにパスワード入力を要求する設定を残します。 これは意図的です。NOPASSWD-sudoは、キーやセッションが侵害された場合の最後の障壁を取り除きます。また、SSHキーを紛失した場合のフォールバックとしてもsudoパスワードが必要です(1.3参照)。

1.3 SSHキー(パスワードなしのキーログイン)

キータイプはed25519(RSAではありません)。ファイル名は<server>_<device>とし、パスフレーズを設定します。

ローカルマシンで実行:

ssh-keygen -t ed25519 -f ~/.ssh/<server>_<device> -C "<server>_<device>"
ssh-copy-id -i ~/.ssh/<server>_<device>.pub <USERNAME>@<SERVER_IP>

別のセッションでキーログインを確認(古いセッションは閉じないこと!):

ssh -i ~/.ssh/<server>_<device> -o IdentitiesOnly=yes <USERNAME>@<SERVER_IP> 'whoami; sudo whoami'

ローカルマシンの~/.ssh/configに便利なエイリアスを設定する:

Host <alias>
    HostName <SERVER_IP>
    User <USERNAME>
    IdentityFile ~/.ssh/<server>_<device>
    IdentitiesOnly yes

1.4 SSHのハードニング

/etc/ssh/sshd_config.d/99-hardening.confを作成:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
AllowTcpForwarding local
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers <USERNAME>

AllowTcpForwarding localは、ローカルポートフォワーディング(ssh -L — ドメイン設定前に127.0.0.1上のサービスにアクセスするため)を許可し、リモートフォワーディング(-R)を禁止します。フォワーディングが全く不要な場合は、noに設定してください。

適用して確認:

sudo sshd -t                                              # 構文はOK?
sudo systemctl restart ssh
sudo sshd -T | grep -iE 'permitroot|passwordauth'         # 有効な値がnoであること

注意点1 — cloud-initがハードニング設定を上書きする可能性があります。 /etc/ssh/sshd_config.d/50-cloud-init.confファイルにPasswordAuthentication yesが含まれていることが多く、sshd_config.d/では「最初に一致したものが優先」されます。自分の設定ファイルだけでなく、sudo sshd -T(有効な値)で確認してください。上書きされる場合は、50-cloud-init.conf内の該当行を修正または削除してください。

注意点2 — Ubuntu 24.04はssh.socketを使用します。 sshd_configのPortは無視されます。SSHポートを変更するには:sudo systemctl edit ssh.socket → ListenStream=<PORT>を追加(デフォルトは削除)。プロバイダーのコンソールで確認してください — ここで間違えるとアクセスがロックされます。

1.5 自動セキュリティ更新 ⭐(自律運用の要)

これは「サーバーが自分自身を管理する」ための最も重要なステップです。これがないと、パッケージの脆弱性が蓄積されます。

sudo apt install unattended-upgrades apt-listchanges -y
sudo dpkg-reconfigure -plow unattended-upgrades   # 「Yes」を選択 — 20auto-upgradesが作成される

/etc/apt/apt.conf.d/20auto-upgradesを確認:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";

/etc/apt/apt.conf.d/50unattended-upgradesで、以下のキー行をコメント解除/設定する:

// セキュリティ更新をインストールする(デフォルトで有効):
Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
    "${distro_id}ESMApps:${distro_codename}-apps-security";
    "${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::Remove-Unused-Dependencies "true";
// 更新に再起動が必要な場合の自動再起動(カーネル)。負荷が最も低い時間帯を選ぶ:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

「自動再起動」の判断: true = カーネル更新時にサーバーが夜間に自動再起動(より安全だが、短いダウンタイムが発生する可能性あり)。false = カーネルは更新されるが、再起動は手動(監視が必要)。単一の本番サーバーを持つ初心者には、通常true + 夜間の時間帯が推奨されます。

インストールなしのドライランで確認:

sudo unattended-upgrade --dry-run --debug 2>&1 | tail -20
systemctl list-timers 'apt-daily*'   # タイマーはアクティブ?

1.6 fail2ban(SSHブルートフォース対策)

基本設定で十分です。最初からrecidive/nftablesで複雑にする必要はありません。

sudo apt install fail2ban -y

/etc/fail2ban/jail.d/sshd.local:

[sshd]
enabled = true
bantime = 1h
findtime = 30m
maxretry = 4
# 自分自身をbanしないように:自分の静的IPを追加(確認方法:curl ifconfig.me)
ignoreip = 127.0.0.1/8 ::1 <YOUR_HOME_IP>
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd

1.7 タイムゾーンと(小規模VPSの場合)スワップ

sudo timedatectl set-timezone <Region/City>     # 例:Asia/Tokyo または UTC

# スワップはRAMが少ないVPS(< 2 GB)で必要。OOMがサービスを強制終了するのを防ぐ:
free -h
sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile
grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swap.conf && sudo sysctl --system

2. UFWファイアウォール

原則:すべてを閉じ、必要なものだけを開く。 最初にSSHを許可しないと、自分自身を締め出すことになります。

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'      # 重要:enableの前に行う!
sudo ufw enable
sudo ufw status verbose

WebサーバーのためにHTTP/HTTPSを追加:

sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'

管理画面/ダッシュボード/メトリクスは、「全員」ではなく、信頼できるIPのみに許可:

sudo ufw allow from <TRUSTED_IP> to any port <ADMIN_PORT> proto tcp comment 'admin panel'

sudo ufw resetは絶対に使用しないでください — SSHを含むすべてのルールがリセットされます。


3. Docker(オプション — アプリケーションをコンテナで実行する場合)

インストール

curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker <USERNAME>      # 再ログインしてグループを適用

dockerグループのメンバーシップは実質的にrootと同等です(docker run -v /:/hostを介して)。これは単一の管理者には許容されますが、サーバーへの完全なアクセスを信頼できないユーザーにはこのグループを与えないでください。

ログローテーション(必須 — そうしないとログがディスクを消費します)

/etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" }
}
sudo systemctl restart docker

⚠️ DockerはUFWをバイパスします

-p 8080:8080としてポートを公開すると、UFWで許可していなくてもインターネットからアクセス可能になります。 安全なオプション:

  • localhostにのみバインドする:-p 127.0.0.1:8080:8080(nginx/SSHトンネル経由でのみアクセス可能)。
  • データベースのポートは外部に一切公開せず、Dockerネットワーク内のサービス名で通信する。

4. nginx + HTTPS(オプション — Webアプリケーションとドメインがある場合)

nginxをアプリケーションのリバースプロキシとして使用(アプリケーションは127.0.0.1でリッスンし、外部にはnginxのみを公開)。

sudo apt install nginx -y

/etc/nginx/sites-available/<app>:

server {
    listen 80;
    server_name <DOMAIN>;
    location / {
        proxy_pass http://127.0.0.1:<APP_PORT>;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
sudo ln -s /etc/nginx/sites-available/<app> /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Let's Encryptで無料のHTTPS(certbotが自動で証明書を発行・更新設定):

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d <DOMAIN>
# 自動更新はタイマーで既に設定済み。確認:
sudo certbot renew --dry-run

保護:/etc/nginx/nginx.confにserver_tokens off;を追加(nginxのバージョンを隠す)し、セキュリティヘッダー(Strict-Transport-Security、X-Content-Type-Options nosniff)を追加します。


5. 自動バックアップ(restic + オフサイト)

完全な手順(インストール、リポジトリ、スクリプト、タイマー、復元): references/backups.md を参照 — ステップバイステップで記載されています。

絶対に破ってはいけない重要な原則:

  1. バックアップはオフサイトに置くこと。 同じサーバー(またはディスク)上のコピーは、サーバー/ディスクが故障した場合に役に立ちません。最低でも外部ストレージ(S3互換:Backblaze B2、rsync.netなど)。
  2. リポジトリの暗号化パスワードはサーバーの外部に保存する(パスワードマネージャー)。パスワードを紛失すると、すべてのバックアップが永久に読めなくなります。これはバックアップを失う最も一般的な原因です。
  3. 復元を確認していないバックアップはバックアップではありません。 少なくとも1回はテスト復元を実行してください(backups.mdの「学習用復元」参照)。
  4. 自動化: systemdタイマーによる毎日の実行 + 古いスナップショットのローテーション。

バックアップするもの

個々のパスではなく、カテゴリで考える — そうすれば何も忘れません:

カテゴリパスの例理由
システム設定/etc 全体nginx、ssh、ufw、fail2ban、systemdユニット — サーバーを「元のように」復元するため
アプリケーションデータ/home/<USERNAME>/app/data、/var/lib/<service>再インストールできないもの:ユーザーコンテンツ、アップロード
データベースダンプ/var/backups/*.sql.gzresticの前にpg_dump/.backupでダンプ(稼働中のDBファイルは破損している可能性があります)
シークレット.env、/root/.config/*暗号化されてバックアップされます(resticはリポジトリを暗号化)。暗号化パスワードはサーバーの外部に

バックアップしないもの(再インストール可能で、バックアップを肥大化させるだけ): node_modules、.venv/venv、Dockerイメージ(docker pull)、システムパッケージ(apt)、 キャッシュ、/var/log、/tmp。これらのパスはスクリプトの--excludeに既に含まれています(backups.md参照)。

保存期間(保持ポリシー) — ポリシーを選択する

resticは重複排除されたスナップショットを保存するため、「より多くのコピー」を保存してもディスク容量をあまり消費しません。 コマンド:restic forget --prune --keep-daily N --keep-weekly N --keep-monthly N。

ポリシーコマンド対象根拠
基本 (デフォルト)--keep-daily 7 --keep-weekly 4 --keep-monthly 6ほとんどの本番サーバー任意の過去7日間へのロールバック + 最大6ヶ月の履歴。新しいエラーも、「いつ壊れたのか」も捕捉可能。backups.mdに既に記載されています。
軽量--keep-daily 7 --keep-weekly 4貴重な蓄積データがないサイト/ボット約1ヶ月の履歴。最小限の容量でシンプル。古いデータの損失が重要でない場合に適しています。
長期--keep-daily 14 --keep-weekly 8 --keep-monthly 12重要なデータを持つDB、財務、法的要件1年の履歴 + 2週間の日次履歴。より多くの容量を消費しますが、「静かな」データ破損がすぐに気づかれなかった場合に備えます。

初心者への推奨: 基本ポリシーから始めてください — 両方のシナリオ(迅速なロールバックと長期履歴)をわずかなストレージコストでカバーします。forgetの数値を変更するだけでいつでも長期ポリシーに移行でき、過去のスナップショットが失われることはありません。

DBに関する重要事項: 稼働中のDBファイルをバックアップする場合、保持ポリシーは無意味です — ファイルが破損している可能性があります。常に最初にダンプ(pg_dump / SQLite .backup)を作成し、resticでダンプをバックアップしてください。


6. 監視

...

[翻訳対象外]

関連スキル