云服务器部署全攻略:从Linux初始化到Nginx反向代理
2026/9/20 3:54:42 网站建设 项目流程

1. 写在前面:雪山下服务器的锅,你背过几个?

云端服务器这码事,估计不少朋友是又爱又恨。爱在它随时可开、按量付费,恨在它一旦涉及到跑代码、做部署,层层叠叠的坑能把一个周日傍晚耗到凌晨两点。最近我在芯飞云(国内一家云服务商)上完整跑通了一个项目的上线流程,从拿到一台裸机Linux,到服务在公网被真实用户访问,前前后后踩了不少“经典款”的坑,今天干脆一篇讲清楚,希望能帮准备上云跑程序的朋友少走点弯路。

这篇文章不聊玄乎的架构设计,也不铺陈复杂的K8s全家桶,就聚焦一个非常实在的场景:你有一台芯飞云服务器,或者说任何一台云主机也行,你现在要让它跑起你的代码,并且最终让别人能通过域名或IP访问到。我会把从机器初始化、SSH连接、环境部署、用Docker跑前端项目、Nginx反向代理到上线后排查问题的整个链路,用“一场实际操作”的口吻完整复现一遍。适合刚接触服务器Linux命令行的新手,也适合正在纠结怎么把本地项目“扔”到云端的同学参考。

2. 服务器选型与初始化:拿到机器后前五分钟该做什么

2.1 先说下为什么选择芯飞云,以及我选了啥配置

很多人买云服务器只看价格,结果买完才发现CPU弱、带宽小,跑个编译任务卡到怀疑人生。我在芯飞云上选机器的时候主要考虑三点:新用户价格是否够低、是否提供弹性IP和独立带宽、售后工单能否快速响应。我这台机器的配置是4核8G、40G系统盘(SSD)、按固定带宽计费,实际跑了两个Node服务、一个Nginx、一个MySQL,资源占用还算健康。

这里多说一句,如果你只是跑个小Demo或者个人博客,2核4G其实完全够用;如果你要跑前端构建(比如Vite、Webpack打包),那内存最好上8G,不然编译到一半OOM(内存溢出)被系统杀掉进程,那叫一个绝望。

2.2 系统镜像怎么选:CentOS、Ubuntu还是Debian?

这块我发现新手特别容易卡住。买机器时会让你选“操作系统镜像”,我建议优先选择Ubuntu 22.04 LTS(长周期支持版)或者Debian 12,原因很简单:

  • 软件源更新及时,apt安装软件包很少遇到依赖冲突。
  • 社区资料多,遇到问题Google一下基本都是现成答案。
  • 不少云厂商的默认安全策略和内核优化对Ubuntu兼容性最好。

选CentOS的话,得注意CentOS 7已经停止维护了,继续用它意味着拿不到安全补丁,这是个大隐患。如果实在要用RHEL系,建议选Rocky Linux或AlmaLinux这类社区替代版。我这次在测试环境用的一台是从CentOS 7迁移到AlmaLinux之后才做生产用的,理由就是不再想“裸奔”在一个维护终止的系统上。

2.3 初始化四步走:改密码、开密钥、建用户、关Root

拿到机器之后,首次登录你会发现你是用root用户直接登录的。生产环境直接用root跑服务是运维大忌,原因不细说,光是安全审计就能让你头大。我一般会按下面四个步骤完成初始化:

  1. 用密码或者密钥登录后,先创建一个普通用户。
  2. 给这个用户添加sudo权限。
  3. 生成SSH密钥对,并把公钥放到普通用户的~/.ssh/authorized_keys里。
  4. 修改sshd配置文件,禁止root密码登录,只允许密钥登录。

具体命令可以这样操作:

# 创建普通用户并加入sudo组 adduser deploy usermod -aG sudo deploy # 切换到新用户 su - deploy # 生成密钥对(在本地电脑执行也行) ssh-keygen -t ed25519 -C "deploy@your-server" # 把公钥写入服务器 cat ~/.ssh/id_ed25519.pub | ssh deploy@你的服务器IP "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

修改SSH配置:

sudo vim /etc/ssh/sshd_config

把下面两项改掉:

PermitRootLogin no PasswordAuthentication no

然后重启SSH服务:

sudo systemctl restart sshd

提示:改完sshd之前一定要先开一个新终端测试能否正常登录,否则配置写错后原连接断了就麻烦了。我身边真有同事因为这个把自己锁在机器外面,最后只能去控制台重置密码。

2.4 安全组和防火墙:你连不上服务器的元凶往往在这里

很多人在云服务器控制台配了“允许所有端口”,然后自己装了个ufw防火墙又没放行SSH端口,结果就是怎么连都超时。这里要区分两层:云控制台的安全组服务器内部的防火墙。两层都要放行需要的端口,缺一个都可能导致服务访问不到。

我的习惯是,控制台安全组只放行必要的端口(22、80、443,以及一个自定义的SSH端口),服务器内部用UFW统一管理:

sudo apt update sudo apt install ufw -y sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable

提示:如果使用的是阿里云、腾讯云、芯飞云等国内云厂商,还需要注意备案问题。域名解析到国内服务器且通过80/443端口提供Web服务,原则上需要完成备案流程。用IP直接访问可绕过这一要求,但正式项目还是得正规操作。

2.5 GPU服务器运维为什么单独拎出来说

热词里出现了“GPU服务器运维”,如果你想在上面跑AI模型或数据处理任务,情况略有不同。GPU服务器的运维除了常规的CPU、内存、磁盘监控,你还要盯着显存占用、驱动版本和CUDA版本是否匹配。

我有一次在GPU实例上跑PyTorch训练,怎么都调不到GPU,查了半天才发现是CUDA版本和驱动不匹配。检查方法很简单:

nvidia-smi

如果提示No devices were found,那基本就是驱动没装好。安装驱动时优先用云厂商镜像市场里预装好的GPU镜像,省事很多。自己在裸系统上从零装NVIDIA驱动真的是苦差事,依赖和内核模块编译能折腾一整天。

3. 连上云端:本地到服务器的通路,怎么才算“稳”

3.1 终端工具选哪个:Windows、macOS各不相同

连接服务器的方式,按平台区分的话:

  • macOS/Linux用户:直接打开终端,ssh deploy@服务器IP就行。
  • Windows用户:比较推荐Windows Terminal + OpenSSH,或者用重量级一点的XshellFinalShell

如果你习惯用IDE写代码,那么VSCode连接SSH远程服务器这个玩法强烈推荐。装好Remote - SSH扩展,在VSCode里配置好服务器连接信息,就能直接在本地编辑远程文件,跑远程终端,配合代码高亮和Git集成,体验并不比本地开发差太多。

VSCode远程连接的配置方式很简单:按F1,输入“Remote-SSH: Connect to Host”,添加主机:

Host my-cloud-server HostName 你的服务器IP User deploy Port 22

它会要求你确认指纹,然后输入密码或者密钥文件的位置,就能连上了。连上之后在VSCode里安装“Remote - SSH: Editing Configuration Files”扩展,可以更舒服地改配置。

3.2 连不上服务器的经典问题与排查清单

我遇到过的“连不上”案例,按频率排序基本是:

  1. 安全组没放行22端口。
  2. 服务器内部防火墙(firewalld/UFW)没放行22端口。
  3. 使用了错误的端口(阿里云、腾讯云等默认有时是22,但有些人为了安全会改成别的)。
  4. 服务器CPU或负载过高,SSH服务响应极慢。
  5. 密钥文件权限不对,OpenSSH拒绝使用。

排查方法可以用telnet先测端口通不通:

telnet 你的服务器IP 22

不通就说明网络层或防火墙有问题,先从安全组查起。

3.3 提升SSH体验的几个小技巧

日常操作服务器,有一些能明显提升幸福感的小细节:

  • 启用SSH连接复用:在本地~/.ssh/config加一行ControlMaster auto,这样第二次连接不会重新握手,速度飞快。
  • 用tmux或者screen管理会话:跑训练或者长任务时,一旦SSH断开任务就断了。tmux可以让你先挂后台,断线重连后又回到之前所有窗口。
  • 配置SSH别名:不用记IP,直接用别名连。

我几乎每台服务器都跑一个常驻tmux会话,里面开着日志窗口、定时任务窗口、手动执行窗口,非常方便。建议所有人养成这个习惯。

4. 环境部署:把一台“裸机”变成一台“能跑程序的机器”

4.1 基础软件包与常用工具安装

系统装好后,第一件事是把更新和常用软件装上:

sudo apt update && sudo apt upgrade -y sudo apt install -y curl wget git vim htop unzip zip tree

如果要在上面跑Docker,先装依赖:

sudo apt install -y apt-transport-https ca-certificates curl software-properties-common

然后安装Docker(当前推荐用官方提供的安装脚本,省心):

curl -fsSL https://get.docker.com | bash

装完记得把用户加入docker组,不然每次都要加sudo:

sudo usermod -aG docker deploy newgrp docker

4.2 运行时环境:Node.js、Python、Java等语言环境怎么装最稳

现在云服务器上跑程序,大多数是直接用容器或者脚本装的运行时。以Node.js为例,不建议直接去官网下载压缩包,推荐用nvm管理版本,体验最好:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash # 重新打开终端或执行 source ~/.bashrc nvm install 20 nvm use 20

Python则推荐用pyenv或者直接使用系统自带的python3,如果是普通项目直接用venv虚拟环境就够了。

需要特别注意的是,不要直接在服务器上用npm install -g全局安装一堆包,一旦和系统依赖冲突,排查起来非常痛苦。项目内的依赖一律通过package.json锁定版本,部署时使用npm ci(严格按锁文件安装)。

4.3 如果要从裸机装数据库

跑项目多半要配数据库,国内用MySQL的居多。直接用Docker跑MySQL是最省心的方式之一:

docker run -d \ --name mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=你的密码 \ -e MYSQL_DATABASE=yourdb \ -v mysql_data:/var/lib/mysql \ mysql:8.0

这里做了数据卷挂在宿主机上,容器删了数据也还在。之前见过有同事没挂数据卷,跑了个docker rm直接把整个库给“物理删除”了,那个表情我现在还记得。

4.4 服务器时区与时间同步:你日志时间错乱了吗

这个看着小,却很影响排查问题。很多新手刚装完系统,发现日志里的时间和本地差8个小时,那就是时区没设对。正确做法:

sudo timedatectl set-timezone Asia/Shanghai sudo timedatectl set-ntp yes

另外,如果timedatectl提示时间没同步,可能是NTP服务没起来。国内服务器推荐使用国内NTP服务器地址:

sudo apt install systemd-timesyncd sudo timedatectl set-ntp yes

如果自己搭NTP服务,需要确认UDP 123端口在防火墙中是放行的。我用过国内时间服务器做客户端同步,效果很稳定,日志时间从此整整齐齐。

5. 跑程序:从“前台挂起”到“后台守护”,再到“容器化”

5.1 最朴素但容易踩坑的方式:nohup和&

新手阶段最容易犯的错是:直接在SSH终端里跑node server.js,看服务起来之后,激动地关掉电脑,第二天发现服务挂了。原因很简单,SSH断开后终端会话结束,子进程默认也收到SIGHUP信号退出。

麻烦的解决方式是用nohup

nohup node server.js > app.log 2>&1 &

nohup让进程忽略挂断信号,&让进程在后台执行。这样即使SSH断开,服务也能继续跑。但问题也随之而来:进程管理不优雅。你想看状态、想重启、想挂掉它,都得去手动找Pid,非常痛苦。一旦机器重启,所有进程都没了。

5.2 正式项目应该用systemd管理亲儿子进程

生产环境跑的东西,我强烈建议使用systemd来管理进程。系统会把你的服务当作一个“系统服务”来看待,崩溃自动重启、开机自启、日志统一交给journald,还支持简单的资源限制。

举一个Node.js服务的例子,在/etc/systemd/system/myapp.service中写:

[Unit] Description=My Node.js App After=network.target [Service] User=deploy WorkingDirectory=/home/deploy/myapp ExecStart=/home/deploy/.nvm/versions/node/v20.11.0/bin/node server.js Restart=always RestartSec=5 Environment=NODE_ENV=production [Install] WantedBy=multi-user.target

然后:

sudo systemctl daemon-reload sudo systemctl enable myapp sudo systemctl start myapp sudo systemctl status myapp

这样一来,开机自启、崩溃重启都搞定了。日志也不用自己写文件,直接journalctl -u myapp -f就能实时看。

注意:systemd的ExecStart里如果要用路径,记得写绝对路径。另外,不要用nohup去启动一个systemd服务,会让管理逻辑彻底混乱。

5.3 前端项目怎么用Docker部署上线

热词里有好几个前端部署相关关键词,这说明前端上云部署确实是个高频场景。传统前端理解的部署就是npm run build后把dist目录扔到一个目录里让Nginx访问。这个没错,但更好的做法是用Docker把整个环境都固定下来,避免“本地好好的,服务器上就白屏”这类环境差异。

一个最简的Dockerfile可以长这样:

# Stage 1: 构建 FROM node:20-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Stage 2: 运行 FROM nginx:1.25-alpine COPY --from=build /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]

构建镜像并启动容器的流程:

docker build -t my-frontend . docker run -d --name my-frontend -p 8080:80 my-frontend

这种多阶段构建的好处是,最终镜像只包含Nginx和构建产物,体积很小(一般几十MB),部署出去很快。前端项目如果要对接后端API,典型的做法是在Nginx配置里做反向代理,比如把/api前缀的请求转发到后端服务的某个端口。

5.4 方案对比:直接进程、systemd还是Docker

我把常见的方式汇总成一个表,方便你看场景选择:

方式优点缺点适用场景
nohup + &简单直接进程管理弱、重启即失效临时调试、本地小工具
systemd开机自启、崩溃重启、日志统一写服务文件需要学习生产环境的单体服务
Docker环境隔离、部署可复现、版本回滚方便额外学习曲线、镜像构建耗时微服务、前后端分离项目、多环境一致部署

我个人现在的习惯是:长期跑的服务一律先做成镜像,再通过Docker Compose组合起来;但某些单机小脚本或者需要直接读宿主机设备的应用,则用systemd。两者并不冲突,dpends on 场景。

6. 项目上线暴露公网:域名、Nginx与HTTPS

6.1 Nginx反向代理:为什么必须要有它

如果你的服务监听在某个随机端口,比如3000,用户访问时需要手动输入http://IP:3000,不仅不专业,而且浏览器还可能因为端口问题被拦截。更关键的是一台服务器不可能只跑一个服务,端口资源有限,所以要让它们在80/443端口后“各回各家”,就需要Nginx反向代理。

一个最基础的前端加后端分离配置如下:

server { listen 80; server_name your-domain.com; # 前端静态文件 root /var/www/html; index index.html; # 后端API代理 location /api/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 前端history路由回退 location / { try_files $uri $uri/ /index.html; } }

这个配置里有两个极易踩的坑:

  1. proxy_pass的URL结尾有没有/语义不同。有斜杠代表替换路径前缀,没有斜杠代表直接转发完整路径,配错了API就404。
  2. 前端路由用了HTML5 History模式时,必须配try_files回退到index.html,否则用户在子路由刷新页面就会出现404。

6.2 域名解析:A记录、CNAME和DNS生效时间

域名解析这块,买好域名后在DNS服务商控制台加一条A记录,主机记录填@或者www,记录值填服务器IP地址。TTL设短一点(比如600秒),方便调试时快速生效。

有几个小点:

  • 如果是国内服务器,域名必须已备案,否则80/443端口的访问会被云服务商拦截,此时页面会提示“该域名未备案”或直接无法访问。
  • 如果做一键部署工具,还可以在云服务商买DNS解析服务,API操作解析记录非常方便。
  • 配完解析用dig your-domain.com或者nslookup验证是否生效。

6.3 HTTPS证书:从免费到自动续期

网站没有HTTPS,浏览器会直接标“不安全”,尤其涉及到用户提交信息时体验很差。幸好现在有免费的HTTPS证书可以申请,最常用的是Let's Encrypt,配合certbot自动续期。

安装和配置:

sudo apt install certbot python3-certbot-nginx -y sudo certbot --nginx -d your-domain.com -d www.your-domain.com

Certbot会自动修改Nginx配置嵌入SSL证书路径并开启HTTP/2。之后它会自动续期证书,你的HTTPS基本就是“无限免费”。

这里有个亲手踩过的坑:域名解析刚生效时马上申请证书,有时候会报错“No valid IP addresses”,这说明DNS还没全球同步到位,等一会儿再跑一次就行。另外,记得把443端口在安全组放行,不然证书申请也可能失败。

7. 问题排查与运维实录:上线后最常遇到的几个幺蛾子

7.1 端口不通,但服务确实在跑

这里的经典场景是:你在服务器上通过curl localhost:3000能看到响应,但浏览器访问http://IP:3000就是超时。排查顺序:

  1. 检查云控制台安全组是否放行3000端口。
  2. 检查服务器内部防火墙:sudo ufw status
  3. 检查服务是不是监听的127.0.0.1还是0.0.0.0,如果是前者,外网访问不到。

最后这个特别容易被忽略。很多框架默认开发模式会监听localhost,你需要显式设置监听0.0.0.0。例如Node服务里server.listen(3000, '0.0.0.0'),或者Docker起容器时-p 3000:3000映射到宿主机。

7.2 进程莫名退出,日志啥都没有

之前遇到过Node服务跑着跑着消失了,排查时发现Node进程被OOM Killer杀了。查看方法:

dmesg | grep -i oom

如果看到内核日志里有对应进程的OOM记录,那基本就能确定是内存不足。解决办法有:

  • 调整服务内存限制:在systemd配置里加MemoryLimit=2G
  • 增加交换分区:sudo fallocate -l 4G /swapfile
  • 如果是Node/V8的老版本,也可能是内存泄漏导致长期占用后被杀。这时候要检查代码里是否有未释放的监听器或全局缓存。

7.3 Nginx 502/504:反向代理出问题怎么定位

Nginx返回502,常见原因有:

  • 后端服务没起来,或端口写错。
  • 后端服务监听的地址和proxy_pass不一致(比如后端只监听IPv6的::1,而Nginx连到了127.0.0.1)。
  • PHP-FPM进程数太少,并发一大全部排队,请求直接超时。

排查时先看后端日志:

journalctl -u myapp -n 50 --no-pager

再看Nginx错误日志:

tail -f /var/log/nginx/error.log

如果日志里写connect() failed (111: Connection refused) while connecting to upstream,那说明后端没在监听端口。如果写upstream timed out,那多半是后端处理太慢,需要调长proxy_read_timeout

7.4 服务器被扫、被攻击怎么办

公网服务器天天被动挨打是常态,我个人防线一般有这几条:

  • 修改SSH端口:把22改成非常端口,能减少90%的暴力扫描流量。
  • 启用Fail2Ban:自动封禁尝试多次密码登录失败的IP。
  • 关闭密码登录:只保留密钥登录。
  • 不要给普通用户随便配sudo权限,必要的时候再给。
  • 定期打安全补丁,至少每月一次。

之前评论区有人说“我小站没啥好攻击的”,这种想法要不得。很多扫描工具是全网段盲扫的,扫到22端口开着就试密码,祈求捡漏。把基础安全做了,能省去很多后续打地鼠的时间。

8. 一些琐碎但当时特别有用的经验

8.1 使用云平台导出镜像备份

已经配好环境、跑起来的机器,强烈建议在控制台手动做一次快照或导出镜像。我一般都会在完成所有基础部署后给系统盘打个快照,这样万一将来操作失误把环境搞坏了,一条命令级别就能回滚,不用重新折腾环境。

  • 时间不要选意外低峰期,快照期间不影响业务。
  • 保留最近两三份就行,不用搞太多浪费存储空间。

8.2 用Git同步代码,而不是用scp上传

每次都在本地scp dist/* root@IP:/var/www/html来部署,既容易漏文件,又没法回滚。正式项目最好用Git做版本管理,服务器端通过git pull拉取最新release分支,或者更先进一点,在CI(持续集成)里构建好镜像推到镜像仓库,服务器直接docker pull

我现在比较常用的最小可用流程是:

  1. 本地提交代码,推送远程仓库。
  2. SSH登录服务器,在项目目录执行git pull
  3. 重新build前端或者重启服务,完成发布。

这比scp稳定得多,至少能确认服务器上跑的代码来自哪个提交。

8.3 多个项目共用一个Nginx时怎么组织配置

当服务器上不止一个网站时,在/etc/nginx/sites-available/下为每个项目单独建一个配置文件,然后软链到/etc/nginx/sites-enabled/。每次改完配置先执行nginx -t校验语法,再systemctl reload nginx。这样做的好处是互不干扰,哪一个站点出了问题单独排查就行。

9. 最后再分享一个很有效的细节习惯

如果只让我挑一个最影响运维效率的小习惯,那就是给每台服务器起个名字,并维护好本地的SSH config,而不是记一堆IP。

举个我自己的本地~/.ssh/config

Host prod-web HostName 1.2.3.4 User deploy Port 22122 IdentityFile ~/.ssh/ida_ed25519 Host prod-db HostName 1.2.3.5 User deploy Port 22122 IdentityFile ~/.ssh/ida_ed25519

配合远程开发工具,我能在一个终端里快速切到任意服务器干活。部署时也是重复敲同样的几行命令,熟练到肌肉记忆。

另外,也建议把服务器日志目录统一约定成一个模式,比如/var/log/myapp/,脚本告警、排错时一下就能找到位置,省得每次都在服务器里“寻宝”。

上了云不等于万事大吉,服务器跑起来只是开始,后续的监控、备份、安全都是长期要做的功课。这篇从连上机器到项目上线,基本把我个人在两台芯飞云上实跑项目时踩过、修过的坑都过了一遍,希望能帮你少熬夜,多睡觉。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询