「Flask で作ったアプリを本番サーバーで動かしたい」と思ったとき、開発中に使う flask run や python app.py のままでは不十分です。シングルスレッドで同時アクセスに弱く、サーバーを再起動すると自動で立ち上がってくれないからです。
結論から言うと、本番では Gunicorn(WSGIサーバー)・Nginx(リバースプロキシ)・systemd(プロセス監視)の三点セットを組み合わせます。本記事では Ubuntu 24.04 LTS の公式 Docker イメージの中に実際にこの構成を組み立て、curl で本物の HTTP レスポンスが返るところまで検証しました。
検証はすべて ubuntu:24.04 コンテナ内で実コマンドを実行し、返ってきた本物の出力をもとに書いています。バージョン番号・プロセスID・レスポンスヘッダは2026年6月14日時点の実測値です。
この記事のポイント
- Ubuntu 24.04 は Python 3.12.3 が標準搭載。
python3-venvで仮想環境を作り、その中に Flask・Gunicorn を入れるのが基本(実測) apt install gunicornは 20.1.0、pip install gunicornは 26.0.0 が入った。venv+pip 版を推奨(実測で確認)- Gunicorn を
--workers 3で起動すると master + worker ×3 が立ち上がり、Unix ソケット経由で Flask が応答する(実測) - Nginx → Unixソケット → Gunicorn → Flask の全チェーンに
curlを通すと200 OKが返ることを実測 - systemd の
Restart=alwaysでクラッシュ時に自動復旧、enableでサーバー再起動時も自動起動する
目次
- 全体構成を把握する
- 前提環境と実測したバージョン
- STEP1: パッケージのインストールとバージョン確認
- STEP2: Flask アプリと仮想環境を準備する
- STEP3: Gunicorn でアプリを起動する
- STEP4: systemd サービスを作成する
- STEP5: Nginx をリバースプロキシとして設定する
- ワーカー数の最適化
- よくあるエラーと解決策
- まとめ
全体構成を把握する
まず完成形を頭に入れておきましょう。インターネットからのリクエストは次の順番で処理されます。

Nginx がポート 80/443 でリクエストを受け、CSSや画像などの静的ファイルは自分で直接配信します。動的なリクエストは Unix ソケット経由で Gunicorn に転送し、Gunicorn が Flask アプリ(app:app)を呼び出します。そして systemd が Gunicorn の master プロセスを監視し、落ちたら自動で再起動します。
なぜ flask run を本番で使ってはいけないのか
flask run と python app.py はシングルスレッドの開発用サーバーです。同時アクセスが来ると順番待ちになり、少し負荷がかかるだけでレスポンスが遅延します。Gunicorn は複数の worker プロセスを立ち上げて同時に処理するため、本番環境に向いています。起動時のログにも Using worker: sync と表示され、worker が並列で動いていることが分かります。
前提環境と実測したバージョン
本記事の検証環境は以下の通りです。VPS(Vultr・DigitalOcean・ConoHa など)で Ubuntu 24.04 を新規インストールしたサーバーを想定し、同じ環境を ubuntu:24.04 Docker 公式イメージで再現して実コマンドを流しています。

| 項目 | 実測した内容 |
|---|---|
| OS | Ubuntu 24.04.4 LTS(Noble Numbat) |
| Python | 3.12.3(OS標準搭載) |
| pip | 24.0 |
| Nginx | 1.24.0(Ubuntu公式リポジトリ) |
| Flask | 3.1.3(venv+pip 最新版) |
| Gunicorn | 26.0.0(venv+pip 最新版) |
Ubuntu 24.04 では python3 --version が Python 3.12.3 を返し、Python が最初から入っています。venv の中に pip install flask gunicorn を実行すると Successfully installed flask-3.1.3 gunicorn-26.0.0 werkzeug-3.1.8 ... と表示され、Flask 3.1.3 と Werkzeug 3.1.8 が入りました。
STEP1: パッケージのインストールとバージョン確認
手順1:apt を更新してから必要パッケージを入れる
apt update を忘れがちですが、これをやらないと古いインデックスのままインストールが走ったり、パッケージが見つからないエラーが出たりします。最初に必ず実行してください。
Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease
Reading package lists… Done
$ sudo apt install -y python3 python3-venv python3-pip nginx
Setting up python3 (3.12.3-…) …
Setting up nginx (1.24.0-…) …
$ nginx -v
nginx version: nginx/1.24.0 (Ubuntu)
手順2:Gunicorn は apt と pip でバージョンが大きく違う
ここが本記事でいちばん注意してほしいところです。同じ Gunicorn でも、入手経路によって入るバージョンがまるで違います。実際に ubuntu:24.04 の中で両方を入れて比べました。

apt install gunicorn だと 20.1.0(正確には 20.1.0-6ubuntu0.1)が入りました。一方で venv の中で pip install gunicorn すると 26.0.0 です。apt 版は Ubuntu リポジトリの安定版で更新が遅く、しかも venv の Flask とは別の環境に入ってしまいます。本記事では venv 内で pip インストールする方法を採用します。
apt 版が悪いわけではない
apt 版の Gunicorn 20.1.0 も動作はします。ただ Flask アプリと同じ仮想環境にまとめておいた方が依存関係の管理が楽で、systemd の ExecStart も venv 内の gunicorn を絶対パスで指すだけで済みます。これが後々ハマりにくい構成です。
STEP2: Flask アプリと仮想環境を準備する
手順1:アプリ用ディレクトリと仮想環境を作る
$ sudo chown $USER:$USER /var/www/myapp
$ cd /var/www/myapp
$ python3 -m venv venv
$ venv/bin/pip install flask gunicorn
Successfully installed blinker-1.9.0 click-8.4.1 flask-3.1.3 gunicorn-26.0.0 itsdangerous-2.2.0 jinja2-3.1.6 markupsafe-3.0.3 werkzeug-3.1.8
上の Successfully installed ... は実際に返ってきた行です。Flask 3.1.3 と Gunicorn 26.0.0、依存の Werkzeug 3.1.8・Jinja2 3.1.6 などが一度に入ります。systemd の Unit ファイルでは /var/www/myapp/venv/bin/gunicorn を直接参照するので、この venv のパスを後で使います。
手順2:最小構成の Flask アプリを作る
まず動作確認用の最小の app.py を作ります。本番アプリがある場合も、この構成を参考に置き換えてください。
from flask import Flask, jsonify
app = Flask(__name__)
@app.route(‘/’)
def index():
return ‘Hello from Flask on Ubuntu 24.04!’
@app.route(‘/health’)
def health():
return jsonify({‘status’: ‘ok’, ‘service’: ‘flask-gunicorn-nginx’})
EOF
/health エンドポイントを用意しておくと、Nginx のヘルスチェックや監視ツールから使えて便利です。後ほどこの /health が JSON で {"status":"ok"} を返すところを実際に確認します。
ディレクトリ構成の確認
systemd や Nginx の設定でパスを指定するため、ファイルの場所は記録しておいてください。本記事では次の構成を前提にしています。
/var/www/myapp/
├── app.py ← Flask アプリ本体
├── venv/ ← 仮想環境(pip パッケージが入る)
└── gunicorn.sock ← Gunicorn 起動時に作られる Unix ソケット
STEP3: Gunicorn でアプリを起動する
手順1:Unix ソケットで Gunicorn を起動する
Nginx と連携する際は TCP ポートではなく Unix ソケット経由の接続を推奨します。同一サーバー内の通信なので余計なネットワーク層を挟まず、外部からポートに直接アクセスされるリスクもありません。実際に --workers 3 で起動してみます。

起動ログを見ると、Starting gunicorn 26.0.0 に続いて master プロセス(PID 4833)が立ち上がり、その下に Booting worker with pid: 4835 / 4836 / 4837 と worker が3つ起動しました。--workers 3 がそのまま3プロセスになっているのが分かります。
[INFO] Starting gunicorn 26.0.0
[INFO] Listening at: unix:/var/www/myapp/gunicorn.sock (4833)
[INFO] Using worker: sync
[INFO] Booting worker with pid: 4835
[INFO] Booting worker with pid: 4836
[INFO] Booting worker with pid: 4837
手順2:別ターミナルから動作確認する
SSH セッションをもう1つ開いて、ソケットに直接 curl を投げます。実際に Ubuntu 24.04 コンテナ内で実行したところ、次のレスポンスが返ってきました。
Hello from Flask on Ubuntu 24.04!
$ curl –unix-socket /var/www/myapp/gunicorn.sock http://localhost/health
{“service”:”flask-gunicorn-nginx”,”status”:”ok”}
正常系が取れたら、本番では systemd に管理させるので手動起動のままにしておく必要はありません。Ctrl+C で止めて次へ進みます。
STEP4: systemd サービスを作成する
systemd はサービスのプロセスを管理する仕組みです。Unit ファイルを作って登録すると、サーバー再起動時に自動で Gunicorn が起動し、クラッシュした場合も自動で再起動されます。
手順1:Unit ファイルを作成する
sudo nano /etc/systemd/system/gunicorn.service を開き、以下の内容を貼り付けます。
Description=Gunicorn daemon for Flask app
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/var/www/myapp/venv/bin/gunicorn \
–workers 3 \
–bind unix:/var/www/myapp/gunicorn.sock \
app:app
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
各設定の意味を補足します。ExecStart がまさに STEP3 で手動実行した Gunicorn コマンドそのものである点に注目してください。
| ディレクティブ | 値 | 意味 |
|---|---|---|
After |
network.target | ネットワーク起動後にサービス開始 |
User/Group |
www-data | 最小権限で実行(root を避ける) |
ExecStart |
venv/bin/gunicorn … | 仮想環境内の gunicorn を絶対パスで指定 |
Restart=always |
— | クラッシュ時に自動再起動 |
RestartSec=5 |
— | 5秒後に再起動を試みる |
手順2:サービスを有効化・起動する
$ sudo systemctl enable gunicorn
Created symlink /etc/systemd/system/multi-user.target.wants/gunicorn.service …
$ sudo systemctl start gunicorn
$ sudo systemctl status gunicorn
● gunicorn.service – Gunicorn daemon for Flask app
Active: active (running)
Main PID: 4833 (gunicorn)
Active: active (running) と表示されれば成功です。enable したので、サーバーを再起動しても自動で Gunicorn が立ち上がります。
検証メモ:systemd は Docker コンテナでは確認できない
今回の検証は ubuntu:24.04 Docker コンテナで行いましたが、コンテナは systemd が PID 1 で起動していないため systemctl は使えません(実際、コンテナ内では systemctl: command not found となりました)。そのため上記 systemctl の出力は VPS 実機での標準的な結果を示しています。一方で、その Unit が起動する Gunicorn コマンド本体(ExecStart の中身)は STEP3 でコンテナ内で実際に動かし、Flask が応答することを確認済みです。VPS では上記の手順をそのまま実行すれば動きます。
STEP5: Nginx をリバースプロキシとして設定する
手順1:Nginx の設定ファイルを作成する
sudo nano /etc/nginx/sites-available/myapp に以下を書きます。server_name は自分のドメインまたはサーバーの IP に変更してください。
listen 80;
server_name example.com; # ← 自分のドメインに変更
# 静的ファイルは Nginx が直接配信(高速)
location /static {
alias /var/www/myapp/static;
}
# 動的リクエストは Gunicorn に転送
location / {
include proxy_params;
proxy_pass http://unix:/var/www/myapp/gunicorn.sock;
}
}
手順2:設定を有効化して全チェーンに curl を通す
シンボリックリンクを張り、nginx -t で構文チェックしてから起動します。実際にコンテナで実行した結果が次です。

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl reload nginx
$ curl -i http://localhost/
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Content-Type: text/html; charset=utf-8
Hello from Flask on Ubuntu 24.04!
実測では nginx -t が syntax is ok / test is successful を返し、:80 へ curl すると HTTP/1.1 200 OK、Server: nginx/1.24.0 (Ubuntu) ヘッダ付きで Flask の応答が返りました。Nginx → Unixソケット → Gunicorn → Flask の全チェーンが通っていることを確認できます。/health も Content-Type: application/json で JSON を返しました。
手順3:ブラウザで実際に表示してみる
コマンドだけでなく、ブラウザからも確認しておきましょう。実際に動いている構成(Nginx 経由)にブラウザでアクセスして撮影したのが次の画面です。トップページに Flask が返した文字列がそのまま表示されています。

/health にアクセスすると、ブラウザの JSON ビューアで {"service":"flask-gunicorn-nginx","status":"ok"} が表示されます。監視ツールはこの JSON を叩いて死活監視に使えます。

ファイアウォールの確認
UFW(Ubuntu Firewall)が有効な場合は Nginx のポートを開放する必要があります。sudo ufw allow 'Nginx Full' を実行してください。これを忘れると、構成は正しいのに外部からアクセスできず詰まりがちです。
ワーカー数の最適化
Gunicorn のワーカー数は サーバーのパフォーマンスに直結する重要な設定です。Gunicorn 公式が推奨する目安は次の式です。
# 1vCPU の VPS(Vultr $5/月 プラン等)の場合
workers = 2 × 1 + 1 = 3
# 2vCPU の VPS の場合
workers = 2 × 2 + 1 = 5
ワーカー数は CPU の「性能」ではなく「数」で決めます。検証環境で sysbench の CPU ベンチを3回実行し、あわせて nproc で CPU 数も取得しました。

このときの nproc は 5、sysbench CPU は平均 4,046 events/sec(最小 2,632〜最大 6,296、3回計測)でした。ただしこの値は Docker on Apple Silicon の共有開発機(並列実行中)での参考値で変動が大きく、VPS 実機の性能とは一致しません。重要なのは絶対値ではなく、自分の VPS で nproc を確認して式に当てはめることです。
1
# 1vCPU なら workers = 2 × 1 + 1 = 3 が基本
メモリにも注意
worker を増やすと各 worker が Flask アプリ全体をメモリに読み込むため、RAM 使用量が比例して増えます。512MB RAM のサーバーでは workers=3 が上限の目安です。free -h でメモリ状況を確認してから設定してください。
よくあるエラーと解決策
502 Bad Gateway が出る
Nginx が Gunicorn に接続できない状態です。まず Gunicorn が起動しているか確認します。
Active: failed (Result: exit-code) …
$ sudo journalctl -u gunicorn –no-pager -n 30
# エラーログで原因を特定する
よくある原因は「app.py に Python の文法エラーがある」「ExecStart のパスが間違っている」「gunicorn.sock を作れないパーミッションエラー」の3つです。
「ModuleNotFoundError: No module named ‘flask’」が出る
systemd が起動する Gunicorn が venv の外のシステム Python を見ている可能性があります。ExecStart に venv/bin/gunicorn を絶対パスで書いているか確認してください。
ExecStart=/var/www/myapp/venv/bin/gunicorn app:app ← OK: venv の gunicorn(26.0.0)
Nginx の設定変更が反映されない
sudo systemctl reload nginx または sudo nginx -s reload でリロードします。restart(再起動)と reload(設定の再読み込み)は別物で、本番では接続を切らない reload が安全です。変更前に sudo nginx -t で構文チェックする癖をつけると事故が減ります。
「Permission denied」でソケットにアクセスできない
Nginx(www-data ユーザー)が gunicorn.sock を読めない状態です。ソケットを含むディレクトリのオーナーとパーミッションを確認します。今回の検証では、ソケットは srwxrwxrwx(先頭の s はソケットファイルの印)で作られていました。
srwxrwxrwx 1 www-data www-data 0 Jun 14 05:39 gunicorn.sock
$ sudo chown -R www-data:www-data /var/www/myapp
$ sudo systemctl restart gunicorn
まとめ
Ubuntu 24.04 LTS で Flask アプリを本番運用するための三点セットを、実際にコンテナで動かして検証しました。
- Ubuntu 24.04 の Python は 3.12.3 標準搭載。
python3-venvで仮想環境を作り、その中に Flask 3.1.3・Gunicorn 26.0.0 を入れる apt install gunicorn(20.1.0)よりpip install gunicorn(26.0.0)の方が新しく、Flask と同じ venv に収まる- Gunicorn は
--workers 3で master + worker ×3 が起動し、Unix ソケット経由で Flask が応答する - Nginx → Gunicorn → Flask の全チェーンに curl を通すと 200 OK が返る。ワーカー数は
2 × CPU数 + 1が目安 - systemd の
Restart=alwaysでクラッシュ時も自動復旧、enableでサーバー再起動時も自動起動
VPS を借りて本格的にサーバーを運用したい方は、LinuxLab の VPS 比較記事も参考にしてください。Vultr・DigitalOcean・ConoHa の東京リージョン対応状況と料金を実測データで比較しています。



コメント