エックスサーバーVPSで運営する、AIアプリ「Dify」が、ある日突然アクセス不能になるというトラブルが発生しました。
本記事では、その原因と対応、そして再発防止に向けて行った設定を共有いたします。
発生した問題とその背景
Difyにアクセスしようとした際に、ブラウザに以下のエラーが表示されました。
この接続ではプライバシーが保護されません(NET::ERR_CERT_DATE_INVALID)
原因は、SSL証明書(Let’s Encrypt)の有効期限切れでした。
実は少し前に、Let’s Encryptから「今後は証明書の期限切れ通知メールを送らなくなる」というお知らせメールが届いていたのですが、他の業務メールに埋もれて見逃してしまっていました。
初期対応と失敗したこと
当初、下記のような手順で対応を試みました。
- certbot renew による手動更新
- Nginx設定ファイルの確認と再起動
- VPSサーバーの再起動
しかし、これらを行っても復旧には至りませんでした。
再起動後はむしろ「ERR_CONNECTION_REFUSED」のエラーが出て、HTTP/HTTPS両方とも接続できない状態に。
原因は以下の2点が重なっていたためでした。
- VPSホスト側にインストールされていたNginxがポート443を占有しており、Dockerコンテナ内のNginxと競合していた
- 証明書はホスト側に更新されていたものの、Docker内のNginxが参照するパスにはコピーされていなかった
復旧までに行った対応
以下の対応により、無事にDifyは復旧しました。
1、ホストOSのNginxを停止・無効化
systemctl disable nginx
systemctl stop nginx
2、更新された証明書をDocker側にコピー
cp /etc/letsencrypt/live/ドメイン名/fullchain.pem /root/dify/docker/nginx/ssl/
cp /etc/letsencrypt/live/ドメイン名/privkey.pem /root/dify/docker/nginx/ssl/
3、Dockerコンテナの再起動
cd /root/dify/docker
docker compose down
docker compose up -d
上記3ステップでHTTPSアクセスが回復し、アプリの表示も正常に戻りました。
今後のための自動更新スクリプト
更新作業を自動化するため、以下のスクリプトを作成しました。
このスクリプトは、証明書の更新・Dockerへのコピー・再起動まで一括で行います。
ファイル名:/root/dify/docker/ssl_update.sh
!/bin/bash
DOMAIN=$1
EMAIL=$2
if [ -z “$DOMAIN” ] || [ -z “$EMAIL” ]; then
echo “使い方: $0 <ドメイン> <メールアドレス>”
exit 1
fi
certbot certonly –standalone -d $DOMAIN –agree-tos -m $EMAIL –non-interactive –force-renewal
cp /etc/letsencrypt/live/$DOMAIN/fullchain.pem /root/dify/docker/nginx/ssl/
cp /etc/letsencrypt/live/$DOMAIN/privkey.pem /root/dify/docker/nginx/ssl/
cd /root/dify/docker
docker compose down
docker compose up -d
echo “SSL証明書の更新と反映が完了しました。”
実行権限を付与:
chmod +x /root/dify/docker/ssl_update.sh
自動実行の設定(cron登録)
このスクリプトを、毎月1日の午前3時に自動実行されるよう設定しました。
0 3 1 * * /root/dify/docker/ssl_update.sh ドメイン名 メールアドレス >> /var/log/ssl_update.log 2>&1
/etc/crontab に追記済みです。
パケットフィルター設定(ファイアウォール)
エックスサーバーの公式マニュアルでは「SSL証明書の更新時には一時的にパケットフィルターをOFFにしてください」と案内されていますが、セキュリティの観点から常時ONにしておいた方が良いと思います。
ただしONにする場合は、以下の必要ポートを許可する必要があります。
ポート | プロトコル | 用途 |
TCP 22 | SSH | サーバー管理用(VS Codeなどから接続) |
TCP 80 | HTTP | Let’s Encrypt認証やリダイレクト用(任意) |
TCP 443 | HTTPS | Webアプリ(Dify)表示用(必須) |
これらの設定を行ったうえでパケットフィルターをONにすることで、セキュリティを保ちながらDifyの安定運用が可能になります。
まとめ
今回のトラブルを通じて、SSL証明書の有効期限切れに対する備えとして、通知メールに依存するのではなく、定期的な確認や自動更新の仕組みを構築することの必要性を改めて実感しました。
また、本件ではサーバー構成に起因するポート競合の問題が復旧を難航させる要因となりました。
今回の環境では、VPSのホストOS上にインストールされていたNginxがポート443(HTTPS)をリスンしていた状態で、同時にDockerコンテナ内のNginxも同じポートで待機しようとしたため、ポートのバインドに失敗してDockerコンテナ側のNginxが起動できなくなるという状態に陥っていました。
これはVPS環境においてよく見られる落とし穴のひとつであり、ホスト側で動作しているサービスが、Dockerコンテナ内のサービスと同一ポートを使用している場合、「先に起動している方がポートを専有」してしまうため、後に起動した側が正常に起動できないという問題が発生します。
特にDockerでは、docker-compose.yml に指定したポート(例:443:443)をホストとバインドする構成が一般的なため、ホスト側に同じポートをリッスンしているプロセスが存在していると、コンテナが起動できず、Webアプリケーションが一切表示されないという状態になります。
このような問題を回避するためには、ホストOS側のNginxを停止・無効化する、あるいはホストとコンテナで使用するポートを明確に分離する構成設計が必要です。特にDocker環境では「ポート競合の可能性があるプロセスは起動させない」ことを前提に設計・運用するべきであり、今回のようにホストNginxが意図せず稼働していた場合、それを無効化することがもっとも確実な対処方法となります。
この経験を通じて、VPS上のDocker運用では、サービス構成の重複や競合が起こりうることを前提に、システム全体のポート使用状況を把握・設計しておく重要性を再認識する結果となりました。