Laravelを構築する
LARAVEL 03 / 03

LaravelのWebサーバー設定

Nginx・Apache・Caddyのいずれかを選び、Laravelのpublicだけを配信します。

01 / 公開前の状態を確認する

PHP・Composerの準備とLaravelアプリの作成を終えた、Ubuntu Server 22.04の手順です。物理マシン・仮想マシン共通です。Laravel 13にはPHP 8.3以降が必要で、既存記事のPHP 8.1用ソケットを指定してはいけません。

公開するのは /var/www/laravel-guide/public だけです。

プロジェクト全体、.env、vendor、storage、bootstrap/cache、SQLiteの保存先を公開ルートにしません。public/index.phpをプロジェクト直下へ移動せず、APP_KEYや設定キャッシュを画面・ログ・記事へ出力しません。

以下の3種類は選択肢です。使用する1つの節だけを実行します。同じアドレスの80・443番で同時に起動できる構成ではありません。既存サイトがある場合は無条件に停止・設定上書きせず、VirtualHostの追加、保守時間、バックアップと戻し方を先に設計してください。以下は管理者が設定を確認できる専用サーバー向けです。

sudo ss -lntup
systemctl is-active php8.3-fpm
sudo ls -l /run/php/php8.3-fpm.sock
cd /var/www/laravel-guide
php8.3 artisan about --only=environment

PHP 8.3-FPMがactiveで、アプリがproduction・Debug Mode OFFであることを確認します。コードは配置用ユーザー所有・www-dataグループ、ディレクトリー2750・ファイル640が基準です。Webプロセスが書き込める範囲はstorage・bootstrap/cacheとSQLite専用保存先に限ります。アプリ全体をwww-data所有や777へ変えません。

laravel-guide.testは確認用の名前で、この記述だけではDNSが作られません。SERVER_IPは確認端末から到達できるサーバーのアドレス、CLIENT_IPはサーバーから見える確認端末の送信元IPへ置き換えます。実ドメインを使う場合は所有する名前とDNS・HTTPSへ合わせます。3種類とも次節のHostヘッダーでLaravelのサイトを選びます。IPだけのアクセスが既存の既定サイトへ届いても、Laravelの動作確認にはなりません。

php artisan serveは開発用です。8000番を外部へ開く手順ではなく、WebサーバーとFPM経由で確認します。ログイン等の認証情報を外部のHTTPで送らず、公開前に選択したWebサーバー向けのHTTPSを整えます。実ドメインの証明書発行は未検証です。

02 / Nginxを選ぶ場合

Nginxの導入を済ませた場合の設定です。新しいサイト設定を作り、ほかのPHP 8.1サイトのソケットは変更しません。

Nginxの設定と反映操作を開く
BACKUP="$(sudo mktemp -d /root/laravel-nginx.XXXXXX)" &&
sudo cp -a /etc/nginx "$BACKUP/"
printf '保存先: %s\n' "$BACKUP"
sudo test ! -e /etc/nginx/sites-available/laravel-guide
sudo test ! -e /etc/nginx/sites-enabled/laravel-guide

同名ファイルがあれば新規作成を中止して内容を確認します。以下の専用ファイルを作成します。

sudo test ! -e /etc/nginx/sites-available/laravel-guide &&
sudo tee /etc/nginx/sites-available/laravel-guide >/dev/null <<'EOF'
server {
    listen 80;
    server_name laravel-guide.test;
    root /var/www/laravel-guide/public;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    location ~ /\. {
        deny all;
    }
    location = /index.php {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
    location ~* \.php(?:/|$) {
        return 404;
    }

    access_log /var/log/nginx/laravel-guide-access.log;
    error_log /var/log/nginx/laravel-guide-error.log;
}
EOF
sudo ln -s /etc/nginx/sites-available/laravel-guide /etc/nginx/sites-enabled/laravel-guide &&
sudo nginx -t && sudo systemctl reload nginx

構文検査が成功してから再読み込みします。処理を渡すPHPはpublic/index.phpだけに限定し、ほかのPHP URLを拒否します。既存のdefault_serverを持つサイトがあれば、IPアドレスだけのアクセスはそのサイトへ届くことがあります。次の確認ではHostでLaravelのサイトを指定します。実ドメインのHTTPSを導入済みなら、その443番の対象serverブロックにも同じ公開ルートとPHP 8.3の連携を反映し、既存の証明書とHTTPS転送を維持します。

curl --fail --show-error -H 'Host: laravel-guide.test' http://SERVER_IP/
curl --fail --show-error -H 'Host: laravel-guide.test' http://SERVER_IP/up
Nginx経由のLaravel応答を確認した実画面
検証VMの実画面。図のguide-checkは検証専用の一時ルートで、試験後に削除しています。本文の標準確認は/upです。公開TLS発行を示す画面ではありません。

03 / Apacheを選ぶ場合

Apacheの導入を済ませた場合の設定です。PHP 8.3はこのVirtualHost内のindex.phpへ明示し、a2enconf php8.3-fpmによって既存サイトのPHPをまとめて切り替えません。

Apacheの設定と反映操作を開く
BACKUP="$(sudo mktemp -d /root/laravel-apache.XXXXXX)" &&
sudo cp -a /etc/apache2 "$BACKUP/"
printf '保存先: %s\n' "$BACKUP"
sudo test ! -e /etc/apache2/sites-available/laravel-guide.conf

同名サイトがないことを確認してから作成します。FallbackResourceが、実在しない通常のURLをLaravelのフロントコントローラーへ渡します。.htaccessへ依存しない構成です。

sudo test ! -e /etc/apache2/sites-available/laravel-guide.conf &&
sudo tee /etc/apache2/sites-available/laravel-guide.conf >/dev/null <<'EOF'
<VirtualHost *:80>
    ServerName laravel-guide.test
    DocumentRoot /var/www/laravel-guide/public
    DirectoryIndex index.php

    <Directory /var/www/laravel-guide/public>
        Options -Indexes -FollowSymLinks
        AllowOverride None
        Require all granted
        FallbackResource /index.php
    </Directory>
    <FilesMatch "(?i)\.php$">
        Require all denied
    </FilesMatch>
    <Files "index.php">
        Require all granted
        SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost/"
    </Files>
    <LocationMatch "(^|/)\.">
        Require all denied
    </LocationMatch>

    ErrorLog ${APACHE_LOG_DIR}/laravel-guide-error.log
    CustomLog ${APACHE_LOG_DIR}/laravel-guide-access.log combined
</VirtualHost>
EOF
sudo a2enmod proxy_fcgi setenvif &&
sudo a2ensite laravel-guide &&
sudo apache2ctl configtest && sudo systemctl reload apache2

index.php以外のPHPは実行させず、隠しパスも拒否します。グローバルServerNameのAH00558警告と構文エラーは別なので、Syntax OK、実際のVirtualHostと応答を照合します。既存サイトは一括無効化しません。IPだけで別サイトが表示される場合はHostを指定してください。

sudo apache2ctl -S
curl --fail --show-error -H 'Host: laravel-guide.test' http://SERVER_IP/
curl --fail --show-error -H 'Host: laravel-guide.test' http://SERVER_IP/up
Apache経由のLaravel応答を確認した実画面
検証VMの実画面。図のguide-checkは検証専用の一時ルートで、試験後に削除しています。本文の標準確認は/upです。公開TLS発行を示す画面ではありません。

04 / Caddyを選ぶ場合

Caddyの導入を済ませた場合の設定です。CaddyのサービスユーザーがPHP 8.3-FPMのソケットへ接続できることを確認します。補助グループを変更した場合はreloadではなくサービスの再起動が必要です。

Caddyの設定と反映操作を開く
id caddy
sudo ls -l /run/php/php8.3-fpm.sock
sudo usermod -a -G www-data caddy
BACKUP="$(sudo mktemp -d /root/laravel-caddy.XXXXXX)" &&
sudo cp -a /etc/caddy "$BACKUP/"
printf '保存先: %s\n' "$BACKUP"
sudo install -d -o root -g root -m 755 /etc/caddy/sites
sudo test ! -e /etc/caddy/sites/laravel.Caddyfile

既存の/etc/caddy/Caddyfileを置き換えず、Laravel専用の名前付きHTTPサイトを新しいファイルへ分けます。同名ファイルがある場合は作成を中止して内容を確認してください。

sudo test ! -e /etc/caddy/sites/laravel.Caddyfile &&
sudo tee /etc/caddy/sites/laravel.Caddyfile >/dev/null <<'EOF'
http://laravel-guide.test {
    root * /var/www/laravel-guide/public
    route {
        @hidden path_regexp hidden (^|/)\.
        respond @hidden 403
        @otherPhp {
            path_regexp php (?i)\.php($|/)
            not path /index.php
        }
        respond @otherPhp 404
        php_fastcgi unix//run/php/php8.3-fpm.sock
        file_server
    }
}
EOF
sudo chown root:root /etc/caddy/sites/laravel.Caddyfile
sudo chmod 644 /etc/caddy/sites/laravel.Caddyfile

拒否ルールをroute内で先に適用し、index.php以外のPHPは大文字の拡張子も含めて拒否します。その後にLaravelのフロントコントローラーへの書き換えとFastCGI処理を行います。

次にsudoedit /etc/caddy/Caddyfileで主設定を開き、既存のサイトブロックを残したまま、次のimportをトップレベルへ1回だけ追加します。同じファイルをすでに読み込むimportや、より広いワイルドカードがある場合は、その既存の読込を利用し、重複追加しません。

import /etc/caddy/sites/*.Caddyfile

これは既存のhttp://:80等と、同じCaddyサービス内で名前付きサイトを分ける構成です。別のWebサーバーと80番を共有する意味ではありません。書式を整えるのは新規ファイルだけとし、検証はimportを含む主設定全体で行います。

sudo caddy fmt --overwrite /etc/caddy/sites/laravel.Caddyfile
sudo -u caddy -H caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile &&
sudo systemctl restart caddy
systemctl is-active caddy
curl --fail --show-error -H 'Host: laravel-guide.test' http://SERVER_IP/
curl --fail --show-error -H 'Host: laravel-guide.test' http://SERVER_IP/up

ブラウザーで確認するときは、管理端末のhostsまたは管理用DNSでlaravel-guide.testをサーバーへ向けます。IP直アクセスは既存の既定サイトへ届く場合があるため、その表示をLaravelの成功と扱いません。公開用ドメインが決まった場合はこの新規サイトの名前を所有する実ドメインへ変更し、自動HTTPSの前提と発行結果を別途確認してください。

Caddy経由のLaravel応答を確認した実画面
検証VMの実画面。図のguide-checkは検証専用の一時ルートで、試験後に削除しています。本文の標準確認は/upです。公開TLS発行を示す画面ではありません。

05 / 応答・保護・戻し方を確認する

UFWが有効ならSSH許可を保持し、確認用端末のHTTPを許可します。CLIENT_IPは実際の送信元IPへ置き換え、クラウド側FWやNATの経路も確認します。WebサーバーとLaravelの構成に成功してから必要な範囲へ広げます。

sudo ufw allow from CLIENT_IP to any port 80 proto tcp
sudo ufw status verbose

別端末から、歓迎画面と/upがHTTP 200で返ることを確認します。ブラウザーで確認用の名前を使うには、その端末のhostsや管理用DNSを設定します。/upは標準ではアプリの起動確認であり、DB・メール・キューをすべて検査するものではありません。DBの確認は前ページのマイグレーションと状態確認も使います。

curl -sS -o /dev/null -w '%{http_code}\n' -H 'Host: laravel-guide.test' http://SERVER_IP/.env
curl -sS -o /dev/null -w '%{http_code}\n' -H 'Host: laravel-guide.test' http://SERVER_IP/composer.json
curl -sS -o /dev/null -w '%{http_code}\n' -H 'Host: laravel-guide.test' http://SERVER_IP/vendor/autoload.php
curl -sS -o /dev/null -w '%{http_code}\n' -H 'Host: laravel-guide.test' http://SERVER_IP/artisan

.envは403、ほかは403または404で、設定内容・PHPソース・依存コードを返さないことを確認します。失敗時にアプリ全体の権限を緩めず、公開ルート・PHPソケット・サイトの選択・FPMユーザーの読み書き範囲を調べます。500ではstorage/logsのLaravelログ、502/503では選択したWebサーバーとphp8.3-fpmのログを確認します。APP_DEBUG=trueで外部にエラー詳細を見せて解決しません。

新規サイトだけを戻す場合は、Nginx/Apacheの追加サイトだけを無効にするか、CaddyのLaravel専用ファイルをimport対象の外へ退避します。Caddyのimportを今回初めて追加した場合は、ほかのサイトがその行へ依存していないか確認してから戻します。既存サイトの設定は一括復元・削除せず、選んだ製品の構成検査に成功してからreloadします。旧PHP 8.1の設定を一括削除しません。HTTPSアプリをHTTPだけの構成へ戻したまま認証情報を送らず、必要なら先に外部公開を止めます。

参考:実測結果と対象外の範囲

Ubuntu Server 22.04.5の実VMでLaravel 13.39.0・PHP 8.3.36を使用し、Nginx・Apache・Caddyを順番に切り替えて検証しました。各製品で歓迎画面と/upは200、PHP 8.3実行・production/debug=false・SQLiteトランザクション内の読取/書込が成功しました。.envは403、公開対象外ファイルとindex.php以外のPHPは403/404で内容を返しません。FPM停止時はNginx/Caddyが502、Apacheが503で、復旧後は再応答しました。意図的な500でも鍵やソースは表示されず、検証用ルートとPHPプローブを削除してキャッシュ更新後の404を確認しています。接続は隔離した管理経路とQEMU NAT転送で、公開DNS・本番TLS・認証・メール等の運用検証とは異なります。

実ドメインの公開証明書、メール、キューワーカー、スケジューラー、WebSocket、フロントエンドビルドとアプリ固有の認証は、この基本導入と別に設計・検証する項目です。既存のHTTPSページの--nginxはNginx用で、Apache/Caddyへ流用しません。

参考:Laravelのデプロイ、Apache FallbackResource、Caddy php_fastcgi。