vps-project-setup

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

VPS Project Setup Skill

VPS项目部署技能

A generalized guide for deploying any project to a Linux VPS — covering compatibility checks, deployment, networking/firewall issues, tunneling, process management, logs, and ongoing maintenance.
这是一份将任意项目部署到Linux VPS的通用指南——涵盖兼容性检查、部署、网络/防火墙问题、隧道搭建、进程管理、日志管理以及日常维护。

Phase 1 — Gather Server Specs

第一阶段——收集服务器规格

Before doing anything, check if the VPS can actually run the project.
bash
lscpu          # CPU cores, architecture
free -h        # Total/available RAM
df -h          # Disk space
cat /etc/os-release   # OS and version
uname -r       # Kernel version
Parse output and summarize: CPU cores, RAM (total/available), disk (total/free), OS.
See
references/spec-interpretation.md
for reading and interpreting common output formats.

在进行任何操作之前,先确认VPS是否能够实际运行该项目。
bash
lscpu          # CPU核心数、架构
free -h        # 总内存/可用内存
df -h          # 磁盘空间
cat /etc/os-release   # 操作系统及版本
uname -r       # 内核版本
解析输出结果并汇总:CPU核心数、内存(总/可用)、磁盘(总/空闲)、操作系统。
如需了解如何读取和解析常见输出格式,请查看
references/spec-interpretation.md

Phase 2 — Compatibility Check

第二阶段——兼容性检查

Given the project's stack (from GitHub URL, code snippet, or user description), assess:
FactorWhat to check
RuntimeNode.js version, Python version, Go, etc. — is it installed?
RAMDoes the build + runtime fit? Next.js builds need ~1GB free
DiskDoes app + dependencies + data fit?
OSDoes the project require specific Linux distros or kernel features?
Native depsFFmpeg, yt-dlp, ImageMagick, etc. — are they available?
Concurrent loadHow many simultaneous users/jobs can the server handle?
State clearly: "Compatible ✅" or "Not compatible ❌ — here's why."

根据项目的技术栈(来自GitHub链接、代码片段或用户描述),评估以下内容:
因素检查内容
运行环境Node.js版本、Python版本、Go等——是否已安装?
内存构建+运行阶段是否满足内存需求?Next.js构建需要约1GB空闲内存
磁盘应用+依赖+数据是否能容纳?
操作系统项目是否需要特定的Linux发行版或内核特性?
原生依赖FFmpeg、yt-dlp、ImageMagick等——是否可用?
并发负载服务器可处理多少并发用户/任务?
明确给出结论:“兼容 ✅” 或 “不兼容 ❌——原因如下。”

Phase 3 — Clone the Repository

第三阶段——克隆仓库

Public repo

公开仓库

bash
git clone https://github.com/user/repo.git project-folder
cd project-folder
bash
git clone https://github.com/user/repo.git project-folder
cd project-folder

Private repo (no AWS/GitHub Actions access)

私有仓库(无AWS/GitHub Actions权限)

Use a Personal Access Token (PAT):
  1. GitHub → Settings → Developer Settings → Personal access tokens → Tokens (classic)
  2. Create token with
    repo
    scope
  3. Clone with token embedded:
bash
git clone https://YOUR_TOKEN@github.com/user/repo.git project-folder
Security note: Tokens in URLs are stored in
.git/config
. After cloning, optionally remove it:
git remote set-url origin https://github.com/user/repo.git

使用个人访问令牌(PAT):
  1. GitHub → 设置 → 开发者设置 → 个人访问令牌 → 经典令牌
  2. 创建带有
    repo
    权限范围的令牌
  3. 嵌入令牌进行克隆:
bash
git clone https://YOUR_TOKEN@github.com/user/repo.git project-folder
安全提示: URL中的令牌会存储在
.git/config
中。克隆完成后,可选择移除令牌:
git remote set-url origin https://github.com/user/repo.git

Phase 4 — Install Dependencies & Configure

第四阶段——安装依赖与配置

Node.js projects

Node.js项目

bash
npm install
If Node.js is missing:
bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install --lts
bash
npm install
如果未安装Node.js:
bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install --lts

Python projects

Python项目

bash
pip install -r requirements.txt          # or
pip install -r requirements.txt --break-system-packages
bash
pip install -r requirements.txt          # 或
pip install -r requirements.txt --break-system-packages

Environment variables

环境变量

Always check for a
.env.example
or README mentions of required env vars.
bash
undefined
务必检查是否存在
.env.example
文件或README中提到的必填环境变量。
bash
undefined

Create .env from example

从示例文件创建.env

cp .env.example .env nano .env # Edit values
cp .env.example .env nano .env # 编辑变量值

Or write a specific value directly

或直接写入特定变量值

echo 'DATABASE_URL="file:./dev.db"' > .env

Common required env vars by stack:
- **Next.js/Prisma**: `DATABASE_URL`
- **Any web app**: `PORT`, `NODE_ENV=production`, `SECRET_KEY`
- **Cloud storage**: `AWS_ACCESS_KEY_ID`, `S3_BUCKET`, etc.
echo 'DATABASE_URL="file:./dev.db"' > .env

各技术栈常见的必填环境变量:
- **Next.js/Prisma**:`DATABASE_URL`
- **任意Web应用**:`PORT`、`NODE_ENV=production`、`SECRET_KEY`
- **云存储**:`AWS_ACCESS_KEY_ID`、`S3_BUCKET`等

Database setup (if applicable)

数据库设置(如适用)

bash
undefined
bash
undefined

Prisma

Prisma

npx prisma generate npx prisma db push # or npx prisma migrate deploy
npx prisma generate npx prisma db push # 或 npx prisma migrate deploy

Django

Django

python manage.py migrate

---
python manage.py migrate

---

Phase 5 — Build & Run

第五阶段——构建与运行

Build (if needed)

构建(如需要)

bash
npm run build      # Next.js, React, etc.
Monorepo warning: If
npm run build
warns about multiple
package-lock.json
files or "workspace root mismatch", delete the conflicting lockfile in the parent directory:
rm ../package-lock.json
then rebuild. This commonly happens in cloud IDEs (Gitpod, etc.).
bash
npm run build      # Next.js、React等项目
单体仓库警告: 如果
npm run build
提示存在多个
package-lock.json
文件或“工作区根目录不匹配”,请删除父目录中冲突的锁文件:
rm ../package-lock.json
然后重新构建。这种情况常见于云IDE(如Gitpod等)中。

Run with PM2 (recommended for persistent processes)

使用PM2运行(推荐用于持久化进程)

bash
undefined
bash
undefined

Install PM2

安装PM2

sudo npm install -g pm2
sudo npm install -g pm2

Start app

启动应用

pm2 start npm --name "my-app" -- run start
pm2 start npm --name "my-app" -- run start

OR for a specific command:

或针对特定命令:

pm2 start "python app.py" --name "my-app" pm2 start "gunicorn app:app" --name "my-app"
pm2 start "python app.py" --name "my-app" pm2 start "gunicorn app:app" --name "my-app"

Enable auto-restart on server reboot

启用服务器重启后自动恢复运行

pm2 startup # Follow the printed command exactly — copy/paste it pm2 save # Save current process list
pm2 startup # 严格按照打印的命令执行——复制粘贴即可 pm2 save # 保存当前进程列表

Useful PM2 commands

常用PM2命令

pm2 list # See all running apps pm2 logs my-app # View live logs pm2 restart my-app pm2 stop my-app pm2 delete my-app
undefined
pm2 list # 查看所有运行中的应用 pm2 logs my-app # 查看实时日志 pm2 restart my-app pm2 stop my-app pm2 delete my-app
undefined

Run with systemd (alternative, no npm required)

使用systemd运行(替代方案,无需npm)

See
references/systemd-service.md
for creating a
.service
file.

如需创建
.service
文件,请查看
references/systemd-service.md

Phase 6 — Make It Accessible (Networking)

第六阶段——实现可访问性(网络配置)

This is the most common point of failure. Use the decision tree below.
这是最常见的故障点。请使用以下决策树排查。

Decision Tree

决策树

Does the server have a stable public IP?
├── YES → Is port open?
│   ├── Check with: curl http://localhost:PORT from server
│   ├── UFW (Ubuntu/Debian): sudo ufw allow PORT/tcp
│   ├── firewalld (CentOS/RHEL): sudo firewall-cmd --add-port=PORT/tcp --permanent
│   └── AWS/GCP/Azure → Must open in CLOUD CONSOLE (see below)
└── NO (IP changes, behind NAT, cloud shell, no console access)
    └── Use a tunnel (see Tunneling section)
服务器是否有稳定的公网IP?
├── 是 → 端口是否开放?
│   ├── 检查方式:在服务器上执行 curl http://localhost:PORT
│   ├── UFW(Ubuntu/Debian):sudo ufw allow PORT/tcp
│   ├── firewalld(CentOS/RHEL):sudo firewall-cmd --add-port=PORT/tcp --permanent
│   └── AWS/GCP/Azure → 必须在云控制台中开放(见下文)
└── 否(IP动态变化、位于NAT之后、云Shell、无控制台权限)
    └── 使用隧道(见隧道搭建部分)

Cloud Provider Firewall (most common issue on AWS)

云服务商防火墙(AWS上最常见的问题)

Symptom: App works on
localhost
but not from outside.
ufw
command not found or doesn't help.
AWS EC2:
  1. AWS Console → EC2 → Instances → select your instance
  2. Bottom panel → Security tab → click the Security Group link
  3. Edit Inbound Rules → Add Rule:
    • Type: Custom TCP
    • Port:
      3000
      (or your port)
    • Source:
      0.0.0.0/0
      (Anywhere-IPv4)
  4. Save rules
GCP: VPC Network → Firewall → Create rule → allow TCP on your port Azure: Network Security Group → Inbound Security Rules → Add
No access to cloud console? → Use tunneling (next section)
症状: 应用在
localhost
上正常运行,但外部无法访问。
ufw
命令不存在或无效。
AWS EC2操作步骤:
  1. AWS控制台 → EC2 → 实例 → 选择你的实例
  2. 底部面板 → 安全标签页 → 点击安全组链接
  3. 编辑入站规则 → 添加规则:
    • 类型:自定义TCP
    • 端口:
      3000
      (或你的应用端口)
    • 来源:
      0.0.0.0/0
      (任何IPv4地址)
  4. 保存规则
GCP: VPC网络 → 防火墙 → 创建规则 → 允许TCP访问你的端口 Azure: 网络安全组 → 入站安全规则 → 添加规则 无云控制台权限? → 使用隧道(下一章节)

Find Your Public IP

查询公网IP

bash
curl ifconfig.me
bash
curl ifconfig.me

If IP keeps changing → you're behind a NAT → use tunneling

如果IP持续变化 → 你处于NAT之后 → 使用隧道


---

---

Phase 7 — Tunneling (When Direct Access Isn't Possible)

第七阶段——隧道搭建(无法直接访问时)

Use tunnels when: no cloud console access, dynamic/NAT IPs, cloud IDE environments (Gitpod, Codespaces, CloudShell).
以下场景可使用隧道:无云控制台权限、动态/NAT IP、云IDE环境(Gitpod、Codespaces、CloudShell)。

Option 1: ngrok (Most Reliable — Recommended)

方案1:ngrok(最可靠——推荐)

Requires free account at ngrok.com — no time limit on free tier.
bash
undefined
需要在ngrok.com注册免费账户——免费版无时间限制。
bash
undefined

Install

安装

sudo npm install -g ngrok
sudo npm install -g ngrok

OR: snap install ngrok

或:snap install ngrok

Authenticate (one-time, token from ngrok dashboard)

认证(一次性操作,令牌来自ngrok控制台)

ngrok config add-authtoken YOUR_TOKEN_HERE
ngrok config add-authtoken YOUR_TOKEN_HERE

Start tunnel

启动隧道

ngrok http 3000

ngrok prints a `Forwarding` URL like `https://abc123.ngrok-free.app` — share this.

> **Important:** Free tier generates a new URL every restart. Use `pm2` or keep the terminal alive.
> On first visit, ngrok shows a warning page — click "Visit Site" to proceed.
ngrok http 3000

ngrok会输出一个类似`https://abc123.ngrok-free.app`的`Forwarding`(转发)链接——可分享此链接。

> **重要提示:** 免费版每次重启都会生成新的URL。可使用`pm2`或保持终端处于运行状态。
> 首次访问时,ngrok会显示警告页面——点击“Visit Site”(访问站点)即可继续。

Option 2: localhost.run (No install, no account)

方案2:localhost.run(无需安装,无需账户)

bash
ssh -R 80:localhost:3000 nokey@localhost.run
Prints a
https://xxx.localhost.run
URL. No time limit on free tier.
bash
ssh -R 80:localhost:3000 nokey@localhost.run
会输出一个
https://xxx.localhost.run
格式的链接。免费版无时间限制。

Option 3: Pinggy (No install, no account)

方案3:Pinggy(无需安装,无需账户)

bash
ssh -p 443 -R0:localhost:3000 a.pinggy.io
Free tier expires in 60 minutes. Good for quick demos.
bash
ssh -p 443 -R0:localhost:3000 a.pinggy.io
免费版60分钟后过期。适合快速演示。

Option 4: Cloudflare Tunnel (Best for permanent setups)

方案4:Cloudflare Tunnel(最适合永久部署)

See
references/cloudflare-tunnel.md
for setup — requires a Cloudflare account and domain.
如需设置,请查看
references/cloudflare-tunnel.md
——需要Cloudflare账户和域名。

Tunnel Troubleshooting

隧道故障排查

ErrorCauseFix
503 Tunnel Unavailable
App not running on that portCheck
pm2 logs
, run
curl localhost:PORT
404 /login?error=config
Browser cache from old appOpen in Incognito, clear cache
URL expiredFree tunnel timeoutRestart tunnel, use ngrok with account
Connection refusedApp crashedCheck logs:
pm2 logs app-name

错误信息原因解决方法
503 Tunnel Unavailable
应用未在对应端口运行查看
pm2 logs
,执行
curl localhost:PORT
检查
404 /login?error=config
旧应用的浏览器缓存使用无痕模式打开,清除缓存
URL过期免费隧道超时重启隧道,使用带账户的ngrok
连接被拒绝应用崩溃查看日志:
pm2 logs app-name

Phase 8 — Logs & Debugging

第八阶段——日志与调试

PM2 Logs

PM2日志

bash
pm2 logs my-app              # Live tail (last 15 lines)
pm2 logs my-app --lines 100  # Last 100 lines
pm2 logs my-app --err        # Only errors
~/.pm2/logs/my-app-out.log   # stdout log file
~/.pm2/logs/my-app-error.log # stderr log file
bash
pm2 logs my-app              # 实时查看日志(最后15行)
pm2 logs my-app --lines 100  # 查看最后100行日志
pm2 logs my-app --err        # 仅查看错误日志
~/.pm2/logs/my-app-out.log   # 标准输出日志文件
~/.pm2/logs/my-app-error.log # 标准错误日志文件

System Logs

系统日志

bash
journalctl -u my-service -f         # systemd service logs
journalctl -n 50                    # last 50 system log lines
tail -f /var/log/nginx/access.log   # nginx access logs (if proxying)
bash
journalctl -u my-service -f         # systemd服务日志
journalctl -n 50                    # 最后50行系统日志
tail -f /var/log/nginx/access.log   # nginx访问日志(如果使用反向代理)

Check If App Is Actually Running

检查应用是否真的在运行

bash
pm2 list                     # PM2-managed apps
curl http://localhost:3000   # Does the app respond locally?
ss -tlnp | grep 3000         # Is something listening on port 3000?
ps aux | grep node           # Find running Node processes

bash
pm2 list                     # PM2管理的应用列表
curl http://localhost:3000   # 应用在本地是否响应?
ss -tlnp | grep 3000         # 端口3000是否有进程监听?
ps aux | grep node           # 查找运行中的Node进程

Phase 9 — Updates & CI/CD

第九阶段——更新与CI/CD

Manual Update Workflow

手动更新流程

bash
cd project-folder
git pull
npm install          # if dependencies changed
npm run build        # if build step needed
pm2 restart my-app
bash
cd project-folder
git pull
npm install          # 如果依赖有变更
npm run build        # 如果需要构建步骤
pm2 restart my-app

Automated (GitHub Actions)

自动化更新(GitHub Actions)

For auto-deploy on every
git push
: See
references/github-actions-deploy.md
for a sample workflow that SSHes into the VPS and runs the above commands automatically.

实现每次
git push
自动部署: 请查看
references/github-actions-deploy.md
获取示例工作流,该工作流可通过SSH连接到VPS并自动执行上述命令。

Phase 10 — Stopping / Shutting Down

第十阶段——停止应用/关闭服务器

bash
undefined
bash
undefined

Stop app (keep VPS running)

停止应用(保持VPS运行)

pm2 stop my-app
pm2 stop my-app

Remove app from PM2 permanently

从PM2中永久移除应用

pm2 delete my-app pm2 save
pm2 delete my-app pm2 save

Shut down entire server

关闭整个服务器

sudo shutdown now
sudo shutdown now

OR

sudo poweroff

> **AWS/GCP note:** After `shutdown now`, you must restart from the cloud console (EC2 → Start Instance).

---
sudo poweroff

> **AWS/GCP提示:** 执行`shutdown now`后,必须从云控制台重启服务器(EC2 → 启动实例)。

---

Common Edge Cases

常见边缘场景

Port Already In Use

端口已被占用

bash
ss -tlnp | grep 3000          # Find what's using port 3000
kill -9 $(lsof -t -i:3000)    # Kill it
bash
ss -tlnp | grep 3000          # 查找占用端口3000的进程
kill -9 $(lsof -t -i:3000)    # 终止该进程

App Runs Fine Locally but 404 on Domain/Tunnel

应用本地运行正常,但域名/隧道访问返回404

  • Check if app binds to
    0.0.0.0
    not just
    127.0.0.1
    — some frameworks need
    --hostname 0.0.0.0
  • Browser cache: always test in Incognito first
  • 检查应用是否绑定到
    0.0.0.0
    而非仅
    127.0.0.1
    ——部分框架需要添加
    --hostname 0.0.0.0
    参数
  • 浏览器缓存:始终先使用无痕模式测试

IP Changes Every SSH Session

每次SSH会话IP都变化

You are behind a NAT or in a shared container. You cannot host without a tunnel. Ngrok or localhost.run are your best options.
你处于NAT之后或共享容器中。没有隧道无法进行托管。Ngrok或localhost.run是最佳选择。

UFW Not Found

UFW不存在

You're on Amazon Linux, Alpine, or another minimal distro. Firewall is managed externally (cloud console) or via
iptables
/
firewall-cmd
.
你使用的是Amazon Linux、Alpine或其他轻量发行版。防火墙由外部(云控制台)管理,或通过
iptables
/
firewall-cmd
管理。

Build Fails with "Out of Memory"

构建失败,提示“内存不足”

bash
undefined
bash
undefined

Node.js — increase memory limit

Node.js — 增加内存限制

NODE_OPTIONS="--max-old-space-size=2048" npm run build
undefined
NODE_OPTIONS="--max-old-space-size=2048" npm run build
undefined

Cannot
sudo
on the Server

服务器上无法使用
sudo

You may not have sudo rights. Try the command without
sudo
, or ask your server admin to add your user to the sudoers group.
你可能没有sudo权限。尝试不使用
sudo
执行命令,或联系服务器管理员将你的用户添加到sudoers组。