FlaskアプリをVPS上で動かせるようになったら、次に挑戦したいのが「独自ドメインでの公開」と「HTTPS化」です。
前回はGunicorn・systemd・Nginxを使った運用を紹介しました。今回は実際のConoHa VPSで、Cloudflare DNS、Nginx、Certbotを使い、HTTPS公開まで検証しました。
実機検証で確認したのは、独自ドメインからの表示、HTTPSの保護された接続、HTTPからHTTPSへの301転送、証明書更新のdry-run成功です。
注意:今回のFlask Labは検証専用です。systemdによる常時起動や証明書の自動更新時のNginx再読み込みは未検証で、参考設定として区別します。
前回までのおさらい
このシリーズでは、ConoHa VPSでPythonのFlaskアプリを動かすところから、公開・運用までを実際に検証しながら紹介しています。
第3回では、Gunicorn・systemd・Nginxを使ってFlaskを安定運用する仕組みを紹介しました。今回はその続きとして、独自ドメインの設定からHTTPS化までを扱います。前回を読んでいなくても、各仕組みの役割から説明するので大丈夫です。

作業前の確認(前提条件)
今回の検証環境はUbuntu 22.04のConoHa VPSです。NginxとPythonを導入済みで、独自ドメインを管理できることを前提とします。ConoHaのセキュリティグループでHTTP(80番)とHTTPS(443番)が許可されているか、必要に応じてsudo ufw statusでも確認してください。既存サイトがある場合は設定をバックアップし、変更後は必ずnginx -tを実行します。
1. 今回の全体像
ブラウザ → DNS → Nginx → Gunicorn → Flaskという経路でアクセスします。DNSは名前からIPアドレスを調べる仕組みで、Webページを動かす役割ではありません。Nginxが外部からのHTTPS通信を受け取り、Gunicornへ渡し、Flaskがページを生成します。CertbotはHTTPSの証明書を取得・更新するツールです。systemdは本番でGunicornを自動起動・監視する仕組みですが、今回の検証では未使用です。

2. Flaskアプリを用意する
mkdir -p ~/projects/flask-lab
cd ~/projects/flask-lab
python3 -m venv .venv
source .venv/bin/activate
python -m pip install Flask gunicornBash他アプリと依存パッケージを混ぜないため、専用の仮想環境を用意します。次の内容をapp.pyに保存します。
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return """
<h1>Flask Labへようこそ!</h1>
<p>ConoHa VPSでFlaskが動いています。</p>
<p>次は独自ドメインとHTTPS化に挑戦します。</p>
"""
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8500)Python8500番ポートが空いていることを確認してから、Gunicornを一時的に起動します。
ss -ltnp '( sport = :8500 )'
gunicorn --workers 1 --bind 127.0.0.1:8500 app:appBashGunicornを動かしているターミナルは占有されるので、別のSSHターミナルで次を確認してください。
curl http://127.0.0.1:8500/BashここではVPS内部だけでの確認です。外部のブラウザで表示できるのはNginxを設定してからです。127.0.0.1に限定することで、Gunicornを直接インターネットには公開しません。
3. Cloudflareで独自ドメインを設定
今回はflask-lab.yasashii-it.com用にAレコードを追加しました。例として、タイプA/名前flask-lab/IPv4アドレスは自分のVPSのIP/TTL自動/プロキシは「DNSのみ(灰色の雲)」です。説明用IPアドレスを使う場合は203.0.113.10などを使用し、実際には必ず自分のVPSのアドレスへ置き換えてください。
DNSのみとしたのは、初回検証ではCloudflareのSSLモードによる影響を避け、VPSへの直接のHTTPS経路を確認するためです。Flexibleなどの設定ではリダイレクトループが起こることがあります。
dig +short flask-lab.yasashii-it.com ABashdigがない環境ではdnsutilsが必要な場合があります。DNSが反映されても、それだけではFlaskアプリは公開されません。

4. NginxでFlaskへ振り分ける
/etc/nginx/sites-available/flask-labに、まずHTTP用の設定を作ります。
server {
listen 80;
server_name flask-lab.yasashii-it.com;
location / {
proxy_pass http://127.0.0.1:8500;
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;
}
}Bashserver_nameで対象ドメインを、proxy_passで転送先を指定します。新しい設定へのリンクを作って検査してから反映します。
sudo ln -s /etc/nginx/sites-available/flask-lab /etc/nginx/sites-enabled/flask-lab
sudo nginx -t
sudo systemctl reload nginxBashGunicornを止めてアクセスすると、今回は502 Bad Gatewayになりました。Gunicornを起動し直すと200 OKが返りました。502はNginxの構文エラーとは限らず、転送先アプリが停止していたりポートが食い違っていたりする場合にも起きます。
curl -I http://flask-lab.yasashii-it.com/Bash
5. Certbotで無料SSL証明書を取得する
HTTPで確認できたら、Let’s Encryptの証明書を取得します。今回は既存の業務アプリが同居しているため、CertbotにNginxを自動編集させないWebroot方式を採用しました。
sudo mkdir -p /var/www/flask-lab/.well-known/acme-challengeBashHTTPのserver内に次のlocationを追加し、既存のlocation /より優先して認証ファイルを返します。
server {
listen 80;
server_name flask-lab.yasashii-it.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/flask-lab;
default_type text/plain;
}
location / {
proxy_pass http://127.0.0.1:8500;
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;
}
}Bashsudo nginx -t
sudo systemctl reload nginx
echo "flask-lab-acme-test" | sudo tee /var/www/flask-lab/.well-known/acme-challenge/test.txtBashhttp://flask-lab.yasashii-it.com/.well-known/acme-challenge/test.txtでテキストが表示されることを確認し、確認後はテストファイルを削除します。最初のCertbot実行ではメールアドレスや利用規約への同意を求められる場合があります。
sudo certbot certonly --webroot \
-w /var/www/flask-lab \
-d flask-lab.yasashii-it.com \
--cert-name flask-lab.yasashii-it.comBash今回の結果はSuccessfully received certificate.で、有効期限は2027年1月12日でした。取得した証明書をNginxに設定する作業は次の節です。

6. NginxをHTTPS対応にする
証明書を取得しただけではHTTPSは有効になりません。今回の実機で使ったHTTPからHTTPSへの転送、および443番ポートの設定例です。
server {
listen 80;
server_name flask-lab.yasashii-it.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/flask-lab;
default_type text/plain;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name flask-lab.yasashii-it.com;
ssl_certificate /etc/letsencrypt/live/flask-lab.yasashii-it.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/flask-lab.yasashii-it.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://127.0.0.1:8500;
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;
}
}Bash新しいVPSでは注意:/etc/letsencrypt/options-ssl-nginx.confと/etc/letsencrypt/ssl-dhparams.pemが存在しないことがあります。これは以前のNginxプラグイン実行等によって準備されていたファイルで、Webroot方式だけで必ず生成されるわけではありません。
ls /etc/letsencrypt/options-ssl-nginx.conf /etc/letsencrypt/ssl-dhparams.pem
存在しない場合は上記のincludeとssl_dhparamをそのまま使わず、現行の推奨TLS設定を確認して環境に合わせた設定に変更してください。次は一例ですが、この代替設定は今回実機未検証です。
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;Bash設定変更前のバックアップを取ったうえで、sudo nginx -tでエラーがないことを確認してからsudo systemctl reload nginxを実行します。
今回、ブラウザでは「この接続は保護されています」と表示されました。

7. HTTP→HTTPS転送も確認
curl -I http://flask-lab.yasashii-it.com/
curl -I https://flask-lab.yasashii-it.com/Bash今回のHTTPの結果はHTTP/1.1 301 Moved PermanentlyとLocation: https://flask-lab.yasashii-it.com/でした。HTTPS側が正常応答していることもブラウザで確認しています。
8. 証明書の更新テストと本番運用時の注意
sudo certbot renew --dry-run --cert-name flask-lab.yasashii-it.comBash今回の結果はCongratulations, all simulated renewals succeededでした。つまりテスト環境での更新手続きが成功したということです。実際の定期実行や、更新後にNginxが新しい証明書を読み込むかまでは確認していません。
以下は未実機検証の本番運用参考例です。 Webrootで証明書が更新されても、Nginxの再読み込みまで自動で行われるとは限りません。成功時に再読み込みするdeploy-hookの構成も検討してください。
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/sh
systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shBash定期実行の有無はsystemctl list-timers | grep -i certbotなどで確認できます。インストール方法によってタイマー名は異なります。既存サイトが同居する場合、デプロイフックは他の証明書更新にも影響するため、反映前に運用設計と十分な検証が必要です。
9. 本番運用ではsystemdでGunicornを管理する(未検証の参考設定)
この節は参考設定です。今回のFlask Labでは実行していません。 ターミナルで手動起動したGunicornは、ターミナルを終了すると停止します。本番ではsystemdでGunicornをサービスとして管理する方法があります。
例:/etc/systemd/system/flask-lab.service
[Unit]
Description=Flask Lab Gunicorn Service
After=network.target
[Service]
User=youruser
WorkingDirectory=/home/youruser/projects/flask-lab
ExecStart=/home/youruser/projects/flask-lab/.venv/bin/gunicorn --workers 1 --bind 127.0.0.1:8500 app:app
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetBash実際のユーザー名・パス・ポートへ変更してください。手動起動したGunicornが8500番を使っていれば先にCtrl+Cで止めないとポートが競合します。
sudo systemctl daemon-reload
sudo systemctl enable --now flask-lab
systemctl status flask-lab
journalctl -u flask-lab -n 50 --no-pagerBashこれは本番運用時の参考コマンドで、今回実行したものではありません。前回記事でsystemdを詳しく扱っているため、そちらも参照してください。今回は他アプリとの競合を防ぐため8500番を使い、再起動ポリシーは異常終了時だけ再起動するon-failureにしています。

10. まとめ|独自ドメインでFlaskをHTTPS公開できた
今回の実機検証では、Cloudflare DNSでサブドメインを設定し、NginxがGunicornへ転送し、Let’s Encryptの証明書を使ってHTTPS接続するところまで確認できました。HTTPからHTTPSへの301転送も動作しています。
DNSは行き先を調べ、Nginxは受付を行い、GunicornがPythonアプリを動かす。この役割が分かると、502エラーなどが起きても原因を整理しやすくなります。
今回は常時公開ではなく、必要なときにGunicornを起動する検証用として運用します。実際のサービスではsystemd・証明書更新・セキュリティを含めた運用設計が必要です。
自分のVPSでFlaskを動かしてみたい方へ
今回の検証にはConoHa VPSを使いました。Pythonの小さなWebアプリを作り、独自ドメインでHTTPS公開するところまで学べます。
環境構築から始めたい方は、第1回:ConoHa VPSでPythonを始めるも参考にしてください。
※この記事には広告・アフィリエイトリンクが含まれる場合があります。
