DELOGs
[自サーバでNext.jsアプリを動かす#1]Node.jsインストール+Nginx設定+デプロイ

自サーバでNext.jsアプリを動かす#1
Node.jsインストール+Nginx設定+デプロイ

Next.jsで作成したWebサイト・アプリを自サーバのNginxで動かすための設定とデプロイ作業の整理

初回公開日

最終更新日

Next.jsで作成したWebサイトやWebアプリを動作させるサーバといえば、「Vercelサーバ」が最初の選択肢になると思います。Next.js関連の書籍でもそうした内容が多いです。しかし、やはり自社で運用しているサーバにアップして動かしたいという需要もあると思います。 私自身がそうしたかったのですが、初心者のため、なかなか苦労しました。
ここではその実践記録を共有します。 すでにSSLやバーチャルホストの設定は完了済みで、通常のHTMLファイルはNginxでブラウザ表示可能な状態まで完了している前提となります。
サーバ構築については...
今回作業する環境は、下記のとおりです。
  • サーバOS:Ubuntu 24.04 LTS
  • Webサーバ:Nginx 1.28.0
  • ルートディレクトリ:/var/www/example.com/html (example.comが対象ドメインとします)
  • バーチャルホスト設定:/etc/nginx/conf.d/example.com.conf()
  • SSL:Let's Encrypt(Certbot 4.0.0)で設定済み
  • http通信:http/3を設定済み(未対応ブラウザようにhttp2へフォールバック)
  • /etc/nginx/nginx.conf にはgzip関連の設定も済み
上記の設定関連については[サーバ構築・運用]-[新規構築]の記事を参照してください。
今回の作業目標は、ビルドされたNext.jsのデフォルトページを表示することです。

1.【サーバ側作業】Node.jsのインストール

Next.jsを動かすにはNode.jsが必要です。なので、まずはNode.jsをサーバへインストールします。

Ubuntu 24.04の標準リポジトリでインストール可能なNode.jsのバージョンを調べる

bash
1sudo apt update 2apt-cache policy nodejs
bash
1nodejs: 2 インストールされているバージョン: (なし) 3 候補: 18.19.1+dfsg-6ubuntu5 4 バージョンテーブル: 5 18.19.1+dfsg-6ubuntu5 500 6 500 http://archive.ubuntu.com/ubuntu noble/universe amd64 Packages
標準リポジトリからのインストールだとnodejs-18.19.1がインストールされるようです。 2025年3月25日現在は、Node.js安定版(LTS)の最新はnodejs-22.14.0となっています。 https://nodejs.org/ja/blog/release 
最新の安定版をイントールしていきます。

Node.js公式リポジトリのGPGキーをバイナリ形式で保存

Ubuntu標準リポジトリ以外からインストールを行うには、バイナリ形式(.gpg)でGPGキーを保存 するのが推奨されています。
bash
1sudo mkdir -p /etc/apt/keyrings #/etc/apt/keyringsディレクトリがない場合 2curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | \ 3 sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
  • curl -fsSL → Node.js公式のGPGキーを取得
  • gpg --dearmor -o /etc/apt/keyrings/nginx.gpg → バイナリ形式(.gpg)に変換して保存

Node.js公式リポジトリを追加

次に、Node.js v22.x のAPTリポジトリを追加します。
bash
1echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" | \ 2 sudo tee /etc/apt/sources.list.d/nodesource.list > /dev/null
  • signed-by=/etc/apt/keyrings/nginx.gpg → GPGキーの正しいパスを指定
  • nodistro は Ubuntu 24.04(noble)がまだ正式サポート外なので、NodeSourceが推奨する記述

パッケージリストを更新して、Node.js のインストール

bash
1sudo apt update 2sudo apt install nodejs -y
バージョンの確認
bash
1node -v 2v22.16.0 3 4npm -v 510.9.2
v22.16.0が表示されれば成功です。

npmをバージョンアップ

npmは2025年5月27日現在、最新安定版は11.4.Xになっています。npmを最新版へアップデートしておきます。
bash
1sudo npm install -g npm@latest
バージョンの確認
bash
1npm -v 211.4.1
  • npmの更新についてはNode.jsをaptで管理するようにしたため、apt updatenode.jsが更新されるとnpmのバージョンがNode.js公式リボジトリで管理されるものへダウングレードされることがあります。nodeとnpmはnvmで管理する方が良いかもしれません。これは別途記事にしたいと思います。
以上で、インストールは完了です。

2.【サーバ側作業】NginxのNext.js用の基本設定

Nginxの設定ですが、一旦、キャッシュとセキュリティヘッダの設定は省略して、動作可能な状態を目指します。 まず、NginxとNext.jsの関係を把握しておきます。
Nginxは「リバースプロキシ」として使います。Nginxがユーザーからのリクエストを受け取り、バックエンド(今回はNext.js)へ中継し、そのレスポンスをユーザーへ返す動作を担ってもらいます。
  • クライアント(ブラウザ)は https://example.com にアクセス
  • そのアクセスは Nginxが受け取る
  • Nginxはそのリクエストを Next.js(Node.js)で動くバックエンドへ転送(例:http://localhost:3000
  • Next.jsがレスポンスを返すと、それをNginxがクライアントに返す
「中継者としてNginxに働いてもらう」動作がリバースプロキシの動作ということになります。Next.jsやNode.jsはWebサーバの役割はありませんので、このような中継機能の担い手が必要になるわけです。

/etc/nginx/conf.d/example.com.conf への追記

まず、現状(対象のserverブロックだけ):
bash : example.com.conf
1server { 2 listen 443 ssl default_server; 3 listen [::]:443 ssl default_server; 4 http2 on; 5 listen 443 quic reuseport; 6 listen [::]:443 quic reuseport; 7 8 server_name example.com; 9 10 # HTTP/3 をサポートすることを通知(Alt-Svc ヘッダー) 11 add_header Alt-Svc 'h3=":443"; ma=86400' always; 12 13 # Basic認証 14 auth_basic "Restricted Area"; # <-認証プロンプトのタイトル 15 auth_basic_user_file /etc/nginx/.htpasswd; # <-認証ファイルのパス 16 ## 17 18 root /var/www/example.com/html; 19 index index.html; 20 21 location / { 22 try_files $uri $uri/ =404; 23 } 24 25 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot 26 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot 27 include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 28 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 29 30}
上記へ変更・追記していきます。
diff : example.com.conf
1server { 2・・上部省略・・ 3 4 root /var/www/example.com/html; 5- index index.html; 6 7- location / { 8- try_files $uri $uri/ =404; 9- } 10 11+ # Next.js用のアクセス禁止 12+ # セキュリティ:特定ファイルのアクセス禁止 13+ location ~* /(package(-lock)?\.json|next\.config\.(js|ts)|\.env.*) { 14+ deny all; 15+ } 16 17+ # セキュリティ:prisma ディレクトリのアクセス禁止 18+ location ^~ /prisma/ { 19+ deny all; 20+ } 21 22+ # セキュリティ:隠しファイルやディレクトリ(.git, .env, .vscode など) 23+ location ~ /\. { 24+ deny all; 25+ } 26 27+ # Next.jsメインルート 28+ location / { 29+ proxy_pass http://localhost:3000; 30+ proxy_http_version 1.1; 31 32+ proxy_set_header Upgrade $http_upgrade; 33+ proxy_set_header Connection 'upgrade'; 34+ proxy_set_header Host $host; 35+ proxy_set_header X-Real-IP $remote_addr; 36+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 37+ proxy_set_header X-Forwarded-Proto $scheme; 38+ 39+ proxy_cache_bypass $http_upgrade; 40+ } 41 42+ # _next/static や image 最適化ルート 43+ location /_next/ { 44+ proxy_pass http://localhost:3000; 45+ proxy_http_version 1.1; 46+ proxy_set_header Host $host; 47+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 48+ } 49 50・・下部省略・・ 51}
それぞれの説明です。
bash
1# セキュリティ:特定ファイルのアクセス禁止 2location ~* /(package(-lock)?\.json|next\.config\.(js|ts)|\.env.*) { 3 deny all; 4} 5 6# セキュリティ:prisma ディレクトリのアクセス禁止 7location ^~ /prisma/ { 8 deny all; 9} 10 11# セキュリティ:隠しファイルやディレクトリ(.git, .env, .vscode など) 12location ~ /\. { 13 deny all; 14}
↑Next.jsを動作させるためにサーバへアップロードしないといけないファイル(後述)や隠しファイル関連へのアクセスを禁止する。
bash
1location / { 2 proxy_pass http://localhost:3000;
↑Nginxが / へのリクエストを受けたら、localhost:3000(Next.jsアプリ)へ転送。
bash
1proxy_http_version 1.1;
↑HTTP/1.1で通信。
  • proxy_http_version は、Nginx(=リバースプロキシ)がバックエンドサーバー(Next.js)にリクエストを送るときのHTTPバージョンを指定するものです。
  • Node.jsのhttpモジュールや多くのバックエンドがHTTP/1.1ベース
  • HTTP/2やHTTP/3はproxy_passのバックエンド通信に対応してない or 非推奨
  • WebSocket対応などのためにも必要。WebSocketはチャットシステムなどの双方向通信を利用するときに使用するプロトコルです。
bash
1proxy_set_header Upgrade $http_upgrade; 2proxy_set_header Connection 'upgrade';
↑WebSocket対応用のヘッダー。Next.jsでSocket系を使うときに必要(不要なら削除しても動くが、保険として残すのが吉)。
bash
1proxy_set_header Host $host; 2proxy_set_header X-Real-IP $remote_addr; 3proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 4proxy_set_header X-Forwarded-Proto $scheme;
↑これらは「リクエスト元の情報(IP・プロトコルなど)」をバックエンドに正しく伝えるためのヘッダ。
bash
1proxy_cache_bypass $http_upgrade;
↑WebSocketなどのアップグレード時にキャッシュをバイパスする設定。
bash
1 location /_next/ {}
↑Next.jsは/_next/image と /_next/static にビルドデータが格納されるので、これらが正しく proxy_pass されていないと、画像が表示されない or 404になるというトラブルになります。通常の / ルートとは別で扱うべきルートなので、Nginxで個別にlocationディレクティブを定義する必要があります。
もし、Next.jsのApp Routerのapiを使う場合は加えて、/_next/と同じ内容でlocation /api/ {}を作成しておいてください。私の環境では基本はサーバアクションで構築するため、apiディレクティブは設定していません。

編集が終わったら、Nginxをリロード

bash
1sudo nginx -t 2sudo systemctl reload nginx

3.【ローカルPC側作業】Next.js作成アプリのアップロード

ビルドは「ローカルで完結」 → サーバは「実行専用」という前提で記述します。 この方が、安全で、余計なファイルをアップせずに済むからです。
私は最初ビルドデータである.next/ディレクトリの内容だけアップすればいいのでは? と思っていたのですが、それは間違いです。

.next/だけをアップしてはいけない理由

○「.next/」はビルド成果物だけで、依存関係(node_modules)や設定情報が含まれていない
「.next/」はあくまでNext.jsのコンパイル済みアプリでこれを動かすには下記のデータ必要です。
  • node_modules/
  • package.json
  • next.config.ts
  • public/(画像・静的ファイル)
これらが揃っていないと本番サーバで正常に動きません。
○Next.jsの実行形式はNode.jsアプリであり、完全自己完結ではない
  • .next/だけでは、必要なモジュールや設定ファイルを失っているため、動作しない
  • 本番環境で npm run start を叩くには、Next.jsアプリ一式+依存モジュールが必要
○node_modulesは通常アップロードしないが、サーバ側でnpm installを行う必要がある
  • node_modules/は巨大なのでアップロード非推奨
  • package.json + package-lock.json は必須
  • サーバで npm ci などで再インストールするのが正攻法

アップロード必要ファイル

というわけどアップロードが必要なファイルと必要なファイルを分けると下記のとおりです。
text
1必要: 2├── .next/ ← ビルド成果物 3├── public/ ← 静的ファイル 4├── prisma/ ← 使っていれば 5├── package.json 6├── package-lock.json 7├── next.config.ts(使ってるなら) 8 9アップロードしない: 10├── .env系 11├── .ignore系 12├── その他.*系 13├── eslint.config.mjs 14├── tsconfig.json 15├── postcss.config.mjs 16├── next-env.d.ts 17├── node_modules/ 18├── src/app/ ← ビルドデータに含まれるから
「.env」の扱いについて補足:本番環境では、.envファイルをアップロードせず、PM2の設定ファイル(ecosystem.config.js)や、環境変数で渡す方法がベスト(後述)。
その他コンパイル時にのみ利用しているファイルなども不要になります。

アップロードコマンド例:

SFTPやSCPなどでアップロードしても良いのですが、ファイル数が多いので私はrsyncコマンドを利用しています。アップロードが早い印象があります。
下記はあくまで例です。「[サーバ構築編#6] UbuntuへNginx1.26.3をインストール」で作業したSetGIDsetfacl(ACL)が済んでいる前提です。これらをやっていない場合は、所有者やパーミッション回りの指定も必要になります(--chown=user:www-data--chmod=D2775,F664)。
rsync : sample
1rsync -avz --delete \ 2 --exclude='node_modules' \ 3 --exclude='src' \ 4 --exclude='tsconfig.json' \ 5 --exclude='.git/' \ 6 --exclude='.vscode/' \ 7 --exclude='.env*' \ 8 --exclude='postcss.config.*' \ 9 --exclude='eslint.config.*' \ 10 --exclude='next-env.d.ts' \ 11 --exclude='.gitignore' \ 12 --exclude='.prettierrc' \ 13 --exclude='.DS_Store' \ 14 --exclude='components.json' \ 15 xxxx/xxxx/project/new-app/ \ 16 user@xxx.xxx.xxx.xxx:/var/www/example.com/html/
オプション説明
-aパーミッション保持・再帰的コピーなどの「アーカイブモード」
-v実行内容を表示
-z転送時に圧縮
--deleteサーバ側に存在していてローカルにないファイルを削除(完全ミラー)
--exclude指定パターンを除外(複数可)
-e "ssh -i ... -p ..."ssh接続の認証ファイルとポート番号
/xxxx/xxxx/project/newapp/アップ元のローカルPCのディレクトリ
user@xxx.xxx.xxx.xxx:/var/www/example.com/html/ssh接続のユーザ@サーバIP:アップ先のディレクトリ
もし、macユーザで「[小技:Mac限定]SSH接続時に秘密鍵のパスフレーズ入力が不要に?!」で~/.ssh/configでSSHの接続設定をしている方は、下記ように少し省略可能です。
zsh
1rsync -avz --delete \ 2 --exclude='node_modules' \ 3 --exclude='src' \ 4 --exclude='tsconfig.json' \ 5 --exclude='.git/' \ 6 --exclude='.vscode/' \ 7 --exclude='.env*' \ 8 --exclude='postcss.config.*' \ 9 --exclude='eslint.config.*' \ 10 --exclude='next-env.d.ts' \ 11 --exclude='.gitignore' \ 12 --exclude='.prettierrc' \ 13 --exclude='.DS_Store' \ 14 --exclude='components.json' \ 15 -e "ssh" \ 16 /xxxx/xxxx/project/new-app/ \ 17 my-web-server:/var/www/example.com/html/
  • --exclude=が結構長くなるので、より柔軟にしたいなら .rsync-exclude ファイルに分けて管理もできる
zsh : .rsync-exclude
1# .rsync-exclude 2node_modules 3src 4tsconfig.json 5.git/ 6.vscode/ 7.env* 8postcss.config.* 9eslint.config.* 10next-env.d.ts 11.gitignore 12.prettierrc 13.DS_Store 14components.json
zsh
1rsync -avz --delete --exclude-from='.rsync-exclude' -e "ssh" \ 2 ~/project/new-app/ \ 3 my-web-server:/var/www/example.com/html/
こんな感じでアップロードを完了させます。

4.【サーバ側作業】依存関係のあるモジュールのインストール

この作業はアップロードが終わったらサーバ側で毎回やる作業になります。
bash
1cd /var/www/example.com/html/ 2npm ci
prismaを利用している場合は下記も合わせて実行:
bash
1npx prisma generate
npm installじゃないの? という方もいると思います。npm cinpm installは次のような違いがあります。
○npm ci と npm install の違い(本番運用で使うべきはどっち?)
コマンド用途特徴・ポイント
npm install開発・普段使い全般package.json を見て、package-lock.jsonと整合性がなければ更新してしまう
npm ci本番環境・CI/CD向けのインストールpackage-lock.json に完全準拠。超高速・再現性あり。node_modulesがあれば削除して再構築
○npm ci が本番向けな理由
  • 再現性が100%保証される(lockファイルどおりにインストール)
  • 依存のバージョンが勝手に変わらない
  • CI/CDパイプラインでも使われる業界標準

5.【サーバ側作業】とりあえずの実行

もし、対象プロジェクトのルートディレクトリから移動していた場合
bash
1cd /var/www/example.com/html/
続いて、フォアグラウンドで、とりあえずのNext.jsアプリの実行
bash
1npm start
とりあえず、これでブラウザでNext.jsのデフォルトページは表示できます。ぜひ、確認してみてください。
Next.js15デフォルトページ
確認が終わったら、[control]+[c]でフォアグラウンドをキャンセルしてください。
これで、サーバ上にアップロードしたNext.jsアプリを、手動で起動して確認することができました。
しかし、この方法ではターミナルを閉じるとアプリが停止してしまいます。 本番運用では「再起動後の自動起動」「ログ管理」「複数アプリのプロセス管理」なども必要になってきます。
次回は、それらを簡単に実現できる PM2 を使った運用方法をご紹介します!
続きはこちら...
この記事の執筆・編集担当
DE

松本 孝太郎

DELOGs編集部/中年新米プログラマー

ここ数年はReact&MUIのフロントエンドエンジニアって感じでしたが、Next.jsを学んで少しずつできることが広がりつつあります。その実践記録をできるだけ共有していければと思っています。