Xserverに個人開発のLaravel+Vueアプリを初めて本番公開するまでに踏んだ罠
個人で開発した目標管理アプリ「Goal Achiever」(Laravel 12+Vue)を、共有サーバー(Xserver)に初めて本番公開した際の作業記録です。2025年12月10日の一晩で、GitHubのリポジトリ整理からSSH接続、PHPバージョンの不一致、マイグレーションエラー、APIのリライト設定、フロントのビルド配置まで、次々と問題にぶつかりました。架空の体験ではなく、実際の開発記録(devlog)に基づいています。
旧GitHubリポジトリを最新コードで上書きする
ローカルの開発フォルダが2つともgit管理外になっており、git push を試みると以下のエラーが出ました。
fatal: not a git repository (or any of the parent directories): .git
error: src refspec main does not match any
新規フォルダ goal-achiever-deploy を作成し、git init → remote add → backend/frontendをコピー → .gitignore 作成という手順を踏みました。git push origin main --force を実行した際に上記のrefspecエラーが出た原因は、ローカルのデフォルトブランチが master、リモートのHEADが main だったことでした。git branch -M main でリネームしてから再度 git push origin main --force を実行し、旧履歴を上書きしました。所要時間は約15分でした。
共有サーバーへのSSH接続とGitHub Deploy keyの設定
サーバーへのSSH接続では、最初に以下のエラーが出ました。
ssh: connect to host ***.xsrv.jp port 22: Connection timed out
***@***.xserver.jp: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
サーバーパネルで接続情報を確認し、ポートが22ではなく10022であること、公開鍵認証が必要であること、ユーザー名がサーバーIDであることを確認しました。鍵を指定せずに接続してPermission deniedになった後、~/.ssh/ 内にサーバー用の秘密鍵が既に存在することを見つけ、-i オプションで指定して接続に成功しました。
ssh -p 10022 -i ~/.ssh/***.key ***@***.xserver.jp
サーバー上で ssh-keygen -t ed25519 を実行し、生成した公開鍵をGitHubのDeploy key(読み取り専用)に登録してから git clone を行いました。所要時間は約20分でした。
PHPのバージョン不一致でComposerが43件のエラー
サーバー上でLaravel 12の依存関係をインストールする際、以下のエラーが発生しました。
Root composer.json requires php ^8.2 but your php version (8.0.30) does not satisfy that requirement.
同種のエラーがlaravel/frameworkやsymfony/*など計43件表示されました。ls /usr/bin/php* で確認すると、サーバー上にはPHP 5.1〜8.4が並存しており、SSH接続時のデフォルトの php コマンドは8.0を指していました。一方、Web側(ドメイン設定)のPHPは8.2.28で、CLIとWebでバージョンが異なっていたことが分かりました。
/usr/bin/php8.2 composer.phar install --no-dev --optimize-autoloader で明示的にバージョンを指定したところ成功し、以降のartisanコマンドもすべて /usr/bin/php8.2 artisan ... の形で実行しました。所要時間は約10分でした。
マイグレーションエラーとAPIのリライトが通らない問題
本番DB(MariaDB 10.5)へのマイグレーションでは、以下のエラーで停止しました。
2024_01_01_000010_create_notifications_table
SQLSTATE[42000]: Syntax error or access violation: 1067 Invalid default value for 'created_at'
マイグレーションファイルを確認すると、$table->timestamp('created_at'); がデフォルト値なしのNOT NULL timestampになっていました。サーバー上でsedにより $table->timestamp('created_at')->useCurrent(); に書き換え、artisan migrate --force を再実行して残りのマイグレーションをすべて成功させました(この修正はこの時点ではサーバー上のファイルのみで、ローカル・GitHubには未反映)。所要時間は約5分でした。
もう一つの問題は、サブディレクトリ /api/ 配下でLaravel APIを公開する設定でした。/api/health にアクセスすると404になり、ブラウザコンソールにも404が表示されました。routes/api.phpにhealthルートを追加しても変わらず、.htaccess を RewriteBase指定、index.php/api/$1 へのリライト、FallbackResource、ErrorDocument 404と複数の方式に変更しましたが、いずれも404のままでした。.htaccess 自体は読み込まれていること(不正な行を入れると500になることで確認)、CLIからindex.phpを直接実行するとJSONが返ること(Laravel自体は動作していること)は確認できましたが、/api/index.php/api/health に直接アクセスした場合のみJSONが返り、リライト経由では index.php に到達していないことが分かりました。
根本原因は特定できなかったため、リライトを使う方式は諦め、フロントの VITE_API_URL を `https://***/api/index.php/api` に直接設定する回避策で進めました。所要時間は約40分でした。
フロントエンドの配置とログインの確認
PC再起動後に鍵未指定でSSH接続しようとして再度Permission deniedになった後、-i で鍵を指定して再接続しました。ローカルで .env.production を新規作成し npm run build を実行、生成された dist を作業リポジトリにコピーしてpushし、サーバー側で git pull と cp -r frontend/dist/* <公開ディレクトリ>/ を実行しました。
公開ディレクトリ直下には、HTTPSを強制し /api はそのまま通過させ、それ以外は index.html に振り向ける .htaccess を作成しました。さらに、管理者ユーザーが存在していなかったため artisan tinker 上で User::create(...) を実行して作成し、本番画面でのログインに成功しました。所要時間は約20分でした。
SSH接続の簡略化とデプロイフローの整理
最後に、毎回 -p 10022 -i <鍵> を指定する必要があった接続を簡略化するため、~/.ssh/config にHostエイリアス(HostName・User・Port・IdentityFile)を追記し、ssh xserver だけで接続できるようにしました。また、build→git add/commit/push→ssh経由でのgit pullとdistコピーを行う deploy.ps1 を作成しました(フロントのみ反映。バックエンド変更時はサーバーで手動コピーとキャッシュクリアが必要)。
開発フォルダは、開発・デプロイとも goal-achiever-deploy の1フォルダに統合する方針にし、旧2フォルダは当面残すことにしました。開発・ステージング・本番の3環境は今回は用意せず、ステージングが必要になった時点でサブドメインを追加する方針としました。所要時間は約15分でした。
まとめ:一晩で踏んだ罠の整理
| 事象 | 原因 | 対応 | 所要時間 |
|---|---|---|---|
| git push失敗 | ローカルmaster/リモートmainの不一致 | ブランチをmainにリネームしてforce push | 約15分 |
| SSH接続失敗 | ポート・鍵の指定不足 | ポート10022・既存秘密鍵を指定して接続 | 約20分 |
| Composerエラー43件 | CLIのデフォルトPHPが8.0 | php8.2を明示指定 | 約10分 |
| マイグレーション停止 | timestampカラムにデフォルト値なし | useCurrent()に修正 | 約5分 |
| /api/が404 | リライトがindex.phpに到達せず(原因未特定) | フロントのAPI URLを直接index.php経由に変更(回避策) | 約40分 |
| フロント配置・ログイン | 管理者ユーザー未作成 | tinkerでUser作成、.htaccess設定 | 約20分 |
| 接続・デプロイの手間 | 毎回手動でポート・鍵指定 | SSH config整理、deploy.ps1作成 | 約15分 |
合計の作業時間は概算で約2時間強でした。/api/のリライトについては根本原因を特定できておらず、回避策のまま運用しています。開発・ステージング・本番の3環境化やdeploy.ps1のバックエンド対応は、今後必要になった時点で検討する想定です。
この記事は運営者の実際の開発記録をもとに、AIの支援を受けて作成しています。