XServer VPSにNode.js+TypeScriptをsystemdで常駐させる最小構成手順
この記事でわかること
XServer VPS に Node.js + TypeScript で書いたAPIサーバーを systemd のサービスとして常駐させるまでの最小構成手順を解説します。
RailwayやRenderなどのPaaSは便利ですが、無料枠の廃止やスリープ問題に悩まされることがあります。VPSであれば月額数百円〜の固定費で、スリープなし・帯域制限なしの環境を自分でコントロールできます。
本記事の構成は次のとおりです。
- VPS初期設定(ユーザー・SSH・ファイアウォール)
- Node.js / pnpm のインストール
- TypeScript プロジェクトのビルドと配置
- systemd サービス化と自動起動
前提環境
| 項目 | バージョン / 設定 |
|---|---|
| XServer VPS OS | Ubuntu 22.04 LTS |
| Node.js | 20.x (LTS) |
| パッケージマネージャ | pnpm 9.x |
| TypeScript | 5.x |
| フレームワーク | Hono(軽量・型安全でAPIに最適) |
XServer VPS はコントロールパネルからOSイメージを選ぶだけでUbuntu 22.04が起動します。プランは最小の 1コア / 512MB RAM で十分動作確認できます。
ステップ1: VPS初期設定
一般ユーザーの作成とSSH鍵設定
# rootでSSHログイン後
adduser deploy
usermod -aG sudo deploy
# SSH公開鍵を登録
mkdir -p /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
# ローカルの ~/.ssh/id_ed25519.pub の内容を貼り付ける
nano /home/deploy/.ssh/authorized_keys
chmod 600 /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
SSHのrootログイン禁止 & パスワード認証無効化
nano /etc/ssh/sshd_config
以下を変更します。
PermitRootLogin no
PasswordAuthentication no
systemctl restart ssh
UFW ファイアウォール設定
ufw allow OpenSSH
ufw allow 3000/tcp # APIサーバーのポート
ufw enable
ufw status
ステップ2: Node.js と pnpm のインストール
NodeSourceの公式スクリプトを使って Node.js 20.x を入れます。
# deployユーザーでSSHログイン
sudo apt update && sudo apt upgrade -y
# Node.js 20.x のセットアップ
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
node -v # v20.x.x が出ればOK
# pnpm のインストール
sudo npm install -g pnpm
pnpm -v
ステップ3: TypeScriptプロジェクトのビルドと配置
ローカルでのビルド(推奨)
VPS上でコンパイルするとメモリ消費が大きいため、ローカルでビルドしてdistだけ転送する方式を推奨します。
// tsconfig.json(最小構成)
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"strict": true
},
"include": ["src"]
}
# ローカルでビルド
pnpm build # "build": "tsc" をpackage.jsonに定義
# rsyncでVPSに転送(node_modulesは除外)
rsync -avz --exclude='node_modules' --exclude='.git' \
./ deploy@YOUR_VPS_IP:/home/deploy/myapp/
VPS側での依存パッケージインストール
cd /home/deploy/myapp
pnpm install --prod # devDependenciesは不要
動作確認
node dist/index.js
# 別ターミナルで
curl http://localhost:3000/health
# {"status":"ok"} が返れば成功
ステップ4: systemd でサービス化
サービスファイルの作成
sudo nano /etc/systemd/system/myapp.service
[Unit]
Description=My TypeScript API Server
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/myapp
ExecStart=/usr/bin/node dist/index.js
Restart=on-failure
RestartSec=5s
# 環境変数はここで直書きするか EnvironmentFile を使う
Environment=NODE_ENV=production
Environment=PORT=3000
# EnvironmentFile=/home/deploy/myapp/.env
[Install]
WantedBy=multi-user.target
サービスの有効化と起動
sudo systemctl daemon-reload
sudo systemctl enable myapp # OS再起動時に自動起動
sudo systemctl start myapp
sudo systemctl status myapp
ステータスが active (running) になれば完成です。
ログの確認
# リアルタイムログ
sudo journalctl -u myapp -f
# 直近100行
sudo journalctl -u myapp -n 100
ステップ5: デプロイ更新フロー(運用時)
# ローカルでビルド後にrsync → サービス再起動
rsync -avz --exclude='node_modules' --exclude='.git' \
./ deploy@YOUR_VPS_IP:/home/deploy/myapp/
ssh deploy@YOUR_VPS_IP \
"cd /home/deploy/myapp && pnpm install --prod && sudo systemctl restart myapp"
このワンライナーをMakefileやpackage.jsonのdeployスクリプトに登録しておくと便利です。
よくある詰まりポイントと対処
| 症状 | 原因 | 対処 |
|---|---|---|
systemctl start でfailed | ExecStartのパスが間違っている | which node で絶対パスを確認 |
| ポートに繋がらない | UFWでブロックされている | ufw allow 3000/tcp を再確認 |
| 再起動後にサービスが起動しない | enable し忘れ | systemctl enable myapp |
Cannot find module エラー | pnpm install --prod を忘れた | VPS側で再度実行 |
| メモリ不足でクラッシュ | 512MBプランの限界 | 1GBプランへ変更 or スワップ追加 |
スワップ追加(512MBプラン向け)
sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
まとめ
XServer VPS + Node.js + TypeScript + systemd の最小構成デプロイは、以下の5ステップで完結します。
- VPS初期設定:一般ユーザー・SSH鍵・UFW
- Node.js / pnpm インストール:NodeSourceスクリプトを利用
- ローカルビルド → rsync転送:VPSのメモリを節約
- systemdサービス化:
enableで自動起動を忘れずに - journalctlでログ監視:トラブル時の第一歩
PaaSのようなGUI操作はありませんが、一度セットアップしてしまえば 月額数百円の固定コストで自由度の高いAPIサーバーを運用できます。スケールアップも簡単なXServer VPSは個人開発のホスティング先としておすすめです。
VPSの運用やLinuxサーバー管理をもっと体系的に学びたい方には、以下の書籍も参考になります。