記事一覧に戻る
Web技術系

UbuntuにLaravelをデプロイする方法|Apache・PHP-FPM・Let’s EncryptでHTTPS化

Ubuntu 24.04にLaravelを公開する手順を解説。Apache・PHP-FPM、PostgreSQL、.envと権限、サブドメインのDNS、Let’s Encryptの証明書取得・自動更新、よくあるエラーまでまとめる。

ノートと鉛筆のイラスト

ローカルで動いたLaravelを、自分のドメインで公開したい。VPSへコードを置けばよさそうだけど、Apacheの設定、データベース、ファイルの権限、HTTPS化と、思ったより確認する場所がある。

この記事では、Ubuntu上にLaravelを配置し、サブドメインで公開して、Let’s Encryptの証明書を自動更新できる状態にするまでを順番に整理する。

コマンドは公式資料を参照した構成例で、この記事の環境を一式構築して実測した記録ではない。

対象環境と、先に決めること

新規のVPSへ、既存のLaravelプロジェクトを初めて配置する例として説明する。

項目 この記事の構成
OS Ubuntu 24.04 LTS
Webサーバー Apache 2.4
PHPの実行方式 PHP-FPM
PHP Ubuntu 24.04標準リポジトリの8.3系を例示
Laravel 12系・13系を想定。実際の要件はプロジェクトのcomposer.jsonとcomposer.lockで確認
DB 同じVPS上のPostgreSQL
公開する名前 app.example.com
アプリの配置先 /var/www/laravel-app
Apacheの公開先 /var/www/laravel-app/public
証明書 Let’s Encrypt。CertbotのApacheプラグインで取得・設定

app.example.comは自分のサブドメインへ、リポジトリURLも実際のものへ読み替える。

作業には、SSHで接続できる一般ユーザーとsudo権限、ドメインのDNSを変更する権限が必要だ。Composer・npm・Gitは、原則としてその一般ユーザーで実行する。

すでに複数のサイトが動いているサーバーでは、既存のPHP設定やVirtualHostを先に確認する。 この記事の新規構築用コマンドを、そのまま既存構成の切り替えに使わない。

それぞれの役割

要素 何を担当するか
DNS app.example.comをサーバーのIPアドレスへ結び付ける
Apache リクエストを受け、静的ファイルを返したりPHP-FPMへ処理を渡したりする
PHP-FPM PHPを実行し、Laravelの処理結果をApacheへ返す
PostgreSQL アプリケーションのデータを保存する
Let’s Encrypt ドメインの管理を確認し、TLS証明書を発行する
Certbot 証明書の取得・Apacheへの設定・更新を自動化する

一般に「SSL化」と呼ばれるが、現在のHTTPS通信で使うのはTLSだ。証明書を取るだけでなく、Apacheがその証明書を使って443番ポートで応答するところまで設定する。

1. Apache・PHP-FPM・必要なツールを準備する

まずOSと、すでに入っているPHPを確認する。

cat /etc/os-release
php -v

未導入ならphp: command not foundになる。以下はUbuntu 24.04の標準リポジトリでPHP 8.3を使う、新規構築の例だ。

sudo apt update
sudo apt install apache2 git unzip curl acl dnsutils nano composer postgresql \
    php8.3-cli php8.3-fpm php8.3-pgsql php8.3-xml php8.3-mbstring \
    php8.3-curl php8.3-zip php8.3-bcmath php8.3-intl

php8.3-pgsqlはPostgreSQLへ接続するPHP拡張、aclは後で書き込み権限を調整するためのツールだ。アプリが画像処理やRedisなどを使う場合は、それに対応した拡張も追加する。

Laravel本体が対応していても、アプリや依存パッケージがPHP 8.4以上を要求する場合がある。PHPのバージョンはアプリの要件に合わせる。 すでに8.4を使っている環境なら、8.3を追加して混在させるのではなく、その環境のパッケージ、FPMサービス、Apacheの接続先を確認する。

ApacheからPHP-FPMへ処理を渡す設定と、LaravelのURL書き換え用モジュールを有効にする。

sudo a2enmod proxy_fcgi setenvif rewrite
sudo a2enconf php8.3-fpm
sudo systemctl enable --now apache2 php8.3-fpm postgresql
sudo apache2ctl configtest

Syntax OKを確認してからApacheへ反映する。

sudo systemctl reload apache2

この構成ではlibapache2-mod-phpを追加する必要はない。すでにmod_phpで動くサイトがある場合は、無条件にモジュールを無効化せず、影響するサイトを確認してからFPMへ移行する。

CLIとFPMは、別に確認する

php -v
php --ini
php -m
composer --version
systemctl status php8.3-fpm --no-pager

php -vで分かるのは、ターミナルで使うPHP CLIのバージョンだ。Webアクセス時のPHPはApacheが接続するFPMで決まる。CLIだけを更新しても、Web側まで切り替わったとは限らない。

PHP-FPMの標準的なプール設定は/etc/php/8.3/fpm/pool.d/www.confにある。後で使うwww-dataが、そのプールのuserとgroupに合っていることも確認しておく。

2. サブドメインのDNSと通信経路を設定する

DNS管理画面で、公開するサブドメインをVPSへ向ける。

種類 名前の例 値の例
A app VPSのIPv4アドレス
AAAA app IPv6でも正しく公開する場合だけ、そのIPv6アドレス

IPv6で受け付ける設定がないのにAAAAレコードだけが残っていると、アクセスや証明書認証が失敗する原因になる。

確認にはdigを使える。

dig +short A app.example.com
dig +short AAAA app.example.com

この記事は、DNSからVPSへ直接接続する構成を基本にする。Cloudflareを使う新規サイトなら「DNSのみ」の状態がこの例に相当する。既存サイトのプロキシ設定は、その影響を確認してから変更する。

開けるポート

VPS事業者側のファイアウォールとUbuntu側の両方で、次の通信を確認する。

ポート 用途
TCP 80 HTTP。この記事で使うHTTP-01認証にも必要
TCP 443 HTTPS

UFWを使っている場合は、まず状態を確認する。

sudo ufw status

有効になっている環境では、Apache用プロファイルでHTTPとHTTPSを許可できる。

sudo ufw allow 'Apache Full'

UFWを新たに有効化する作業は別に考える。SSHの許可を用意せずに有効化すると、サーバーへ再接続できなくなる場合がある。

3. Laravelのコードを配置する

配置先を作り、SSH接続中の一般ユーザーが所有するようにする。

sudo install -d -m 0755 -o "$USER" -g www-data /var/www/laravel-app
umask 0022
git clone https://github.com/your-account/your-app.git /var/www/laravel-app
cd /var/www/laravel-app

your-account/your-appは実際のリポジトリに置き換える。非公開リポジトリなら、読み取り用のデプロイキーなど、用意した認証方法で取得する。

umask 0022は、このシェルで新しく作る項目の権限を調整する。一般的な作成モードなら、通常ファイルは0644、ディレクトリは0755になる。0077などの設定のまま取得すると、PHP-FPMがコードを読み取れないことがある。既存ファイルの権限は変わらない。後で作る.envは、別途0640へ制限する。

以降のアプリ用コマンドは、このプロジェクトのルートで実行する。artisan、composer.json、publicがあることを確認しよう。

/var/www/htmlがApacheの既定の公開先として残っている場合も考え、ここではその外側へ配置している。実際に公開するのは、このプロジェクト内のpublicだけだ。

4. .envと書き込み権限を準備する

初回配置で、まだ.envがない場合だけコピーする。

cp .env.example .env
sudo chgrp www-data .env
chmod 640 .env
nano .env

まず本番用の設定にする。

APP_ENV=production
APP_DEBUG=false
APP_URL=https://app.example.com

APP_DEBUG=trueのまま公開すると、エラー画面に内部情報が出る可能性がある。.envはGitへコミットせず、APIキーやDBパスワードはサーバー側で設定する。

Laravelが書き込む場所

Laravelは主にstorageとbootstrap/cacheへファイルを書き込む。PHP-FPMと、SSHでArtisanを実行するユーザーの両方から扱えるようにする。

必要なディレクトリが欠けている場合も含め、作成しておく。

mkdir -p storage/framework/cache/data storage/framework/sessions \
    storage/framework/views storage/logs bootstrap/cache

この記事ではACLを使い、対象ディレクトリだけに権限を与える。

sudo setfacl -R -m u:www-data:rwX,u:"$(id -un)":rwX storage bootstrap/cache
sudo setfacl -R -d -m u:www-data:rwX,u:"$(id -un)":rwX storage bootstrap/cache
getfacl storage bootstrap/cache

1行目は既存の項目、2行目の-dはディレクトリ内で今後作られる項目の既定ACLを設定する。Xはディレクトリなどに必要な実行・通過権限を与える指定だ。ファイルシステムがACLに対応していることが前提になる。

www-dataはPHP-FPMの実際の実行ユーザーへ読み替える。一般ユーザー名を取得する$(id -un)も、普段デプロイを行う一般ユーザーのシェルで実行する。

プロジェクト全体を777にする必要はない。 また、ACLを設定しても、後からのchmodやファイルの作り直しで有効な権限が変わる場合がある。エラー時はgetfaclのeffective表示と、実際に生成されたファイルも確認する。

権限を詳しく調べたい場合は、LaravelのtempnamエラーとHTTP 500を、書き込み権限から直すにも整理している。

5. Composerの依存関係とAPP_KEYを設定する

本番では、リポジトリに記録されたcomposer.lockの内容をインストールする。

composer install --no-dev --prefer-dist --optimize-autoloader --no-interaction
composer check-platform-reqs --no-dev
オプション 意味
--no-dev 開発用の依存パッケージをインストール対象から外す
--prefer-dist 配布アーカイブを優先して取得する
--optimize-autoloader クラスの読み込み用情報を最適化する
--no-interaction 対話入力を求めず実行する

check-platform-reqsでは、実際のPHPと拡張が依存パッケージの要件を満たすか確認できる。ただし、これはCLI側の確認だ。FPM側にも同じ必要拡張があることを確認する。

本番サーバーでcomposer updateを実行すると、依存関係を解決し直してcomposer.lockも変更し得る。依存関係の更新は開発・検証環境で行い、デプロイではinstallを使う。違いはComposerの基本とinstall・updateの使い分けで詳しく説明している。

続いて、新規アプリでAPP_KEYがまだない場合だけ生成する。

php artisan key:generate --no-interaction

APP_KEYは暗号化などに使うアプリ固有のキーだ。既存アプリの移行では、元のAPP_KEYを引き継ぐ。再デプロイのたびに生成し直すと、既存の暗号化データやCookieを正しく扱えなくなる。

ComposerのスクリプトでDBなどへの接続が必要なアプリは、先に次のDB設定も済ませておく。失敗したコマンドを無視して後の工程へ進まない。

6. PostgreSQLを用意してマイグレーションする

新しいアプリ専用のロールとDBを作る。すでに用意してある場合は、作成を繰り返さず接続情報を確認する。

sudo -u postgres psql

ここからはpsqlの対話画面で入力する。

CREATE ROLE laravel_user WITH LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE;

パスワードはSQLへ直接書かず、対話入力で設定する。

\password laravel_user

その後、所有者を指定してDBを作る。

CREATE DATABASE laravel_db OWNER laravel_user;
\q

Laravelの.envに接続情報を設定する。

DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=laravel_db
DB_USERNAME=laravel_user
DB_PASSWORD="replace_with_your_actual_password"

DB_PASSWORDは先ほど設定した実際の値へ置き換える。DB_URLなど別の接続設定があるプロジェクトは、config/database.phpと合わせて確認する。

Laravelと同じTCP接続で、ログインできるか試す。

psql -h 127.0.0.1 -p 5432 -U laravel_user -d laravel_db -W

接続を確認したら\qで終了し、プロジェクトのルートで実行する。

php artisan config:clear --no-interaction
php artisan migrate --force --no-interaction
php artisan migrate:status --no-interaction

--forceは、本番環境での確認プロンプトを省略する指定だ。変更が安全になるオプションではない。 既存DBへ適用する場合は、マイグレーションの内容とバックアップ・復旧手順を確認してから実行する。

migrate:freshは、すべてのテーブルを削除して作り直す。初回でも、接続先を取り違えるとデータを失うため、通常の本番デプロイ手順には含めない。

接続エラーやDB権限については、Laravel用のPostgreSQLユーザーとデータベースを作成するも参照してほしい。

7. フロントエンドとLaravelのキャッシュを生成する

Viteなどを使うアプリでは、CSSとJavaScriptを本番用にビルドする。手順1のAPTコマンドにはNode.jsとnpmを含めていないため、VPSでビルドする場合は、Node.js公式の導入案内を確認してプロジェクトが要求するバージョンを先に用意しておく。

node -v
npm -v
npm ci --include=dev
npm run build

この例はpackage-lock.jsonがコミットされている前提だ。別のパッケージマネージャーを採用している場合は、そのロックファイルと手順に合わせる。Node.jsはpackage.jsonのengines、.nvmrc、Viteなどの要件から選ぶ。

ビルドツールはdevDependenciesに入っていることが多いため、ビルド時には開発用依存関係も必要になる。npm ci --include=devはその意図を明示した例だ。CIなどで生成したpublic/buildを配置する運用なら、VPS上でのNode.js実行は必須ではない。

npm run devは開発用だ。本番ではビルド結果を配信し、開発サーバーを参照するpublic/hotが残っていないことも確認する。

公開ディスクのアップロード画像を使うアプリでは、必要に応じてシンボリックリンクも作る。

php artisan storage:link --no-interaction

標準構成ではpublic/storageからstorage/app/publicを参照できるようになる。ここに置くのは公開してよいファイルだけにする。S3を使う場合など、不要な構成では省略できる。

設定がそろったら、本番用キャッシュを生成する。

php artisan config:cache --no-interaction
php artisan route:cache --no-interaction
php artisan view:cache --no-interaction

config:cache後は、アプリの通常処理で.envを直接読み込む前提にしない。環境変数は設定ファイルから参照し、アプリ側ではconfig()を使う。.envを変更したら、設定キャッシュも再生成する。

PHP実行ユーザーから読み書きできるか確認する

SSHユーザーでArtisanが成功しても、PHP-FPMからコードを読める、または生成先へ書けるとは限らない。www-dataで動かす例では、同じプロジェクトのルートで次の確認を行う。

sudo -u www-data sh -c '
    test -r .env &&
    test -r public/index.php &&
    test -r vendor/autoload.php &&
    test -w storage/framework/views &&
    test -w bootstrap/cache
' && echo 'PHP実行ユーザーの読み書き権限: OK'

OKが表示されない場合は、公開前に各パスと親ディレクトリの権限を確認する。これはOSの権限の確認であり、CLIとFPMのPHPバージョンや拡張が一致することまでは保証しない。Web経由の動作確認も併せて行う。

8. ApacheのVirtualHostを設定する

公開するサブドメイン専用の設定を作る。

sudo nano /etc/apache2/sites-available/laravel-app.conf

内容は次のようにする。

<VirtualHost *:80>
    ServerName app.example.com
    DocumentRoot /var/www/laravel-app/public

    <Directory /var/www/laravel-app/public>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/laravel-app-error.log
    CustomLog ${APACHE_LOG_DIR}/laravel-app-access.log combined
</VirtualHost>
設定 確認するポイント
ServerName DNSと証明書で使うapp.example.comにそろえる
DocumentRoot Laravelのpublicを指定する
<Directory> DocumentRootと同じパスにする
AllowOverride All プロジェクトのpublic/.htaccessを適用する
Options -Indexes ファイル一覧の表示を無効にする
FollowSymLinks 標準のURL書き換えやシンボリックリンクを利用できるようにする
ErrorLog このアプリのApacheエラーを追いやすくする

DocumentRootを/var/www/laravel-appにしない。 プロジェクトのルートには.envなどの非公開ファイルがある。公開先はpublicに限定する。

www.app.example.comなども使う場合だけ、DNSを用意してServerAliasへ追加する。使わない名前をテンプレート感覚で増やす必要はない。

設定を有効にする。

sudo a2ensite laravel-app.conf
sudo apache2ctl configtest

Syntax OKを確認したら反映する。

sudo systemctl reload apache2
sudo apache2ctl -S

apache2ctl -Sで、app.example.comが意図した設定ファイルへ割り当てられているか確認する。同じServerNameが複数の設定にあると、別サイトが表示されたり、Certbotが正しいVirtualHostを選べなかったりする原因になる。

続いて、外部からHTTPでアクセスする。

curl -I http://app.example.com

想定したLaravelのページやリダイレクトが返ることを確認する。ブラウザでも、CSS、画像、ページ遷移を確認しよう。IPアドレスだけへのアクセスでは、名前によるVirtualHostの確認にはならない。

この段階では通信が暗号化されていないため、ログインや個人情報の入力はHTTPS化後に行う。

9. Let’s Encryptの証明書を取得し、HTTPS化する

Let’s Encryptは、申請されたドメインを管理していることを確認してから証明書を発行する。

ここで使うHTTP-01認証では、Let’s Encryptがhttp://app.example.com/.well-known/acme-challenge/...へアクセスする。DNS、80番ポート、Apacheの設定が先に必要なのはこのためだ。

Certbotをインストールする

この記事では、Certbot公式で案内されているsnap版を使う。まず既存の導入状態を確認する。

command -v certbot
snap list certbot

未導入なら、コマンドが見つからない・パッケージがない旨が表示される。すでにapt版などで証明書を管理している場合は、その更新設定も含めて確認し、導入方法を混在させない。 以下はCertbotを初めて導入する場合の手順だ。

sudo apt install snapd
sudo snap install --classic certbot
sudo /snap/bin/certbot --version

ここでは実行する版を明確にするため、以降も/snap/bin/certbotを直接指定する。

Apacheへ証明書を設定する

sudo /snap/bin/certbot --apache -d app.example.com --redirect

初回は、連絡先メールアドレスや利用規約への同意などの案内に従って進める。--apacheは証明書の取得とApacheへの設定、--redirectはHTTPからHTTPSへのリダイレクト設定を行う指定だ。

複数の名前を1枚の証明書に入れる場合は-dを追加できる。ただし、指定するすべての名前のDNSと認証経路を用意する。 この記事のサブドメイン1つだけなら、上のコマンドでよい。

Certbotの処理後も設定を確認する。

sudo apache2ctl configtest
sudo apache2ctl -S
sudo /snap/bin/certbot certificates
curl -I http://app.example.com
curl -I https://app.example.com

HTTPが意図したHTTPSへ転送され、HTTPS側でページが表示できることを確認する。TLSエラーが出る場合は原因を直し、curl -kで証明書検証を省略した結果を成功扱いにしない。

HTTPS化後、ログインを扱うアプリでは、セッションCookieをHTTPSでだけ送る設定も確認する。

SESSION_SECURE_COOKIE=true

この設定はconfig/session.phpが対応する環境変数を参照していることが前提だ。変更後は設定キャッシュを再生成し、ブラウザでログインやフォーム送信も確認する。

php artisan config:cache --no-interaction

証明書ファイルの場所

証明書名がapp.example.comなら、主に次のパスが使われる。

/etc/letsencrypt/live/app.example.com/fullchain.pem
/etc/letsencrypt/live/app.example.com/privkey.pem

前者は証明書チェーン、後者は秘密鍵だ。重複取得などで証明書名に-0001等が付く場合もあるので、実際のパスはcertbot certificatesの表示で確認する。

秘密鍵をGitやWeb公開ディレクトリへコピーしたり、読めないからと権限を広げたりしない。Apacheの証明書設定は、Certbotが作った内容と実際のファイルパスを確認する。

10. 証明書の自動更新を確認する

証明書の取得ができても、更新が止まれば期限切れになる。snap版の更新ジョブを確認し、更新テストを行う。

systemctl list-timers --all | grep certbot
sudo /snap/bin/certbot renew --dry-run

--dry-runは、テスト用環境を使って更新処理を試すコマンドだ。成功したことに加えて、定期実行のタイマーが有効で次回実行予定を持っていることも確認する。タイマーが見つからない場合は、snapサービスの状態と導入方法を調べる。

snap services certbot

既存の更新ジョブがある場合、同じ目的のcronを重ねて追加する必要はない。更新失敗を見逃さないよう、タイマー・ログと、外部から見える証明書の期限も定期的に確認する。

HTTP-01を使い続けるなら、更新時にも80番ポートと認証用パスへ到達できる必要がある。HTTPS化が終わったからといって、80番ポートを閉じない。

ワイルドカード証明書や、80番ポートを公開しない構成にはDNS-01認証という別の方法がある。DNSプロバイダーのAPIでTXTレコード更新まで自動化する構成などが必要で、--apacheの例をそのまま置き換えるだけではない。

Cloudflareのプロキシを使う場合の補足

Cloudflareでプロキシを有効にすると、ブラウザからCloudflare、CloudflareからVPSという2つの通信経路になる。ブラウザで見える証明書だけでは、VPS側の証明書の状態までは分からない。

VPS側のHTTPSが正しく動いたうえで、Cloudflareの暗号化モードは有効な証明書を検証する**Full (strict)**を使う構成にする。FlexibleはCloudflareからVPSへHTTPで接続するため、VPS側のHTTPS転送と組み合わさるとリダイレクトループの原因になる。

HTTP-01認証を使う場合は、Cloudflare経由でも認証用パスがVPSへ届くことを確認する。WAF、アクセス認証、転送ルールなどで遮断されていないかを調べ、プロキシ有効後にも更新テストを行う。プロキシを無効にしないと取得できない、と一律に決まっているわけではない。

よくあるエラーと、確認する順番

症状 まず確認すること
名前解決できない・別のIPになる DNSのA/AAAA、反映状況、Cloudflareのプロキシ有無
接続がタイムアウトする VPS事業者側の制限、UFW、80/443の待ち受け
別サイトやApacheの初期ページが出る ServerName、設定の有効化、apache2ctl -S
トップは開くが別URLが404 rewrite、AllowOverride、public/.htaccess、Laravel側のルート
HTTP 403 Require all granted、公開先と親ディレクトリの通過権限
HTTP 500・Permission denied・tempnam storage・bootstrap/cache、FPM実行ユーザー、ACL
PHPのソースが表示・ダウンロードされる FPMの接続設定。公開を止めてPHPハンドラーを修正する
DBへ接続できない .env、設定キャッシュ、pdo_pgsql、PostgreSQL認証
Vite manifest not found ビルドの成功、public/buildの配置
Certbotの認証が失敗 指定した全ドメインのDNS、IPv6、80番ポート、認証用パス
証明書取得後、Apacheへの設定で失敗 ServerNameに対応するVirtualHost、重複、configtest
HTTPSだけ接続できない 443番ポート、SSL用VirtualHost、証明書設定
リダイレクトが終わらない Apacheの転送、Cloudflareの暗号化モード、プロキシ設定

調査用のコマンドをまとめておく。

sudo apache2ctl -S
sudo apache2ctl configtest
sudo tail -n 100 /var/log/apache2/laravel-app-error.log
sudo journalctl -u php8.3-fpm -n 100 --no-pager
tail -n 100 storage/logs/laravel.log
getfacl storage bootstrap/cache

Laravelのログ先はconfig/logging.phpによって異なる。日別ファイルや標準エラー出力を使う設定なら、その保存先を確認する。ログには認証情報や利用者データが含まれることもあるため、そのまま公開しない。

証明書が取得できていて、Apacheへの設定だけに失敗した場合は、まずcertbot certificatesで証明書名を確認する。VirtualHostを修正した後は、公式のinstallサブコマンドで既存証明書を設定する方法もある。

sudo /snap/bin/certbot install --cert-name app.example.com

--cert-nameには実際に表示された証明書名を使う。原因を直さず、証明書の新規発行を繰り返さない。

2回目以降のデプロイで変わること

初回構築と更新時の作業を分けると、必要以上の変更をせずに済む。

初回に準備すること 更新時にも行うこと
DNS・Apache・PHP-FPMの設定 レビュー・検証済みのコードを配置する
アプリ専用DBとロールの作成 composer installでロックファイルの依存関係を再現する
.envと初回のAPP_KEY 必要なフロントエンドビルドとマイグレーション
証明書の取得と自動更新設定 設定・ルート・ビューのキャッシュ再生成
実行ユーザーと権限の設計 権限、画面、ログ、証明書の状態を確認する

既存アプリでは、コードとDBの変更内容に応じてメンテナンスモードやリリースディレクトリの切り替えを使う。git pullからマイグレーションまでを並べるだけで、無停止更新になるわけではない。

更新時にAPP_KEYを生成し直したり、.env.exampleで.envを上書きしたり、migrate:freshを実行したりしない。コードだけ戻してもDBの変更が戻るとは限らないので、DBを含む復旧方法を用意しておく。

キューやスケジューラーを使うアプリでは、Webの公開に加えて常駐ワーカーやcron等の設定も必要になる。キューワーカーはコード更新後の再起動・再読込まで含めて運用する。

参考資料