はじめに (対象読者・この記事でわかること)
この記事は、VPSやレンタルサーバーで「1つのサーバーで複数のWebサイトを運用したい」と考えている方向けです。特に「/var/www以下をどう整理すれば保守・デプロイが楽なのか?」と悩んでいる方に最適です。
記事を読むことで、本番環境・ステージング環境を同一サーバー内に構築する際のディレクトリ構成のベストプラクティスと、Git・CI/CDを意識した設計ができるようになります。サンプルはUbuntu 22.04 + Nginxを前提に解説しますが、Apacheでも概念は同じです。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。 - Linuxの基本的なコマンド操作(ls, mkdir, chown, chmod) - NginxまたはApacheの仮想ホスト(VirtualHost / server block)の基本概念 - Gitの基本操作(clone, pull, branch)
なぜ/var/wwwの構成を考える必要があるのか
1つのサーバーで「本番サイト」「ステージングサイト」「開発中の新サービス」を運用するとき、適当にディレクトリを増やしていくと次のような弊害が出ます。
- ドキュメントルートのPathが環境ごとにバラバラになり、デプロイスクリプトが分かりづらい
- 環境変数やキャッシュディレクトリが混在し、セキュリティリスクが高まる
- チームメンバーが「どこに配置すればいいか」を都度確認する運用負荷が発生する
これを防ぐには、「ドメイン・サブドメイン・環境」の3軸を明示的にディレクトリ名に落とし込むことが重要です。
実践:Git/CI/CDも意識したディレクトリ構成を作る
ステップ1:基本方針を決める
今回は次の方針で進めます。
- ドメイン単位でGitリポジトリを分ける(例:example.com, api.example.com, blog.example.com)
- 環境(本番/ステージング/開発)はGitブランチで切り替え、サーバー上では
/var/www/<ドメイン>/<環境>/currentという構造にする - currentはシンボリックリンクにしてデプロイ時にアトミックに切り替える(Capistrano方式)
ディレクトリイメージ:
/var/www/
├── example.com/
│ ├── prod/
│ │ ├── current -> /var/www/example.com/prod/releases/20250602120000
│ │ ├── releases/
│ │ └── shared/
│ ├── stg/
│ │ └── ...(同様の構成)
│ └── dev/
│ └── ...
├── api.example.com/
│ └── ...
└── blog.example.com/
└── ...
ステップ2:実際にディレクトリを作成し、権限を設定する
まずはプロジェクトごとにディレクトリを作成します。本番とステージングはdeployユーザー、開発環境は開発者自身のユーザーで管理するとミスが減ります。
Bash# ドメイン単位で作成 sudo mkdir -p /var/www/example.com/{prod,stg,dev}/{shared,releases} # shared以下に環境変数やログを配置するため、ワritableにする sudo chown -R deploy:deploy /var/www/example.com sudo chmod g+w /var/www/example.com/*/shared
次にNginxのserver block(ApacheならVirtualHost)でドキュメントルートをcurrent/publicに向けます。
Nginxserver { server_name example.com; root /var/www/example.com/prod/current/public; access_log /var/www/example.com/prod/shared/logs/access.log; error_log /var/www/example.com/prod/shared/logs/error.log; include snippets/php-fpm.conf; # 必要に応じて }
ステップ3:GitHub Actionsでデプロイを自動化する
.github/workflows/deploy.yml(prodブランチへのpushをトリガーに)
Yamlname: Deploy to Production on: push: branches: [ prod ] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Deploy via SSH uses: appleboy/ssh-action@v1.0.0 with: host: ${{ secrets.HOST }} username: deploy key: ${{ secrets.SSH_PRIVATE_KEY }} script: | set -e REPO_DIR=/var/www/example.com/prod RELEASE_DIR=$REPO_DIR/releases/$(date +%Y%m%d%H%M%S) # 1. 新しいリリースディレクトリにclone git clone --depth 1 --branch prod git@github.com:yourorg/example.com.git $RELEASE_DIR # 2. sharedディレクトリ内の.envやuser_uploadsを参照するようシンボリックリンク ln -s $REPO_DIR/shared/.env $RELEASE_DIR/.env ln -s $REPO_DIR/shared/user_uploads $RELEASE_DIR/public/user_uploads # 3. composer/npm install などビルド cd $RELEASE_DIR composer install --no-dev --optimize-autoloader npm ci && npm run build # 4. currentリンクをアトミックに切り替え ln -sfn $RELEASE_DIR $REPO_DIR/current # 5. 古いリリースを削除(最新5世代残す) cd $REPO_DIR/releases && ls -t | tail -n +6 | xargs rm -rf
ハマった点やエラー解決
1. デプロイ後に「403 Forbidden」「No input file specified」が出る
原因:Nginxのrootディレクトリがcurrent/publicを正しく解決していない、またはPHP-FPMのパーミッションがdeploy:deployでなくwww-data:www-dataになっていない。
解決:/etc/nginx/sites-available/example.comでrootを絶対パスで記述し、PHP-FPMのプール設定でlisten.owner = www-data, listen.group = www-dataを明示する。
2. shared以下のログディレクトリに書き込めない
原因:ログローテート(logrotate)が動いた際にディレクトリ権限がリセットされる。
解決:logrotateの設定ファイルでcreate 640 deploy www-dataとし、rotate後もdeployグループが書き込めるようにしておく。
3. ステージング環境で本番DBに接続してしまう
原因:.envのシンボリックリンクを間違えてprod/shared/.envを参照していた。
解決:各環境のshared以下に.env.production, .env.stagingを配置し、デプロイスクリプト内でcp shared/.env.${ENV} release/.envするように変更。
まとめ
本記事では、1つのサーバーで複数サイトを運用する際の/var/www以下のディレクトリ構成を「ドメイン×環境×Gitブランチ」の3軸で整理する方法を解説しました。
- ドメイン単位でGitリポジトリを分け、環境ごとに
/var/www/<ドメイン>/<env>/currentとする - currentはシンボリックリンクにしてアトミックなデプロイを実現
- sharedディレクトリで環境変数やログ、アップロードファイルを共有化し、ミスを減らす
この構成にすることで、GitHub ActionsやGitLab CIなどのCDツールと組み合わせて「プッシュ→テスト→本番反映」までを安全に自動化できます。
次回は「Let's Encryptのwildcard証明書 + nginxのSNIで、サブドメン増やしてもSSL証明書をシンプルに管理する方法」を紹介します。
参考資料
- Debian/UbuntuのFile Hierarchy Standard
- Nginx Pitfalls and Common Mistakes – Root Directive
- Capistrano公式ドキュメント – Directory Structure
- GitHub Actions – appleboy/ssh-action
