vps-project-setup
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseVPS 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 versionParse output and summarize: CPU cores, RAM (total/available), disk (total/free), OS.
Seefor reading and interpreting common output formats.references/spec-interpretation.md
在进行任何操作之前,先确认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:
| Factor | What to check |
|---|---|
| Runtime | Node.js version, Python version, Go, etc. — is it installed? |
| RAM | Does the build + runtime fit? Next.js builds need ~1GB free |
| Disk | Does app + dependencies + data fit? |
| OS | Does the project require specific Linux distros or kernel features? |
| Native deps | FFmpeg, yt-dlp, ImageMagick, etc. — are they available? |
| Concurrent load | How 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-folderbash
git clone https://github.com/user/repo.git project-folder
cd project-folderPrivate repo (no AWS/GitHub Actions access)
私有仓库(无AWS/GitHub Actions权限)
Use a Personal Access Token (PAT):
- GitHub → Settings → Developer Settings → Personal access tokens → Tokens (classic)
- Create token with scope
repo - Clone with token embedded:
bash
git clone https://YOUR_TOKEN@github.com/user/repo.git project-folderSecurity note: Tokens in URLs are stored in. After cloning, optionally remove it:.git/configgit remote set-url origin https://github.com/user/repo.git
使用个人访问令牌(PAT):
- GitHub → 设置 → 开发者设置 → 个人访问令牌 → 经典令牌
- 创建带有权限范围的令牌
repo - 嵌入令牌进行克隆:
bash
git clone https://YOUR_TOKEN@github.com/user/repo.git project-folder安全提示: URL中的令牌会存储在中。克隆完成后,可选择移除令牌:.git/configgit remote set-url origin https://github.com/user/repo.git
Phase 4 — Install Dependencies & Configure
第四阶段——安装依赖与配置
Node.js projects
Node.js项目
bash
npm installIf Node.js is missing:
bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install --ltsbash
npm install如果未安装Node.js:
bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install --ltsPython projects
Python项目
bash
pip install -r requirements.txt # or
pip install -r requirements.txt --break-system-packagesbash
pip install -r requirements.txt # 或
pip install -r requirements.txt --break-system-packagesEnvironment variables
环境变量
Always check for a or README mentions of required env vars.
.env.examplebash
undefined务必检查是否存在文件或README中提到的必填环境变量。
.env.examplebash
undefinedCreate .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
undefinedbash
undefinedPrisma
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: Ifwarns about multiplenpm run buildfiles or "workspace root mismatch", delete the conflicting lockfile in the parent directory:package-lock.jsonthen rebuild. This commonly happens in cloud IDEs (Gitpod, etc.).rm ../package-lock.json
bash
npm run build # Next.js、React等项目单体仓库警告: 如果提示存在多个npm run build文件或“工作区根目录不匹配”,请删除父目录中冲突的锁文件:package-lock.json然后重新构建。这种情况常见于云IDE(如Gitpod等)中。rm ../package-lock.json
Run with PM2 (recommended for persistent processes)
使用PM2运行(推荐用于持久化进程)
bash
undefinedbash
undefinedInstall 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
undefinedpm2 list # 查看所有运行中的应用
pm2 logs my-app # 查看实时日志
pm2 restart my-app
pm2 stop my-app
pm2 delete my-app
undefinedRun with systemd (alternative, no npm required)
使用systemd运行(替代方案,无需npm)
See for creating a file.
references/systemd-service.md.service如需创建文件,请查看。
.servicereferences/systemd-service.mdPhase 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 but not from outside. command not found or doesn't help.
localhostufwAWS EC2:
- AWS Console → EC2 → Instances → select your instance
- Bottom panel → Security tab → click the Security Group link
- Edit Inbound Rules → Add Rule:
- Type: Custom TCP
- Port: (or your port)
3000 - Source: (Anywhere-IPv4)
0.0.0.0/0
- 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)
症状: 应用在上正常运行,但外部无法访问。命令不存在或无效。
localhostufwAWS EC2操作步骤:
- AWS控制台 → EC2 → 实例 → 选择你的实例
- 底部面板 → 安全标签页 → 点击安全组链接
- 编辑入站规则 → 添加规则:
- 类型:自定义TCP
- 端口:(或你的应用端口)
3000 - 来源:(任何IPv4地址)
0.0.0.0/0
- 保存规则
GCP: VPC网络 → 防火墙 → 创建规则 → 允许TCP访问你的端口
Azure: 网络安全组 → 入站安全规则 → 添加规则
无云控制台权限? → 使用隧道(下一章节)
Find Your Public IP
查询公网IP
bash
curl ifconfig.mebash
curl ifconfig.meIf 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
undefinedInstall
安装
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.runPrints a URL. No time limit on free tier.
https://xxx.localhost.runbash
ssh -R 80:localhost:3000 nokey@localhost.run会输出一个格式的链接。免费版无时间限制。
https://xxx.localhost.runOption 3: Pinggy (No install, no account)
方案3:Pinggy(无需安装,无需账户)
bash
ssh -p 443 -R0:localhost:3000 a.pinggy.ioFree 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 for setup — requires a Cloudflare account and domain.
references/cloudflare-tunnel.md如需设置,请查看——需要Cloudflare账户和域名。
references/cloudflare-tunnel.mdTunnel Troubleshooting
隧道故障排查
| Error | Cause | Fix |
|---|---|---|
| App not running on that port | Check |
| Browser cache from old app | Open in Incognito, clear cache |
| URL expired | Free tunnel timeout | Restart tunnel, use ngrok with account |
| Connection refused | App crashed | Check logs: |
| 错误信息 | 原因 | 解决方法 |
|---|---|---|
| 应用未在对应端口运行 | 查看 |
| 旧应用的浏览器缓存 | 使用无痕模式打开,清除缓存 |
| URL过期 | 免费隧道超时 | 重启隧道,使用带账户的ngrok |
| 连接被拒绝 | 应用崩溃 | 查看日志: |
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 filebash
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 processesbash
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-appbash
cd project-folder
git pull
npm install # 如果依赖有变更
npm run build # 如果需要构建步骤
pm2 restart my-appAutomated (GitHub Actions)
自动化更新(GitHub Actions)
For auto-deploy on every :
See for a sample workflow that SSHes into the VPS and runs the above commands automatically.
git pushreferences/github-actions-deploy.md实现每次自动部署:
请查看获取示例工作流,该工作流可通过SSH连接到VPS并自动执行上述命令。
git pushreferences/github-actions-deploy.mdPhase 10 — Stopping / Shutting Down
第十阶段——停止应用/关闭服务器
bash
undefinedbash
undefinedStop 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 itbash
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 not just
0.0.0.0— some frameworks need127.0.0.1--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 / .
iptablesfirewall-cmd你使用的是Amazon Linux、Alpine或其他轻量发行版。防火墙由外部(云控制台)管理,或通过/管理。
iptablesfirewall-cmdBuild Fails with "Out of Memory"
构建失败,提示“内存不足”
bash
undefinedbash
undefinedNode.js — increase memory limit
Node.js — 增加内存限制
NODE_OPTIONS="--max-old-space-size=2048" npm run build
undefinedNODE_OPTIONS="--max-old-space-size=2048" npm run build
undefinedCannot sudo
on the Server
sudo服务器上无法使用sudo
sudoYou may not have sudo rights. Try the command without , or ask your server admin to add your user to the sudoers group.
sudo你可能没有sudo权限。尝试不使用执行命令,或联系服务器管理员将你的用户添加到sudoers组。
sudo