10G光クロス×MAP-Eサバイバル。VPS防壁をすり抜ける「生IP直打ちボット」をNginxレイヤーで無言でゴミ箱に捨てるセキュリティ設定

この記事は約21分で読めます。

wp-login.phpの防衛、fail2banの自動アクティブ防御、xmlrpc等のアプリ脆弱性対策と、これまで内側の防壁を徹底的に固めてきました。

しかし、一息ついて自宅サーバーのアクセスログを眺めていると、ある不気味な事実に気づきます。 こちらのドメイン(test-site.online)を名乗らずに、自宅ルーターのグローバルIPアドレスを直接ブチ込んで偵察してくる不審なボットのアクセスが届いているのです。
「特定の接続元IPを deny で弾けばいいのでは?」と思うかもしれません。しかし、ネットの海からランダムに襲ってくる無数のスキャンボットのIPを一つずつ追いかけてブラックリストに登録するのは不可能です。

今回は、Nginxの「看板チェック(Hostヘッダー)」の仕組みを逆手に取り、「正規のドメイン名を持っていないアクセスは、接続元IPが誰であろうと、エラー画面すら返さずにその場で通信をブチ切る(無言の門前払い)」という、最もスマートで超強力な設定を解説します。

🚨 検証フェーズで気づく盲点:Nginxの仕様と「Hostヘッダー」の力

すべての配管が繋がり、HTTPSでのアクセスも無事に確認できた検証の段階。ここで、ふと気になるのが「ドメイン名を使わず、自宅のグローバルIPアドレスを直接叩いた場合はどうなるのか?」という点です。

ブラウザにドメインではなく、自宅のIPアドレス(例:http://153.156.79.69)を直接ブチ込んでみてください。環境によっては、これでも自宅サーバーのWordPressのフロントページまでしっかり届き、表示されてしまうはずです。
なぜ生IP直打ちのアクセスが自宅サーバーまで通ってしまうのか。それは、Nginxがアクセスを処理する際、デフォルトでは「Hostヘッダー」が何であれ、最初にマッチしたサーバーブロックにパケットを流してしまう仕様だからです。

インターネット上の正規のアクセスには、「私は test-site.online に用事があります」という看板(Hostヘッダー)が必ず付いています。しかし、生のIPアドレスを直打ちしてくるスキャンボットは、看板の中身が「ただの数字の羅列(IPアドレス)」になっています。
そこで、自宅サーバー側、そして手前の中継VPS側のNginxに「この看板(正規のドメイン名)を持っていない客は、全員不審者とみなしてその場で即切断する」というトラップ用のサーバーブロックを設置し、検証時に見つけたこのセキュリティホールを完全に塞ぎます。

🛠️ 対策:中継VPS側のNginxに「最外壁のトラップ」を仕込む

設定は非常にシンプルです。これまでに編集してきたVPS(ConoHa)側の設定ファイル(test-site.online)の先頭(一番上)に、ボットを無言で即座に叩き落とすトラップ用の設定を直接書き加えます。

■ ステップ 1:VPS側の設定ファイルを開く

VPS側のコンソール(TeraTerm等)で、設定ファイルを開きます。

Nginx
sudo nano /etc/nginx/sites-available/test-site.online

■ ステップ 2:ファイルの中身を書き換える

ドメイン用の server { ... } 設定よりも上(一番先頭)に、以下の「生IP直打ち拒否専用」のトラップブロック(レイヤー1)を追加し、既存の設定を綺麗な「防衛レイヤー順」へと並び替えます。

Nginx
# ==========================================
# 【レイヤー1:最外壁】
# 【防壁4】ドメイン名以外(生IP等)のアクセスは無言で即時切断
# ※本家defaultファイルと共存するため「default_server」は指定しません
# ==========================================
server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name _; # すべての正規外のアクセス(生IP直打ちなど)を受け止める

    # HTTPSでの「生IP直打ち」を落とすため、既存の証明書ファイルを指定
    ssl_certificate /etc/letsencrypt/live/test-site.online/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/test-site.online/privkey.pem;

    # 1バイトのデータも返さず、即座に接続を切断する(Nginx独自コード)
    return 444;
}

# ==========================================
# 【レイヤー2:本番&内側の防壁】
# ドメイン経由の正常なアクセスを受け止め、自宅サーバーへ中継する
# ==========================================
server {
    server_name test-site.online;

    # ------------------------------------------
    # A. 接続受付・SSL暗号化設定
    # ------------------------------------------
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/test-site.online/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/test-site.online/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

    # ログイン画面(wp-login.php)へのアクセス制限
    location ~ ^/(wp-login\.php) {
        allow 192.168.1.0/24;  # あなたの自宅ローカルネットワーク(VPN接続時など)
        allow 153.156.79.0/24; # あなたのスマホや職場の固定回線の「本当のグローバルIP」
        deny all;              # それ以外は世界中すべて、自宅に転送すらさせずにここで遮断!

        # 許可されたクリーンなアクセスだけを自宅サーバーへ転送する
        proxy_pass http://153.156.79.69:50080; # 自宅サーバーのIPアドレスとカスタムポート
        include proxy_params;
    }

    # ------------------------------------------
    # B. 内側の防壁(特定の不審なリクエストをブロック)
    # ------------------------------------------
    # 【防壁1】XML-RPCへのアクセスを単体で強制拒否
    location ~ ^/xmlrpc\.php {
        deny all;
        access_log off;     # 攻撃ログでディスクが埋まるのを防ぐ
        log_not_found off;
    }

    # 【防壁2】REST APIのユーザー一覧露出を単体で強制拒否
    location ~ ^/wp-json/wp/v2/users {
        deny all;
        access_log off;     # 攻撃ログを無効化してディスク消費を防ぐ
        log_not_found off;
    }

    # 【防壁3】著者スキャン(?author=数値)を検知してトップへ強制転送
    if ($query_string ~* "author=([0-9]+)") {
        return 301 $scheme://$host/;
    }

    # ------------------------------------------
    # C. 中継処理(すべての防壁を抜けた安全なアクセスのみを自宅へ流す)
    # ------------------------------------------
    location / {
        proxy_pass http://118.7.101.133:50624;
        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;
        proxy_ssl_server_name on;
    }
}

# certbotの改造により無効化されたディレクティブ
# もう使うことはないので削除してしまって構いません
#server {
#    if ($host = test-site.online) {
#        return 301 https://$host$request_uri;
#    } # managed by Certbot

#    listen 80;
#    server_name ice-military.online;
#    return 404; # managed by Certbot
#}

貼り付けたら、キーボードの Ctrl + OEnter で保存し、Ctrl + X を押してエディタを閉じます。

■ ステップ 3:VPS側のNginxに設定を反映させる

最後に、VPS側のNginxの構文チェックを行い、問題がなければ設定をリロードして反映させます。

Bash
#構文チェック
sudo nginx -t

# 反映
sudo systemctl reload

🚨 【超重要】動作確認で「ブラウザ」を使ってはいけない理由

設定が完了すると、つい手元のスマホやPCのブラウザ(ChromeやSafariなど)を開いて http://[VPSのIPアドレス] と打ち込んでテストしたくなります。しかし、これは絶対にやめてください。泥沼のデバッグ地獄に落ちる原因になります。

現代のブラウザは非常に「お節介」で賢いため、以下のような挙動を裏で勝手に行います。

  • HTTPS自動昇格(HSTS):
    「このIP、さっきHTTPSで繋がったな」と記憶していると、http:// で打ち込んでも、裏で勝手に https://(443番)に書き換えてパケットを送信します。
  • リダイレクトの強固なキャッシュ:
    Nginx側で「80番のリダイレクト設定」を削除・無効化したとしても、ブラウザが過去のキャッシュを保持しているため、勝手にHTTPSへとアクセスを曲げてしまいます。
  • Hostヘッダーの自動補完:
    IPアドレス直打ちでアクセスしているつもりでも、ブラウザの親切心でリクエストヘッダーにドメイン名(test-site.online)を載せて送信してしまうことがあります。

このブラウザの「お節介」のせいで、「Nginxの設定が間違っているのか、ブラウザが過去のキャッシュで動いているのか」が完全に判別できなくなってしまうのです。

🧪 インフラ検証の絶対正義:自宅IP・ポート直撃テスト

この構築において最も重要であり、全ての苦労が報われる瞬間がこのテストです。

一切の忖度(キャッシュや自動書き換え)をしない curl コマンドを使って、外のネットワーク(VPSのコンソールなど)から「自宅のグローバルIP」、そして「特定される恐れのあるカスタムポート」まで含めて直接撃破しにいきます。

一般的なWebアクセスである「80番(HTTP)」「443番(HTTPS)」に加え、中継時に使っている内部の開放ポート番号(50080)まで直接指定して、自宅宛てに容赦なくノックを送り込んでみましょう。

Bash
# HTTP(通常の接続)で自宅を直撃
curl -I http://[あなたの自宅グローバルIP]

# HTTPS(暗号化接続)で自宅を直撃
curl -I https://[あなたの自宅グローバルIP]

実際に返ってくるログ:

Plaintext
curl -I http://153.156.79.69
curl: (28) Failed to connect to 153.156.79.69 port 80 after 7168 ms: Couldn't connect to server

curl -I https://153.156.79.69
curl: (28) Failed to connect to 153.156.79.69 port 443 after 7182 ms: Couldn't connect to server

curl -I http://153.156.79.69:50080
curl: (28) Failed to connect to 153.156.79.69 port 50080 after 7187 ms: Couldn't connect to server

curl -I https://153.156.79.69:50080
curl: (28) Failed to connect to 153.156.79.69 port 50080 after 7181 ms: Couldn't connect to server

ご覧ください。標準ポートから、裏側の配管であるカスタムポートに至るまで、全ての打鍵が例外なく Couldn't connect to server(接続不可) に終わる完全勝利です!

💡 なぜ Couldn’t connect to server が最強の防御なのか?

もしここで 403 ForbiddenEmpty reply (444) などのWebサーバー由来の応答が返ってきた場合、攻撃者に「エラーを返すNginxなどのWebサーバーがそこで動いている」という決定的な手がかりを与えてしまいます。
しかし、このログは「ポート80番も443番も、接続するための窓口(ポート)すら外の世界に存在しない」というルーターレベル(物理レイヤー)での即時拒否を意味しています。
私たちの10G光クロス回線(MAP-E)環境では、外部から直接80番や443番を叩かれても、ルーターがパケットをすべて虚空に投げ捨てます。攻撃者から見れば、ここはWebサーバーでも何でもない、ただの一般家庭のネット回線にしか見えません。自宅サーバーをネットの海から完全に「隠蔽(ステルス化)」できている決定的な証拠です。

🏆 これまで積み上げてきた設定の総仕上げ

ここまで、自宅サーバーを安全に運用するために必要な設定を一つずつ順に実装してきました。

  1. wp-login.php や管理画面への適切なアクセス制限
  2. 無差別な総当たり攻撃を遮断する fail2ban の設定
  3. xmlrpc.phpREST API?author= を使った情報スキャンへのアクセスブロック

これらWebアプリケーション側で必要な個別対策を施した上で、今回、インフラの根本であるネットワーク境界の設定を完了させた形になります。
そして、手元で実際にコマンドを叩いて得られた以下の結果こそが、この一連の設定の総仕上げとしての役割を物語っています。

  • ドメイン経由での正規のアクセスだけが、中継VPSを通り、すべてのフィルターを通過して自宅のWordPressへと安全に届く。
  • 一方で、ドメインを介さずに自宅のグローバルIPへ直接ポート80や443、ポートフォワーディングで解放しているポート番号(例:50080)を叩き込んでも、返ってくるのは Couldn't connect to server(接続不可)という応答だけ。

この「物理ポートの隠蔽」が機能していることにより、これまで仕込んできた wp-login.phpxmlrpc.php への個別対策は、「そもそも、直接自宅のポートを叩くことすらできない」というネットワークレイヤーの強固な防護壁に守られることになります。
「ドメインからは快適にアクセスできる。しかし、IPアドレスで直接狙おうとすると、どこを叩いても接続すらできずに虚空に消える。」
物理サーバーとネットワーク設定が美しく噛み合い、ステルス性の高い自宅サーバー環境が完成したところで、今回の構築手順はすべて完了です。

🔒 コラム:なぜ、ここで「完結」なのか?

ここまで読み進めて設定を終えた方は、「本当にこれだけで大丈夫なのか?」「もっと他にやるべき対策があるのでは?」と不安に思うかもしれません。
結論から言うと、個人運用の自宅サーバーにおけるセキュリティ設定は、これで間違いなく「完結(ゴール)」です。
私たちがここまでで構築した防御陣形を、セキュリティの階層(レイヤー)で整理すると以下のようになります。

レイヤー対策内容防御の効果
①物理・ネット
ワーク層
自宅ルーターのポート閉鎖&接続元制限XG-100NE等の仕様上、外部へ向けて80/443ポートはそもそも使用不可(開放・転送設定ができない)。そのため、一般的なWebスキャンポート(80/443)で自宅IPに直接アクセスを試みても、接続の受け皿自体が存在しないため即座に接続不可(Couldn’t connect)となる。
② インフラ(WEB)層自宅側のIP直打ち拒否
(return 444)
ドメインを伴わない不正なアクセス(ボットの自動スキャン等)をVPSの水際で無言即死させる。
③ アプリ(WordPress)層wp-login.php、xmlrpc.php、REST API、?author=の制限ドメイン経由で入ってきた正規のアクセスに対し、ブルートフォースやユーザー名偵察、脆弱性突きの余地を排除する。
④ OS・ホスト層fail2banの導入制限を突破しようとしつこくアクセスを試みるIPを、OSのファイアウォール(iptables)レベルで自動BANする。

これ以降にできる対策(高価なWAFの導入や、クライアント証明書によるガチガチの認証など)は、個人運用のブログとしては完全に「やりすぎ(過剰防衛)」の領域です。設定や運用の手間が増え、利便性が極端に下がる割に、得られる防衛メリットはほとんどありません。

セキュリティ対策は、やりすぎれば不便になり、放置すれば牙を剥きます。

今回ご紹介した「10Gクロス(MAP-E)特有の物理ポートの制約」に、中継VPSによる各種個別制限を組み合わせた構成は、安全性と利便性の天秤が最も美しく釣り合う、実戦的でスマートな終着点です。
面倒な設定を一通り終え、手元のコマンドで「接続不可」のログを確認できたなら、セキュリティ設定はコンプリートです。
外からは接続の糸口すら掴めないのに、ドメインを通せば自分の物理サーバーへと安全に繋がる。自宅で静かにファンを回す愛機を運用する上で、これほど頼もしいことはありません。
あとは日々のシステムアップデートに任せて、安心して物理機の稼働とコンテンツの運用を進めていきましょう。

というわけで、セキュリティ強化の設定についてのお話は(今のところは)以上となります。運用していく中でまた新しい発見やトラブルがあれば、いつかひょっこり記事を書くかもしれません。

管理人
管理人

自宅サーバーのセキュリティ強化、本当にお疲れ様でした!
あなたの快適で安全な自宅鯖ライフを、心より応援しています。
最後まで読んでいただき、本当にありがとうございました!

コメント

タイトルとURLをコピーしました