Web项目线上部署全流程:从静态站点到Spring Boot生产环境 1. 项目概述这不是“上传代码就完事”而是一场从开发机到生产环境的精密迁移“搭建网站——web项目线上部署”这九个字是无数开发者职业生涯里最常遇到、却最容易被轻视的坎。它不像写一个炫酷的前端动画那样让人兴奋也不像调通一个分布式事务那样充满技术挑战感但它却是决定你辛辛苦苦写的项目到底是躺在本地硬盘里吃灰还是真正在互联网上被成千上万人访问的关键一跃。我干这行十多年亲手部署过从学生期末作业级别的静态页面到日活百万的电商中台系统也帮客户救过凌晨三点因部署失败导致全站502的火。所有经验都指向一个事实线上部署不是开发的终点而是产品生命周期真正开始的第一道安检门。它涉及操作系统、网络协议、进程管理、安全策略、资源隔离、监控告警等多层知识的交叉验证任何一个环节的疏忽都可能让“能跑在localhost”的项目在线上变成一个沉默的404或持续报错的500。你搜到的“web”“项目”“线上”“部署”这些热搜词背后其实是三类完全不同的真实需求第一类是刚学完HTML/CSS/JS的学生想把课程设计挂到网上给老师看核心诉求是“快、简单、别出错”第二类是用Vue/React写完前端、Spring Boot搭好后端的初级工程师手头有个完整项目但第一次面对服务器满脑子问号“jar包怎么运行”“域名怎么连上我的代码”“别人访问不了是不是防火墙没关”第三类是团队中的运维或全栈角色需要建立一套可复现、可回滚、可持续集成的发布流程关注点早已不是“能不能上线”而是“上线时用户无感吗”“回滚是否能在30秒内完成”“这次更新有没有悄悄引入内存泄漏”。这篇内容就是为这三类人同时写的——学生能照着步骤把个人博客跑起来初级工程师能理解每一步背后的“为什么”而资深从业者则能从中看到那些藏在文档角落、只有踩过坑才懂的实操细节。我们不讲虚的“DevOps理念”只聊你打开终端后敲下的每一行命令究竟在做什么以及当它不工作时你该往哪个方向去查。2. 核心思路拆解为什么不能直接把开发环境“复制粘贴”到线上很多人第一次部署失败根源在于一个朴素但危险的假设“我在自己电脑上能跑传到服务器上不就能跑”这个想法错得离谱。开发环境和线上生产环境本质是两个物种强行套用同一套逻辑就像试图用家用电饭锅去炼钢——原理相似但对温度、压力、安全冗余的要求天差地别。我见过太多人卡在第一步把本地打包好的dist文件夹整个拖到服务器Nginx目录下刷新浏览器结果一片空白控制台报错“Failed to load resource: net::ERR_CONNECTION_REFUSED”。问题不在代码而在他根本没意识到开发时的npm run serve启动的是一个带热重载、跨域代理、内存缓存的开发服务器而线上Nginx只是一个纯粹的静态文件分发器它不会帮你启动任何进程也不会自动处理API请求的转发。所以部署的第一步永远不是操作代码而是明确你的项目类型与对应的交付物形态。这是所有后续决策的基石。Web项目大致可分为三类纯静态站点Static Site如个人博客、企业官网、文档中心。技术栈通常是HTML/CSS/JS VuePress/Hugo/Jekyll等生成器。交付物是一个dist或_site文件夹里面全是.html、.js、.css、图片等静态资源。这类项目部署最简单但也最容易因路径配置错误而白屏。比如你在Vue Router里用了history模式本地开发没问题但Nginx默认不识别这种URL会直接返回404必须手动配置try_files指令兜底。前后端分离的动态应用SPA API这是目前最主流的架构前端Vue/React打包成静态资源后端Spring Boot/Node.js/Django提供RESTful API。交付物是两份一份是前端的dist文件夹另一份是后端的可执行包如Spring Boot的.jar文件或Node.js的package.json源码。部署时前端由Nginx/Apache托管后端则需独立进程管理。这里最大的陷阱是跨域问题的幻觉开发时用vue.config.js里的proxy解决了但上线后这个代理配置就消失了前端请求会直接打到自己的域名而你的后端API很可能在另一个域名或端口上浏览器同源策略立刻拦截。解决方案不是在前端硬编码后端地址这会让环境切换变得灾难而是让Nginx在反向代理层统一处理把/api/开头的请求转发到真实的后端服务。传统服务端渲染应用SSR如老式的JSP、PHP、或现代的Next.js/Nuxt.js启用SSR模式。这类应用的HTML是在服务器端动态生成的交付物通常是一个需要运行时环境的程序如PHP-FPM进程、Java Web容器、Node.js服务。部署复杂度最高因为你不仅要管静态资源更要管整个运行时的生命周期。比如Tomcat你得知道webapps目录下放WAR包和直接放解压后的文件夹行为是不同的又比如Node.js用node app.js直接启动进程一旦崩溃就没了必须用pm2或systemd守护。选择哪种方式不取决于你“想用什么”而取决于你的项目“需要什么”。一个展示型官网用纯静态CDN成本最低、速度最快、安全性最高一个需要实时数据交互的后台管理系统就必须用前后端分离前端负责UI后端负责业务逻辑和数据安全而一个对SEO要求极高、首屏加载速度敏感的新闻门户则可能必须上SSR。我曾帮一个客户把他们的Vue SPA强行改成SSR只为把首屏时间从2.8秒压到0.9秒搜索引擎收录率提升了47%。但代价是部署脚本从10行增加到80行运维复杂度翻倍。没有银弹只有权衡。3. 核心细节解析与实操要点从服务器选型到Nginx配置的硬核细节明确了项目类型下一步就是把抽象的“部署”二字拆解成一个个可触摸、可验证的具体动作。这个过程我把它比作“给项目办一场严谨的跨国签证”你需要准备护照服务器、填写申请表环境配置、通过海关检查安全策略、最终获得入境许可公网访问。任何一个环节材料不全或格式不对都会被拒之门外。下面我们就以最常见的“前后端分离VueSpring Boot项目”为例逐层深挖那些文档里不会明说、但决定成败的核心细节。3.1 服务器选型与基础环境初始化别让“免费”成为最大成本新手最容易犯的错就是一头扎进“买服务器”这个环节却忽略了最关键的前置思考你的项目到底需要多大的“房子”我见过太多人为了一个个人博客买了8核16G的云服务器月付几百块结果CPU常年在1%徘徊硬盘IO几乎为零。这不仅是钱的问题更是认知偏差——你买的不是“服务器”而是“计算资源的使用权”而资源的价值永远体现在它被有效利用的时刻。对于绝大多数中小型Web项目起步推荐配置是2核4G内存 100GB SSD云盘 1Mbps带宽。这个配置能稳稳支撑日均1万PV以下的流量足够一个功能完整的后台管理系统或中型社区网站。为什么是这个组合我们来算一笔账2核CPU足以应对Nginx的高并发连接和Spring Boot应用的常规业务逻辑4G内存是底线因为Nginx自身占用约50MBJVM堆内存建议设为2G-Xms2g -Xmx2g再留1G给系统和其他进程刚好够用100GB SSD是为未来日志、备份、临时文件预留的空间机械硬盘在高IO场景下会成为性能瓶颈1Mbps带宽约125KB/s听起来很小但对文本和压缩后的JS/CSS来说已能满足百人并发的流畅访问。如果你的项目包含大量高清图片或视频那带宽才是首要瓶颈此时应优先升级带宽而非CPU。拿到服务器后第一件事不是装软件而是加固安全基线。很多人的服务器在开通几分钟后就被黑产扫描到植入挖矿木马。这三步必须做修改默认SSH端口将22端口改为一个大于10000的随机端口如22222。虽然不能防高级攻击但能过滤掉90%的自动化暴力扫描。禁用root密码登录强制密钥认证生成一对RSA密钥ssh-keygen -t rsa -b 4096将公钥id_rsa.pub内容追加到服务器的/root/.ssh/authorized_keys中然后在/etc/ssh/sshd_config里设置PermitRootLogin no和PasswordAuthentication no。重启SSH服务后只能用私钥登录彻底杜绝密码爆破。配置UFW防火墙Ubuntu/Debian系统自带一条命令即可“ufw allow 22222 ufw allow 80 ufw allow 443 ufw enable”。只开放必要的端口其他一切封死。这条命令执行后你的服务器就像一个只留了三个小窗的堡垒外界无法窥探内部任何信息。提示千万别跳过这三步我亲眼见过一个客户因为图省事没改SSH端口服务器在开通2小时后就被植入了DDoS僵尸程序不仅自己服务瘫痪还被用来攻击其他网站最终被云厂商强制关停。3.2 前端静态资源部署Nginx不只是“放文件”更是“智能路由”前端部署看似简单就是把dist文件夹扔进Nginx的html目录。但现实远比这复杂。一个典型的Vue CLI项目vue.config.js里可能有如下配置module.exports { publicPath: /my-app/, // 静态资源根路径 outputDir: dist, devServer: { proxy: { /api/: { target: http://localhost:8080, // 开发时代理到后端 changeOrigin: true } } } }这个publicPath: /my-app/就是埋下的第一个雷。它意味着所有JS/CSS的引用路径都会自动加上/my-app/前缀比如script src/my-app/js/app.js。如果你直接把dist文件夹放到Nginx默认的/usr/share/nginx/html/下那么访问http://your-domain.com/my-app/才能看到页面而http://your-domain.com/则会404。解决方案有两个一是构建时去掉publicPath让它为/这样资源就放在根目录二是让Nginx“假装”这个路径存在。后者更灵活适合一个服务器托管多个项目的场景。Nginx的核心配置文件/etc/nginx/conf.d/default.conf关键部分如下server { listen 80; server_name your-domain.com; # 根路径用于托管主站 location / { root /usr/share/nginx/html; index index.html index.htm; # 关键解决Vue Router history模式的404问题 try_files $uri $uri/ /index.html; } # 专门用于托管/my-app项目 location /my-app/ { alias /usr/share/nginx/html/my-app/; index index.html index.htm; # 注意这里用alias且路径末尾必须有/否则会拼接错误 try_files $uri $uri/ /my-app/index.html; } # 反向代理将/api请求转发给后端 location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 关键传递原始请求方法避免OPTIONS预检失败 proxy_method $request_method; } }这段配置里藏着三个必须理解的细节location /my-app/和alias /usr/share/nginx/html/my-app/;的配合。alias指令会完全替换匹配到的路径所以/my-app/js/app.js会被映射到/usr/share/nginx/html/my-app/js/app.js。如果这里误用了rootNginx会尝试去找/usr/share/nginx/html/my-app//my-app/js/app.js显然不存在。try_files $uri $uri/ /my-app/index.html;这行是SPA的灵魂。它告诉Nginx先找真实的文件$uri找不到就找目录$uri/都找不到就返回/my-app/index.html。这样无论用户直接访问/my-app/还是/my-app/user/profileNginx都会返回index.html由前端Router接管路由实现真正的单页应用体验。proxy_pass http://127.0.0.1:8080/;末尾的/至关重要。它表示“去掉匹配到的/api/前缀后再转发”。如果没有这个//api/users会被原样转发到http://127.0.0.1:8080/api/users而你的后端API很可能定义在/users导致404。加了/就变成了http://127.0.0.1:8080/users完美匹配。3.3 后端Java应用部署JVM不是“开箱即用”而是“精雕细琢”Spring Boot项目打成的xxx.jar是一个“可执行JAR”它内部嵌入了Tomcat所以不需要单独安装Web容器。但这绝不意味着你可以java -jar app.jar就完事。生产环境的JVM必须像赛车引擎一样被精细调校。首先进程守护是生死线。java -jar命令在终端关闭后进程会随之终止。必须用systemdLinux标准或pm2Node.js生态常用来管理。以systemd为例创建服务文件/etc/systemd/system/myapp.service[Unit] DescriptionMy Spring Boot Application Afternetwork.target [Service] Typesimple Userdeploy WorkingDirectory/opt/myapp ExecStart/usr/bin/java -Xms2g -Xmx2g -XX:UseG1GC -XX:MaxGCPauseMillis200 -jar /opt/myapp/myapp.jar --spring.profiles.activeprod Restartalways RestartSec10 # 关键限制内存防止OOM杀掉其他进程 MemoryLimit3G # 关键限制文件句柄数避免Too many open files LimitNOFILE65536 [Install] WantedBymulti-user.target这个配置里-Xms2g -Xmx2g设置了堆内存初始值和最大值相等避免运行时频繁扩容缩容-XX:UseG1GC启用了G1垃圾收集器适合大内存、低延迟场景MemoryLimit3G是systemd的硬性限制即使JVM参数失效进程也不会突破3G内存上限保护了整台服务器的稳定性。其次日志管理是排障的眼睛。Spring Boot默认将日志输出到控制台但在systemd下你需要显式重定向。在ExecStart后面加上 /var/log/myapp/app.log 21但这只是开始。真正的日志轮转要靠logrotate。创建/etc/logrotate.d/myapp/var/log/myapp/app.log { daily missingok rotate 30 compress delaycompress notifempty create 644 deploy deploy sharedscripts postrotate systemctl kill -s USR1 myapp.service /dev/null 21 || true endscript }这个配置每天切割一次日志保留30天自动压缩并在切割后发送USR1信号给Java进程通知其重新打开日志文件Spring Boot支持此信号。没有这套机制你的app.log几天就会涨到几个GB磁盘空间告急排查问题时还得在海量日志里大海捞针。注意systemd的Restartalways非常有用但也有陷阱。如果应用启动时因数据库连接失败而崩溃systemd会不断重启形成“启动-崩溃-重启”的雪崩循环瞬间耗尽服务器资源。务必在应用代码里加入启动健康检查失败时优雅退出而不是无限重试。4. 实操过程与核心环节实现从零开始手把手完成一次完整部署理论讲完现在进入最硬核的部分一次真实的、从无到有的部署实操记录。我会以一个具体的、可复现的案例贯穿始终——一个基于Vue 3 Vite构建的前端和一个基于Spring Boot 3的后端它们共同构成一个简单的“待办事项Todo List”应用。整个过程我将严格遵循生产环境的最佳实践不走捷径不跳步骤让你看到每一个命令背后的意图以及当它不工作时我如何一步步定位。4.1 环境准备与工具链安装标准化是稳定性的基石我们使用一台全新的Ubuntu 22.04 LTS云服务器作为目标机器。所有操作均以非root用户deploy身份进行这是安全的基本要求。第一步安装基础依赖# 更新系统 sudo apt update sudo apt upgrade -y # 安装必要工具curl用于下载git用于拉取代码unzip用于解压 sudo apt install -y curl git unzip # 安装Nginx sudo apt install -y nginx sudo systemctl enable nginx sudo systemctl start nginx # 安装OpenJDK 17Spring Boot 3官方推荐 sudo apt install -y openjdk-17-jdk java -version # 验证输出应为 openjdk version 17.x.x提示为什么选OpenJDK而非Oracle JDK因为前者是开源、免费、社区维护活跃的发行版且与Oracle JDK在功能和性能上已无实质差异。商业项目中选用LTS长期支持版本是铁律Ubuntu 22.04自带的OpenJDK 17正是LTS版本能获得至少8年的安全更新。第二步创建项目目录结构# 创建统一的项目根目录 sudo mkdir -p /opt/todo-app/{frontend,backend,logs} # 设置权限让deploy用户拥有全部控制权 sudo chown -R deploy:deploy /opt/todo-app # 创建Nginx的前端资源目录 sudo mkdir -p /usr/share/nginx/html/todo-frontend sudo chown -R deploy:deploy /usr/share/nginx/html/todo-frontend这个目录结构的设计体现了生产环境的清晰分层/opt/是第三方应用的标准安装位置frontend和backend物理隔离便于独立更新logs集中存放所有日志方便统一管理/usr/share/nginx/html/是Nginx的默认Web根目录符合Linux FHS文件系统层次结构标准规范。4.2 前端构建与Nginx配置让静态资源“活”起来我们从GitHub拉取一个公开的Vue Todo示例为演示我们假设仓库地址为https://github.com/example/todo-vue.git。# 切换到deploy用户 sudo su - deploy # 进入前端目录 cd /opt/todo-app/frontend # 克隆代码 git clone https://github.com/example/todo-vue.git . # 安装依赖并构建 npm install npm run build # 构建完成后dist目录生成构建成功后dist文件夹里是所有静态资源。现在我们需要将它们“搬家”到Nginx的指定位置# 清空旧的前端资源 sudo rm -rf /usr/share/nginx/html/todo-frontend/* # 复制新的dist内容 sudo cp -r dist/* /usr/share/nginx/html/todo-frontend/ # 验证文件权限 ls -la /usr/share/nginx/html/todo-frontend/ # 应该能看到index.html等文件且属主为root因为sudo操作接下来编写专属的Nginx配置文件/etc/nginx/conf.d/todo.confserver { listen 80; server_name todo.your-domain.com; # 替换为你的实际域名 # 前端资源 location / { root /usr/share/nginx/html/todo-frontend; index index.html; try_files $uri $uri/ /index.html; } # API代理 location /api/ { proxy_pass http://127.0.0.1:8080/; 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_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; } }这个配置的关键在于server_name它将todo.your-domain.com这个域名精准地绑定到这个server块。这意味着如果你的DNS已将该域名解析到这台服务器的IP那么所有对该域名的HTTP请求都会被Nginx捕获并按此规则处理。最后测试并重载Nginx# 检查配置语法是否正确 sudo nginx -t # 如果输出Syntax OK则重载配置 sudo systemctl reload nginx此时如果你在浏览器访问http://todo.your-domain.com应该能看到一个空白的Vue页面因为后端还没起来API请求会404但页面本身能正常加载控制台没有Failed to load resource的错误。这证明前端部署的第一步已经成功。4.3 后端构建、配置与systemd服务化让Java进程“永不宕机”后端代码同样从GitHub拉取假设地址为https://github.com/example/todo-springboot.git。# 切换到后端目录 cd /opt/todo-app/backend # 克隆代码 git clone https://github.com/example/todo-springboot.git . # 使用Maven构建确保pom.xml中已配置好profile ./mvnw clean package -DskipTests # 构建完成后target目录下会有jar包 ls target/*.jar # 应该看到类似 todo-springboot-0.0.1-SNAPSHOT.jar构建成功后我们将jar包复制到一个固定位置并创建systemd服务# 复制jar包到/opt/todo-app/backend/下并重命名 sudo cp target/todo-springboot-0.0.1-SNAPSHOT.jar /opt/todo-app/backend/todo-app.jar # 创建application-prod.yml配置文件用于生产环境 sudo tee /opt/todo-app/backend/application-prod.yml /dev/null EOF spring: profiles: active: prod datasource: url: jdbc:mysql://127.0.0.1:3306/todo_db?useSSLfalseserverTimezoneUTC username: todo_user password: your_strong_password redis: host: 127.0.0.1 port: 6379 server: port: 8080 servlet: context-path: /api EOF # 创建systemd服务文件 sudo tee /etc/systemd/system/todo-app.service /dev/null EOF [Unit] DescriptionTodo Spring Boot Application Afternetwork.target mysql.service redis-server.service [Service] Typesimple Userdeploy WorkingDirectory/opt/todo-app/backend ExecStart/usr/bin/java -Xms2g -Xmx2g -XX:UseG1GC -XX:MaxGCPauseMillis200 -jar /opt/todo-app/backend/todo-app.jar --spring.config.locationfile:/opt/todo-app/backend/application-prod.yml --spring.profiles.activeprod Restartalways RestartSec10 MemoryLimit3G LimitNOFILE65536 StandardOutputjournal StandardErrorjournal [Install] WantedBymulti-user.target EOF # 重载systemd配置 sudo systemctl daemon-reload # 启动服务 sudo systemctl start todo-app # 查看服务状态 sudo systemctl status todo-appsystemctl status todo-app的输出是判断后端是否成功的唯一金标准。理想状态下你应该看到Active: active (running)并且journalctl -u todo-app -f实时查看日志中能看到Spring Boot启动完成的日志如Started TodoApplication in X.XXX seconds。如果看到Active: failed那就意味着启动失败必须立即查看日志定位原因。常见的失败点包括数据库连接不上检查MySQL是否已安装并启动、Redis连接超时检查redis-server服务、配置文件路径错误--spring.config.location参数是否指向了正确的文件。4.4 域名解析与HTTPS配置让网站“值得信赖”当Nginx和后端都运行起来后你用服务器IP地址访问一切正常。但用户不会记IP他们记的是域名。所以最后一步是让todo.your-domain.com真正生效。第一步DNS解析登录你的域名注册商如阿里云万网、腾讯云DNSPod添加一条A记录主机名todo记录值你的云服务器公网IPTTL默认即可通常600秒DNS全球生效需要几分钟到几小时你可以用ping todo.your-domain.com来快速验证。第二步申请并配置HTTPS证书使用certbotLets Encrypt官方客户端一键搞定# 安装certbot sudo apt install -y certbot python3-certbot-nginx # 为你的域名申请证书Nginx插件会自动修改配置 sudo certbot --nginx -d todo.your-domain.com # 执行后certbot会自动 # 1. 验证你对域名的控制权通过在Nginx下创建临时文件 # 2. 向Lets Encrypt签发证书 # 3. 修改/etc/nginx/conf.d/todo.conf添加HTTPS监听和证书路径 # 4. 自动将HTTP请求重定向到HTTPS完成之后sudo nginx -t sudo systemctl reload nginx再用浏览器访问https://todo.your-domain.com地址栏会出现绿色的锁图标表示连接是加密且可信的。这不仅是安全要求更是现代Web的标配。Google搜索排名、浏览器警告、甚至某些API调用如地理位置、摄像头都强制要求HTTPS。至此一次完整的、生产级的Web项目线上部署宣告完成。从服务器初始化、环境安装、代码构建、服务配置到域名解析和HTTPS每一个环节都经过了真实世界的检验。它不是一个“理论上可行”的方案而是一个你今天就可以照着做的、能立刻上线的行动指南。5. 常见问题与排查技巧实录那些文档里不会写的“血泪教训”部署过程中90%的问题都出在“意料之外的细节”上。这些细节往往不会出现在官方文档的“快速入门”章节里因为它们太琐碎、太具体只属于那些在深夜对着终端反复敲命令、看着日志一行行滚动的实战者。我把这些年踩过的、帮客户解决过的、最具代表性的五个问题连同我的完整排查思路和独家技巧毫无保留地分享出来。它们不是“可能遇到”而是“你几乎一定会遇到”。5.1 问题一Nginx返回403 Forbidden但文件明明存在现象将前端dist文件夹复制到/usr/share/nginx/html/后访问域名Nginx返回403 Forbidden。用ls -la确认index.html权限是-rw-r--r--属主是root看起来一切正常。排查思路403错误Nginx官方定义是“服务器理解请求客户端的请求但是拒绝处理它”核心原因永远是权限或SELinux。在Ubuntu/Debian上SELinux默认不启用所以问题大概率出在文件权限的“隐性规则”上。独家技巧Nginx的worker进程默认是以www-data用户身份运行的。它需要对/usr/share/nginx/html/目录及其所有子目录、文件拥有读取r和执行x权限。x权限对目录而言意味着“可以进入该目录”这是很多人忽略的关键点index.html文件有r权限是不够的它的父目录/usr/share/nginx/html/也必须有x权限。解决方案# 给整个html目录添加执行权限递归 sudo chmod -R ax /usr/share/nginx/html/ # 更精确的做法只给目录加x文件保持r sudo find /usr/share/nginx/html/ -type d -exec chmod 755 {} \; sudo find /usr/share/nginx/html/ -type f -exec chmod 644 {} \;执行后403立刻消失。这个技巧我称之为“Nginx权限三定律”目录要x文件要r用户要对得上。记住它能省下你至少半小时的谷歌搜索。5.2 问题二前端页面能打开但所有API请求都返回502 Bad Gateway现象https://todo.your-domain.com能正常显示Vue页面但打开浏览器开发者工具的Network标签发现所有/api/xxx的请求状态码都是502 Bad Gateway。排查思路502错误是Nginx作为反向代理时无法从上游服务器即你的Spring Boot应用收到有效的HTTP响应。问题一定出在proxy_pass这一环。常见原因有三个上游服务根本没在运行、上游服务监听的地址/端口不对、上游服务虽然运行了但被防火墙拦截。独家技巧不要盲目重启服务先用最原始的curl命令绕过Nginx直接探测后端服务的“心跳”。# 在服务器本地直接curl后端 curl -v http://127.0.0.1:8080/api/health # 如果返回OK说明后端本身是好的问题在Nginx配置 # 如果返回Connection refused说明后端没起来或者没监听127.0.0.1:8080 # 如果返回timeout说明后端起来了但被防火墙挡住了如果curl成功那就检查Nginx配置里的proxy_pass地址是否和curl的地址完全一致注意末尾的/。如果curl失败就去看systemctl status todo-app重点看Active状态和journalctl -u todo-app的最后几行日志。我见过最多的情况是后端配置文件里server.port写成了8081而Nginx里proxy_pass写的是8080这种低级错误用curl一测便知。5.3 问题三Spring Boot应用启动后过几分钟就自动退出systemctl status显示failed现象sudo systemctl start todo-app后systemctl status显示active (running)但几分钟后再看就变成了failed日志里没有任何明显的错误堆栈。排查思路这几乎是systemd服务化Java应用的“经典病”。根本原因在于systemd默认认为一个Typesimple的服务其ExecStart命令启动的进程就是该服务的主进程。但如果这个Java进程在启动后又fork出了子进程比如某些JVM的JIT编译线程、GC线程systemd可能会“跟丢”主进程误判为崩溃。独家技巧强制systemd将Java进程视为“长期运行”的守护进程使用Typeforking并配合PIDFile。但这需要Java应用本身支持输出PID文件。更简单、更通用的方案是使用Typenotify并让Spring Boot主动向systemd发送就绪信号。这需要在pom.xml中添加spring-boot-starter-systemd依赖并在application.yml中配置spring: boot: systemd: # 启用systemd通知 enabled: true # 就绪信号超时时间 readiness-timeout: 300然后在systemd服务文件中将Typesimple改为Typenotify。这样Spring Boot启动完成后会主动调用sd_notify(READY1)告诉systemd“我好了可以标记为active了”。systemd会耐心等待这个信号而不是凭空猜测。5.4 问题四HTTPS配置后页面能打开但控制台报错“Mixed Content: The page at https://... was loaded over HTTPS, but requested an insecure resource http://...”现象https://todo.your-domain.com能打开但页面功能异常控制台出现大量Mixed Content警告所有API请求都失败。排查思路这是一个典型的“协议不一致”问题。你的页面是HTTPS加载的但页面里的某个资源JS、CSS、图片或者更关键的——API请求的URL仍然是http://开头的。现代浏览器出于安全考虑会直接阻止这种混合内容的加载。**独家