news 2026/11/5 3:09:29

云服务器上部署Web服务:Nginx反向代理、HTTPS与进程守护

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
云服务器上部署Web服务:Nginx反向代理、HTTPS与进程守护

本地跑得再顺的 webserver,一旦要放到公网上给别人访问,问题就会像串门一样一个接一个冒出来:进程半夜挂了、端口开了但外面连不上、域名解析了却打不开、HTTPS 证书过期了没人管、安全组规则写错把自己锁在外面。我这几年帮人做过不少“把本地项目搬到公网”的活,从个人小工具、内部管理系统,到给朋友做的展示型站点,绕来绕去其实都是同一套东西:一台云服务器(弹性计算实例或轻量应用服务器这类)、一个能常驻的进程管理方案、一层反向代理、一份能让外部访问的域名与证书配置,再加上一套不至于让自己裸奔的安全策略。下面这篇就把这套流程完整拆一遍,从选机器到上线验证,把参数计算、配置细节和踩过的坑都摊开讲,适合刚把项目在本地跑通、准备第一次上公网的朋友,也适合已经上线但总觉得“哪里不太对劲”的人对照着自查。

1. 部署方案的整体设计与选型考量

1.1 从“本地能跑”到“公网可访问”到底差了什么

很多人对部署的理解停留在“把代码扔上去,跑起来就行”,但本地环境和公网环境之间隔着好几层东西,每一层都可能成为拦路虎。本地开发时,你是用localhost:3000或者127.0.0.1:8000访问的,这个地址只有你自己这台机器认识;一旦别人要从外部访问,请求会先经过 DNS 解析、公网路由、云厂商的网络边界(安全组)、操作系统的防火墙、监听地址绑定,最后才落到你的应用进程上。这条链路上任何一环没打通,表现都是“打不开”,但原因可能天差地别。

我习惯把这套链路的差异归纳成四类:可达性差异(外网能不能找到你)、持续性差异(进程能不能一直活着)、安全性差异(暴露在公网后攻击面有多大)、可维护性差异(出问题你怎么知道、怎么修)。本地开发完全不用考虑前两项,第三项压根不存在,第四项靠 IDE 的终端日志就够了。上了公网,这四项全都要重新设计一遍。

所以我会在动手之前先把这几个问题回答清楚:这个服务是给自己用还是给别人用?访问量大概什么量级?有没有需要长期保存的数据?能不能接受几分钟的停机?这些答案直接决定了后面选什么规格的机器、要不要上数据库、要不要做备份、日志留多久。很多人跳过这一步直接买机器,结果要么买大了浪费钱,要么买小了天天被 OOM 杀掉进程。

1.2 服务器规格、系统镜像与带宽怎么选

规格选择这块,我的经验是先按内存卡下限,再按带宽卡上限。绝大多数 web 服务是内存敏感型而不是 CPU 敏感型:一个 Node 进程空跑也要占 80-150MB,Python 的 gunicorn 每个 worker 大概 60-120MB,再加上系统本身占的 200MB 左右,2GB 内存基本就是“能跑但不宽裕”的分界线,1GB 在装完运行时和反向代理之后经常剩不下什么,稍微来点并发就开始用 swap,swap 一上,响应时间立刻拉胯。

给个粗略的估算方法:假设你用 Nginx 做反向代理,应用进程开 2 个 worker,每个 worker 峰值内存 150MB,那么内存下限 ≈ 系统 200MB + Nginx 30MB + 应用 300MB + 缓冲余量 300MB ≈ 850MB,取整到 1GB 是紧巴巴的,2GB 才舒服。CPU 方面,这类小站 2 核足够,1 核也能跑,只是编译依赖、打包、证书签发这些操作会慢一些。

带宽是最容易被低估的一项,也是最容易产生额外费用的地方。这里有个换算要记牢:1Mbps 带宽的理论下载速度是 128KB/s,实际跑起来扣掉协议开销大概 110-120KB/s。假设你的页面首屏资源(HTML + CSS + JS + 图片)总共 500KB,那么在 1Mbps 带宽下,一个用户完整加载需要 4 秒多,而且这 4 秒内带宽是被占满的,第二个人同时访问就得排队。所以如果页面做了压缩、图片控制在 100KB 以内、总资源 200KB 左右,1-3Mbps 的固定带宽撑住每天几百次访问没问题;如果页面里有大图、视频、或者要提供文件下载,那按流量计费或者直接用对象存储放静态资源会更划算。

场景类型建议内存建议 CPU带宽策略说明
个人工具 / 演示站2GB2 核固定 1-3Mbps访问量低,静态资源少,固定带宽成本可控
内部管理系统2-4GB2 核固定 3-5Mbps并发用户十几个,注意数据库内存占用
含图片/附件上传的站点4GB2 核按流量计费 + 对象存储附件放对象存储,别让服务器扛流量
有持续写入的业务4GB+2-4 核固定 5Mbps 起需要额外规划数据盘和备份

系统镜像我一般直接选长期支持版本的 Linux 发行版,Ubuntu LTS 或者 Debian 稳定版都行,社区资料多、软件源新、出问题好搜。不建议为了“熟悉”选桌面版镜像,服务器上多一个图形界面就是多几百 MB 内存和一堆用不上的服务,纯属给自己增加攻击面。

注意:买机器时如果选了“按量付费”的带宽,一定去控制台把带宽上限和费用告警设好,我就见过有人忘了设上限,被一波异常流量刷出一个月饭钱的账单。

1.3 部署拓扑与安全边界的设计原则

拓扑这块,小项目最容易犯的错是“把应用端口直接暴露到公网”。比如 Node 服务监听 3000,就去安全组开 3000 端口,然后让用户用http://IP:3000访问。这样能通,但代价是:没有 HTTPS、没有统一的访问日志、应用直接面对公网请求(框架层面的漏洞会被直接打到)、以后想换端口或者加个路径前缀就得通知所有人。更麻烦的是,很多框架的开发服务器在生产模式下压根不该用,性能和安全都不达标。

我推荐的拓扑是三层收敛:公网只开放 80 和 443(以及一个改过端口的 SSH),Nginx 监听这两个端口做反向代理,应用进程只监听127.0.0.1:端口,完全不对外。这样做的收益很实在:请求进来先过 Nginx,限流、缓存、压缩、日志、证书全在这一层解决;应用挂了换个端口重启,外部完全无感;应用即使有漏洞,攻击者也得先突破 Nginx 这一层;将来加第二个服务,只是多一个location的事。

公网用户 | (80 / 443) v 云厂商安全组(只放行必要端口) | v 系统防火墙(与安全组规则保持一致) | v Nginx 反向代理(TLS 终止、静态资源、限流、日志) | v (127.0.0.1:3000,仅本机可达) 应用进程(由 systemd 守护)

这个结构还有个隐性好处:它逼着你把配置和应用解耦。应用不需要知道自己跑在哪个域名下,也不用处理证书,这些都在 Nginx 层配置。以后从一台机器迁到另一台、或者加个负载均衡,改动量都很小。

2. 服务器初始化与运行环境搭建

2.1 首次登录与最小权限账号配置

新机器到手第一件事不是装环境,是把登录这件事弄稳妥。控制台给的初始方式通常是密钥对或者初始密码,不管哪种,第一步都要创建一个普通用户,后续所有操作都用它,需要提权时再sudo。原因很直白:root 是所有人的第一猜测对象,直接拿 root 对外,等于把用户名这一半的信息白送出去。

# 以 root 或初始账号登录后执行 adduser deploy usermod -aG sudo deploy # 把公钥复制过去,实现免密登录 mkdir -p /home/deploy/.ssh chmod 700 /home/deploy/.ssh cp /root/.ssh/authorized_keys /home/deploy/.ssh/ chmod 600 /home/deploy/.ssh/authorized_keys chown -R deploy:deploy /home/deploy/.ssh

搞定之后先别急着关掉当前会话,另开一个终端窗口用新账号登录验证一遍,确认能上去、能sudo,再去动 SSH 配置。这个习惯救过我两次,一次是密钥文件权限不对导致验证失败,一次是配置写错把端口改成了被占用的值,因为老会话还开着,才不至于彻底进不去。

登录上去之后顺手做几件小事:timedatectl set-timezone Asia/Shanghai把时区调对(日志时间差 8 小时排查起来非常难受)、hostnamectl set-hostname web-prod-01设置主机名(多台机器时一眼能分清)、apt update && apt upgrade -y把系统补丁打一遍。这些都不是必须的,但做完之后整个环境会“干净”很多。

2.2 系统基础组件与软件源优化

接下来是把常用的基础工具装上。不要小看这一步,很多排查问题的关键时刻,就是靠curl、netstat、dig、vim这些工具救场。

sudo apt install -y curl wget git vim unzip htop \ net-tools dnsutils ufw fail2ban ca-certificates gnupg

net-tools提供netstat,dnsutils提供dig和nslookup,fail2ban后面做 SSH 防爆破要用。有些精简镜像默认没装curl,连安装脚本都跑不了,先补上这些没坏处。

软件源方面,默认源有时候速度不理想,可以换成地理位置更近的镜像源。但这里有个坑要提醒:换源之后一定要再跑一次apt update,确认没有 GPG 报错。我遇到过一次换了源但密钥没同步,结果后续所有安装都失败,报错信息还很绕,排查了半小时才发现是源的问题。

安全更新这块,我建议开启自动安全更新,只更新安全补丁,不升级大版本。这样做的好处是不用天天盯着,坏处是偶尔会在半夜自动重启服务,所以如果你跑的是对重启敏感的服务,就改成手动检查、定期维护窗口更新。

sudo apt install -y unattended-upgrades sudo dpkg-reconfigure -plow unattended-upgrades

2.3 运行时环境的安装与版本固定

运行时安装最忌讳的是“随手装个最新的”。版本漂移是线上事故的常见源头:本地 Node 18 跑得好好的,服务器装了个 22,某些依赖的 ABI 变了,或者某个 API 行为改了,报错信息还跟版本毫无关系。我的做法是显式固定版本,并且在项目里写清楚,让本地和线上保持一致。

以 Node 为例,用版本管理工具或者官方源都行,关键是记下版本号:

# 使用 NodeSource 源安装指定大版本 curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt install -y nodejs node -v && npm -v

Python 项目的话,系统自带的 Python 版本往往偏旧,建议用pyenv或者uv管理,虚拟环境一定要建,不要往系统 Python 里装包,不然哪天系统工具升级依赖冲突了,apt都能给你弄坏。

sudo apt install -y python3 python3-venv python3-pip cd /opt/myapp python3 -m venv venv source venv/bin/activate pip install -r requirements.txt

装完之后做一次冒烟测试,确认运行时能正常起一个最小服务。这一步看似多余,但能提前把“缺系统库”“动态链接库版本不对”这类问题暴露出来,比等到部署完再排查省事得多。

2.4 代码上传与目录结构规划

代码怎么传到服务器,取决于你的工作流。用 Git 是最省心的,直接在服务器上git clone,以后更新就是git pull。私有仓库的话,建议用部署密钥(deploy key),只给读权限,比把个人账号的密钥放服务器上安全得多。

目录结构上,我习惯用/opt/放应用代码,/var/log/放日志,/etc/放配置,/var/www/放静态文件。这么分的原因是符合 Linux 的惯例,将来别人接手或者你自己半年后再看,一眼就知道东西在哪。

sudo mkdir -p /opt/myapp sudo chown -R deploy:deploy /opt/myapp git clone git@example.com:team/myapp.git /opt/myapp sudo mkdir -p /var/log/myapp sudo chown -R deploy:deploy /var/log/myapp

依赖安装要在部署目录内做,别用全局安装。npm ci比npm install更适合线上,因为它严格按 lock 文件装,不会因为解析出新的小版本导致行为不一致。装完跑一次构建(如果有构建步骤),确认产物生成在预期位置。

提示:.env这类配置文件不要提交到仓库,在服务器上单独创建,权限设成600,属主是运行应用的用户。数据库密码、密钥这些泄露一次就够喝一壶的。

3. Web 服务的常驻运行与进程守护

3.1 为什么不能直接 nohup 或开个 screen

我见过太多人用nohup node app.js &或者开个screen会话就跑上线了。这样确实能让进程在终端断开后继续活着,但问题在于:机器重启之后它不会自动起来,进程崩溃之后它不会自动拉起,日志没有任何轮转,出问题你只能靠手动ps去找。作为临时调试手段没问题,作为生产方案是不合格的。

进程守护要解决的核心问题有三个:自动重启(崩溃后几秒内拉起)、开机自启(服务器重启后无需人工干预)、日志归一(标准输出和错误输出有统一去处,能配合日志轮转)。这三件事 systemd 全都能做,而且是系统自带的,不用额外装 supervisor 之类的工具。

3.2 systemd 服务单元的完整配置

下面这份配置是我用得最多的模板,改改路径和命令就能用:

# /etc/systemd/system/myapp.service [Unit] Description=My Web Application After=network.target Wants=network-online.target [Service] Type=simple User=deploy Group=deploy WorkingDirectory=/opt/myapp Environment="NODE_ENV=production" Environment="PORT=3000" EnvironmentFile=-/opt/myapp/.env ExecStart=/usr/bin/node /opt/myapp/dist/server.js Restart=always RestartSec=3 StandardOutput=append:/var/log/myapp/app.log StandardError=append:/var/log/myapp/error.log # 安全加固 NoNewPrivileges=true PrivateTmp=true ProtectSystem=full ProtectHome=true [Install] WantedBy=multi-user.target

几个参数值得展开说。Restart=always是核心,进程无论因为什么原因退出都会被拉起;RestartSec=3是重试间隔,别设太短,否则一个启动就崩的程序会疯狂重启把 CPU 打满;EnvironmentFile前面加个-表示文件不存在也不报错,避免因为漏了配置文件导致服务起不来;Wants=network-online.target配合After能减少“开机时网络还没就绪就启动然后失败”的概率。

安全加固那几行看起来不起眼,作用不小。NoNewPrivileges=true阻止进程通过 setuid 提权,PrivateTmp=true给进程一个独立的/tmp,ProtectSystem=full把/usr、/boot、/etc挂成只读(你自己的/opt目录不受影响),ProtectHome=true让进程看不到/home下的东西。这些配置基本不影响正常业务,但能显著降低被拿下之后的破坏半径。

启用流程:

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

enable和start是两件事,enable管开机自启,start管现在就跑,两个都要做。改完配置之后必须daemon-reload,不然 systemd 读的还是旧配置,这个坑我踩过不止一次,改了配置重启服务发现没生效,折腾半天才想起来。

3.3 日志管理与轮转策略

日志无限增长是老生常谈的问题,但真正出事的时候往往是磁盘满了才发现。一个高频报错的服务,一天写几个 G 的日志完全可能,20G 的系统盘撑不了几天。

两种做法,一是交给 systemd 的 journald 管理,用journalctl查;二是指定文件输出,用 logrotate 切割。我更倾向第二种,因为排查的时候tail -f和grep更顺手,也能直接把日志文件下载下来分析。

# /etc/logrotate.d/myapp /var/log/myapp/*.log { daily rotate 14 compress delaycompress missingok notifempty copytruncate }

copytruncate这个参数很关键:它先复制一份日志文件再清空原文件,不需要程序重新打开文件句柄,适合那些不自己处理信号的应用。如果你用的是create模式(重命名后新建),程序必须支持收到信号后重开日志,否则会继续往那个已经被重命名的文件里写,新日志永远是空的。

rotate 14表示保留 14 份,按天切割就是两周。实际保留多久看你的排查需求和磁盘大小,但至少留 7 天,因为很多问题不是你当天就能发现的,等用户反馈过来可能已经过去三四天了。

4. 反向代理、域名解析与 HTTPS 配置

4.1 Nginx 反向代理的完整配置文件

Nginx 装好之后,配置不要直接改主文件,而是在sites-available里新建一个站点配置再软链过去,这样管理多个站点的时候清爽很多。

# /etc/nginx/sites-available/myapp.conf server { listen 80; listen [::]:80; server_name example.com www.example.com; location /.well-known/acme-challenge/ { root /var/www/certbot; } location / { return 301 https://$host$request_uri; } } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example.com www.example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; add_header X-Content-Type-Options nosniff; add_header X-Frame-Options SAMEORIGIN; add_header Referrer-Policy strict-origin-when-cross-origin; client_max_body_size 20m; gzip on; gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml image/svg+xml; gzip_min_length 1024; gzip_comp_level 5; access_log /var/log/nginx/myapp.access.log; error_log /var/log/nginx/myapp.error.log warn; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_connect_timeout 10s; proxy_read_timeout 60s; proxy_send_timeout 60s; } location /static/ { alias /opt/myapp/public/static/; expires 7d; add_header Cache-Control "public, immutable"; access_log off; } }

几个地方值得细说。proxy_set_header那四行是必须的,缺了X-Forwarded-For,你的应用日志里记录的全是127.0.0.1,完全没法做访问分析;缺了X-Forwarded-Proto,应用判断不出原始请求是 HTTP 还是 HTTPS,生成的重定向链接可能是错的。Upgrade和Connection这两行是为了 WebSocket 支持,如果你的应用有实时推送、在线状态之类的功能,缺了这两行连接会握手失败,而且报错信息很不直观。

client_max_body_size默认只有 1MB,文件上传超过 1MB 直接返回 413,这个报错在应用日志里看不到,只在 Nginx 的错误日志里有一行,非常容易被误判成后端问题。所以我一般直接按业务上限设一个明确的值。

gzip对文本类资源效果显著,JSON 接口响应 gzip 之后通常能省 60-70% 的流量,对带宽紧张的小机器帮助很大。但图片、视频这些已经是压缩格式的,再 gzip 只是浪费 CPU,所以gzip_types里不要加。

配置写完先测语法,再重载,别直接 restart:

sudo nginx -t sudo systemctl reload nginx

reload是平滑重载,正在处理的请求不受影响;restart会断掉所有连接。线上环境养成用reload的习惯。

4.2 域名解析与接入前的准备工作

域名这块,第一步是把 A 记录指向服务器的公网 IP。解析生效有延迟,通常几分钟到几小时不等,取决于 TTL 设置。调试阶段可以先把 TTL 改成 60 秒,上线稳定之后再改回 600 秒以上,这样以后换 IP 的时候切换更快。

解析是否生效用dig验证,不要只信浏览器:

dig example.com +short dig www.example.com +short

如果返回的不是你配置的 IP,先别怀疑服务器,八成是解析还没生效或者本地 DNS 缓存的问题。可以换一个公共 DNS 再查一次,用dig @8.8.8.8 example.com +short这样的写法,能快速判断是解析问题还是本地缓存问题。

还有一件容易被忽略的事:域名要在境内服务器上正常对外提供访问,通常需要先完成实名认证和接入相关的合规手续,这是个纯流程性的工作,跟技术没关系,但需要时间。我的建议是买域名和买服务器放在同一天做,把材料一次性准备好提交,等手续跑完的这段时间正好用来搭环境、调配置,两边并行,不浪费时间。很多人是环境都搭好了才发现域名还不能用,干等着。

4.3 免费证书的申请与自动续期

HTTPS 现在基本是标配,浏览器对 HTTP 站点的提示越来越明显。好在免费证书的申请已经完全自动化了,用certbot几分钟就能搞定。

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

它会自动改 Nginx 配置、自动完成验证、自动配置续期。执行完可以测试一下续期流程是否正常:

sudo certbot renew --dry-run

这一步很重要,很多人只管申请不管续期,三个月后证书过期,浏览器直接红屏警告。--dry-run会模拟一次续期,把可能的问题(比如验证路径被拦截、80 端口不通)提前暴露出来。certbot 装完之后会自动创建定时任务,每天检查两次,剩余 30 天内自动续期,正常情况下不需要你操心。

注意:续期验证默认走 80 端口的 HTTP 挑战,如果你的安全组或者防火墙把 80 关了,续期就会失败而且不会有任何提示,等发现的时候证书已经过期了。所以要么保持 80 端口对特定路径可达,要么改用其他验证方式。

5. 安全加固与访问控制策略

5.1 安全组与系统防火墙的双层控制

这里有个概念一定要分清:云厂商的安全组和系统防火墙是两套独立的规则,两套都要通才能访问。安全组在云平台网络层生效,流量压根到不了你的机器;系统防火墙在操作系统层生效,流量到了机器门口被拦下。我遇到过配置了系统防火墙但忘了改安全组的情况,也遇到过反过来,两边对了半天才发现另一边没开。

原则是两边规则保持一致,只开必要端口。最小集合大概是:

端口协议来源用途是否必须
22TCP你的固定 IP(如果可能)SSH 管理必须,建议限制来源
80TCP0.0.0.0/0HTTP 跳转 + 证书验证必须
443TCP0.0.0.0/0HTTPS必须
应用端口TCP仅本机内部通信不对外开放

系统防火墙用ufw的话配置很简单:

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 sudo ufw status verbose

执行ufw enable之前一定要确认 22 端口已经放行,否则 SSH 立刻断掉,只能去控制台用 VNC 救。这个操作我建议在控制台开着的情况下做,心里踏实。

5.2 SSH 加固的几项关键配置

SSH 是暴露在公网上最危险的入口,全世界都在扫描。默认配置下,每天几千次爆破尝试是常态。加固主要做三件事:禁密码、改端口、上防爆破。

# /etc/ssh/sshd_config.d/99-hardening.conf Port 2222 PasswordAuthentication no PermitRootLogin no PubkeyAuthentication yes MaxAuthTries 3 LoginGraceTime 20 ClientAliveInterval 300 ClientAliveCountMax 2

改端口的作用不是“更安全”,而是把日志里的噪音降低 95%,让你能一眼看出真正的异常尝试。这个是从业者的共识:改端口不增加实质安全性,但显著提升可维护性。

禁密码登录是真正的关键项。只要密码登录开着,弱密码就是早晚的事。改用密钥登录之后,爆破就彻底没戏了。配置改完先sudo sshd -t测语法,再用新端口开一个新会话验证,确认没问题再关旧会话。

防爆破用 fail2ban,它监控日志,发现同一个 IP 连续失败多次就临时封禁:

# /etc/fail2ban/jail.local [sshd] enabled = true port = 2222 maxretry = 5 findtime = 600 bantime = 3600

maxretry = 5表示 10 分钟内失败 5 次就封 1 小时。这个阈值不要设得太低,自己手抖输错几次被自己封了就尴尬了。

5.3 常见攻击面的收敛与日常检查

除了 SSH,还有几个地方经常被忽视。

应用层的管理后台。如果你有/admin这类路径,别直接对公网开放。最简单的办法是在 Nginx 层加一层 IP 白名单:

location /admin/ { allow 203.0.113.10; deny all; proxy_pass http://127.0.0.1:3000; }

接口的限流。Nginx 自带限流模块,几行配置就能挡住基本的刷接口:

limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; location /api/ { limit_req zone=api burst=20 nodelay; proxy_pass http://127.0.0.1:3000; }

10r/s 的意思是每个 IP 每秒 10 个请求,burst 20 表示允许瞬时 20 个的突发。对小站来说这个阈值足够宽松,不会误伤正常用户,但能挡住大部分脚本化的恶意请求。

日常检查清单。我习惯每周花五分钟看几件事:df -h看磁盘(日志和数据最容易撑爆)、free -m看内存(有没有异常增长)、systemctl status看服务状态(有没有意外重启)、tail -100看错误日志(有没有新的异常)。这几项花不了几分钟,但能在问题变大之前发现苗头。

6. 上线验证与故障排查实录

6.1 分层排查:从外到内的定位方法

访问不通的时候最忌讳乱试,一通瞎改往往把原本好的地方也弄坏了。我习惯按链路的顺序从外往里查,每一步都能明确排除一层。

第一层,解析是否正常。dig example.com +short,返回的 IP 对不对。不对就是 DNS 问题,跟服务器无关。

第二层,端口是否可达。从本地机器测:

# 测端口通不通(没有 nc 的话用 telnet) nc -zv example.com 443 nc -zv example.com 80 # 直接看 HTTP 响应头,不依赖浏览器 curl -I https://example.com curl -v https://example.com 2>&1 | head -30

curl -v能看到完整的握手过程,卡在哪一步一目了然:连接超时是网络层问题(安全组、防火墙),连接被拒是服务没监听,SSL 报错是证书问题,能连上但返回 502 是后端应用的问题。

第三层,服务是否在监听。在服务器上执行:

ss -lntp | grep -E '80|443|3000'

这里要重点确认应用的监听地址,如果是0.0.0.0:3000说明对全网开放(不符合我们的设计),如果是127.0.0.1:3000才是正确的。

第四层,本机能否访问。绕过所有外部因素,在服务器上直接请求:

curl -I http://127.0.0.1:3000 curl -I -H "Host: example.com" http://127.0.0.1

本机通而外网不通,问题一定在网络层或者 Nginx;本机都不通,那就是应用本身的问题,去看journalctl -u myapp -n 50或者应用的错误日志。

第五层,看日志。Nginx 的error.log里通常有直接答案,connect() failed (111: Connection refused)是后端没起,upstream timed out是后端太慢,no live upstreams是健康检查全挂了。

6.2 常见问题速查表

下面这些是我实际遇到过、并且被问得最多的,按现象整理成表格,方便对照:

现象最可能的原因验证方法解决方向
浏览器显示连接超时安全组未放行 / 防火墙拦截nc -zv IP 端口检查安全组和ufw status
连接被拒绝服务未启动 / 监听地址不对ss -lntp检查 systemd 状态和监听绑定
502 Bad Gateway后端进程挂了或端口不对curl 127.0.0.1:端口看应用日志,核对 proxy_pass
504 Gateway Timeout后端处理太慢看 Nginx error.log 耗时优化查询或调proxy_read_timeout
上传文件报 413client_max_body_size太小看响应码和 error.log调大该参数并 reload
静态资源 404路径映射错误 / 权限不足ls -l看目录权限检查alias结尾斜杠和属主
证书报过期自动续期失败certbot renew --dry-run检查 80 端口和验证路径
服务隔几天就挂内存不足被 OOM`dmesggrep -i oom`
日志时间差 8 小时时区未设置timedatectlset-timezone Asia/Shanghai
修改配置不生效忘了 daemon-reload / reload看服务启动时间daemon-reload+restart

6.3 几个印象深刻的踩坑记录

坑一:alias结尾的斜杠。静态资源 404 排查了两个小时,最后发现是location /static/配了alias /opt/myapp/public/static;,少了结尾的斜杠,导致路径拼接错位。alias和root的路径拼接规则不一样,alias是替换匹配部分,结尾斜杠必须和location保持一致,这个细节文档里写了但很容易看漏。

坑二:EnvironmentFile里的引号。.env文件里写了PASSWORD="abc#123",结果应用拿到的密码是被截断的。原因是部分解析器会把#当注释起始符,加上引号也没用。解决办法是密码里避开特殊字符,或者改用不同的配置加载方式。这类问题最恶心的地方是本地开发环境和线上表现不一致,因为本地可能压根没走同一个加载路径。

坑三:磁盘被 Docker 日志吃满。一个跑了三个月的服务突然全部挂掉,df -h显示 100%,查下来是容器日志没有任何限制,单个日志文件涨到了十几 G。教训是:任何会产生日志的组件,都要设上限。Docker 可以在 daemon 配置里设max-size和max-file,其他服务用 logrotate,总之不能放任。

坑四:reload之后新配置没生效。改了 Nginx 配置,nginx -t通过,reload也执行了,但行为没变。最后发现是同一个server_name在另一个配置文件里也定义了,Nginx 按字母序加载,实际生效的是另一个。排查这类问题的办法是nginx -T,它会打印合并后的完整配置,能直接看到最终生效的内容。

坑五:改了 SSH 端口但安全组没加。这个最典型,改完端口重启 sshd,当前会话还连着所以没感觉,下次登录就连不上了。只能去控制台用网页终端登进去改回来。所以改 SSH 端口的时候,先加安全组规则,再改配置,最后验证,顺序不能乱。

6.4 上线后第一周的观察重点

服务刚上线那几天是最关键的观察期,很多问题只在真实流量下才会暴露。我会重点盯这几个指标:响应时间的变化趋势(有没有随时间变慢,可能是内存泄漏或连接池耗尽)、错误率的分布(是集中在某个接口还是全局)、资源的增长曲线(内存是不是只涨不降)、访问来源的异常(有没有大量来自单一 IP 的请求)。

如果条件允许,配一个简单的监控比什么都强。不需要多复杂的方案,一个定时脚本每分钟记录一次内存、磁盘、响应码分布,写到文件里,出问题的时候至少有历史数据可查。我吃过没有监控的亏:服务凌晨三点挂了,早上七点才发现,中间四个小时的日志全是空的,完全不知道挂之前发生了什么。

7. 长期运维的成本与可维护性考量

7.1 带宽、流量与费用的控制思路

云服务器的费用结构里,带宽往往比机器本身还贵。固定带宽包月看起来简单,但如果选了 10Mbps 而实际只用 1Mbps,那 90% 的钱是白花的;反过来选小了,高峰期卡顿又影响体验。

我的做法是先按流量计费跑两周,看真实的流量曲线,再决定要不要改成固定带宽。同时做几件事把流量压下来:静态资源走对象存储(不占服务器带宽,单价还便宜)、开启 gzip、给静态资源设置长缓存(Cache-Control: max-age=604800)、图片统一压缩到合适尺寸。这四项做完,同等访问量下的带宽消耗通常能降一半以上。

另外要留意出方向流量和入方向流量的计费差异,绝大多数场景是出方向收费、入方向免费,所以上传文件不心疼,下载和页面访问才心疼。

7.2 数据备份与故障恢复的最小方案

备份这件事,没出事的时候觉得多余,出了事就是救命。最小可用的方案是数据库每日全量导出 + 异地存储,脚本很简单:

#!/bin/bash # /opt/scripts/backup.sh set -euo pipefail DATE=$(date +%Y%m%d) BACKUP_DIR=/opt/backup mkdir -p "$BACKUP_DIR" # 数据库导出(按实际数据库类型调整) pg_dump -U appuser appdb | gzip > "$BACKUP_DIR/appdb-$DATE.sql.gz" # 只保留最近 7 天 find "$BACKUP_DIR" -name "appdb-*.sql.gz" -mtime +7 -delete # 上传到对象存储(用厂商提供的命令行工具) # ossutil cp "$BACKUP_DIR/appdb-$DATE.sql.gz" oss://my-backup/

配到 crontab 里每天凌晨跑一次。关键在于必须验证恢复流程,我见过备份跑了半年,真要恢复的时候发现导出的文件是空的,因为数据库密码改了脚本一直失败但没人看日志。所以每隔一两个月,挑一份备份实际恢复到测试环境跑一次,确认能用。备份不做恢复验证,等于没备份。

配置文件的备份同样重要。/etc/nginx、/etc/systemd/system、.env这些改了之后没有版本记录,一旦机器挂了重建起来全靠记忆。我的做法是把这些配置也放进一个私有仓库,每次改动提交一次,注释写清楚改了什么、为什么改。

7.3 后续扩展的几个方向

如果这个服务后面要长起来,有几个扩展点值得提前想清楚。

应用多实例。当单进程扛不住的时候,最直接的办法是起多个进程监听不同端口,Nginx 里配upstream做负载均衡。这时候应用必须是无状态的,session 要么存 Redis,要么放加密 cookie,不能存在进程内存里。

静态资源分离。把图片、CSS、JS 全部迁到对象存储加 CDN,服务器只处理动态请求。这一步做完,服务器的带宽压力和 CPU 压力都会明显下降,是小站性价比最高的优化之一。

数据库独立。应用和数据库挤在一台机器上,初期没问题,但数据库的内存需求会随数据量增长,跟应用抢内存的情况迟早出现。数据量到一定规模之后,把数据库拆到独立实例上,应用机器只负责计算,扩容会简单很多。

日志集中化。机器多了之后,一台台登上去看日志不现实。可以在每台机器上装一个采集 agent,把日志汇总到一个地方统一检索。这个不用一开始就上,两三条机器以内还是直接 SSH 更快。

这套流程我从第一次摸索到现在,前后改过好几版,每版都是被坑出来的。最开始我也干过直接开应用端口、用 root 跑服务、日志不管不顾的事,后来每次出事都补一块,慢慢就成了现在这个样子。如果你刚开始做这件事,不必一次把上面所有内容都配齐,先把“进程守护 + 反向代理 + HTTPS + 安全组”这四件基础的事做对,服务就能稳定跑起来,剩下的加固和优化可以边跑边补。真正会让人手忙脚乱的,从来不是配置写得多复杂,而是某个半夜服务挂了,你却不知道从哪儿开始查。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/5 3:08:30

校园网自动登录脚本:从抓包到自启的完整指南

校园网自动登录这个需求,几乎每个住校的同学都动过念头。每次开机、每次休眠唤醒、每次手机切回Wi-Fi,都要重新打开浏览器、等认证页面加载、输账号密码、点登录,一套流程下来少说半分钟,一天重复七八次,烦得很。我最早…

作者头像 李华
网站建设 2026/11/5 3:05:49

Git工程化实践:从提交规范到自动化部署

1. 从混乱到秩序:为什么我们需要Git工程化体系在我参与过的十几个后端项目中,发现一个普遍现象:90%的团队都拥有完整的工具链,但只有不到30%真正发挥了工具的价值。上周排查一个线上事故时,我们花了整整4小时才定位到问…

作者头像 李华
网站建设 2026/11/5 3:05:19

Univer 开源在线表格:Canvas 高性能渲染与插件化架构实践

1. 从一次团队踩坑说起:为什么我们需要 Univer今年年中,我们团队接到一个内部中台改造需求:把原来基于老旧前端表格组件、一次最多只能渲染几千行数据的管理系统,升级成能扛住十几万行业务数据、还要支持多人同时编辑的在线表格。…

作者头像 李华
网站建设 2026/11/5 3:04:44

AI情感支持悖论:为何人类更倾向真人安慰

1. 研究背景与核心发现2023年《Nature》期刊发表的一项跨学科研究揭示了人机交互中一个耐人寻味的矛盾现象:当受试者面临压力情境时,虽然客观评估显示AI生成的情感支持内容在同理心表达、问题分析准确性等维度上显著优于人类(平均得分高出23%…

作者头像 李华
网站建设 2026/11/5 3:04:15

并发控制中的悲观锁与乐观锁技术解析

1. 并发控制的核心矛盾在多线程或分布式系统中,当多个操作同时访问同一数据资源时,如何保证数据一致性成为关键挑战。我处理过的一个电商库存系统案例中,就曾因并发控制不当导致超卖——明明数据库显示库存充足,实际发货时却发现商…

作者头像 李华
网站建设 2026/11/5 3:02:10

Redis版本选择与多平台安装指南

1. Redis简介与版本演进Redis作为当下最流行的开源内存数据库,其版本迭代历程反映了技术发展的清晰脉络。从2009年Salvatore Sanfilippo发布首个版本至今,Redis已经经历了从简单键值存储到多功能数据系统的蜕变。目前官方维护的主要版本线包括&#xff1…

作者头像 李华