Nacos 报出 Unable to start embedded Tomcat,大概率不是你 Nacos 自身的业务代码出了问题,而是它的内置 Tomcat 容器根本没能在预期端口上正常跑起来。这类报错在本地开发、服务器部署、Docker 容器化三种环境里我都遇到过,表面上都是同一行异常,实际根因可能完全不同。这篇文章就把这个报错从现象到根因完整拆一遍,把排查顺序和解决方式整理成可以直接照着操作的内容,后面再遇到 Nacos 起不来,不会慌。
1. 先从日志本身说起
遇到这个问题,第一时间要做的事不是改配置,而是把日志里 Tomcat 启动那一段完整复制下来。Unable to start embedded Tomcat 是 Spring Boot 框架抛出的一层包装异常,它本身只说明“内嵌的 Tomcat 没起来”,但真正导致 Tomcat 起不来的 Caused by 异常,才是定位问题的关键。
1.1 这个报错到底在说什么
Nacos 的服务端是基于 Spring Boot 开发的,内置了一个 Tomcat 作为 HTTP 容器,用来对外提供控制台页面和 HTTP API。也就是说,Nacos 进程启动时会先初始化 Spring 容器,再启动这个内嵌 Tomcat,最后才是加载 Nacos 自己的注册中心逻辑。
我见过很多人一搜这个报错,就开始改 Tomcat 配置、调 Nacos 端口,但其实问题往往不在 Tomcat 本身。Unable to start embedded Tomcat 只是 Spring Boot 在最外层打的包,真正的异常在它的 Caused by 链里。比如下面这种典型日志:
text复制org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:126)
...
Caused by: java.net.BindException: Address already in use: bind
at sun.nio.ch.Net.bind0(Native Method)
看到 BindException: Address already in use,说明是端口被占;如果看到 Invalid bound statement 或 Failed to configure a DataSource,那是数据库问题;如果看到 FileNotFoundException 或者 Caused by: java.lang.IllegalStateException,那可能就是配置文件或环境变量的问题。前面的包装信息几乎一样,后面的 Caused by 完全不同。所以拿到完整堆栈,先倒着看 Caused by,再看第一行异常,顺序不能反。
1.2 哪些人最容易碰到这个报错
根据我处理过的咨询和排查经历,遇到这个报错的场景高度集中在三种情况。
第一种是第一次在本机装 Nacos 的新手,下载了压缩包,改了两行配置,启动脚本一跑,看到这个异常就懵了。这种情况九成是端口被占用,或者 MySQL 连接配置没写对。
第二种是在服务器上用 Docker 部署 Nacos,宿主机端口和容器端口映射搞错了,或者数据库地址写成了 localhost,导致容器内的 Nacos 连不到宿主机的 MySQL。
第三种是从 Nacos 1.x 升级到 2.x,配置文件里少了新版本的必填项,或者集群模式下 cluster.conf 配置不对,Nacos 服务在启动注册阶段就失败,Tomcat 跟着也起不来。
所以这不是一个“配置一下就好”的单一问题,而是需要按根因分类处理的启动故障。下面把根因拆开讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 报错根因拆解:端口、数据库、环境三选一
分析这个报错,本质上是在分析“Nacos 启动被哪一步卡住了”。从现象看,Tomcat 启动失败只是最终表现,真正的原因基本跑不出下面三类。
2.1 端口占用:最直接也最常见
Nacos 默认控制台端口是 8080,server.port=8080 写死在 application.properties 里。如果你的机器上已经有别的服务占了 8080,Nacos 的 Tomcat 就会因为地址绑定失败直接退出,抛出的就是上面看到的 BindException。
在 Linux 下我用这个命令确认端口占用:
bash复制ss -lntp | grep 8080
在 Windows 下用这个:
bash复制netstat -ano | findstr 8080
拿到占用进程的 PID 之后再确认是谁占的。如果确实是其他业务服务占用了 8080,可以直接改 Nacos 的端口。但要注意,Nacos 2.x 版本除了 8080 控制台端口,还会用到两个偏移端口:8080+1000=9848 和 8080+1001=9849。改端口时必须保证偏移端口也没被占用,否则客户端连接时一样会出问题。
提示:Nacos 2.x 的 gRPC 端口是主端口加 1000 和加 1001 这两个,改
server.port后,这两个偏移端口会自动跟着变,但要确保它们在宿主机上也没被占用。
2.2 数据库连接失败:启动流程直接中断
Nacos 从 1.x 开始默认使用内嵌的 Derby 数据库,很多教程会教你把数据库切换到 MySQL。这一步是新手最容易踩坑的地方。
Derby 模式不需要额外配置,开箱即用。一旦你把配置改成 MySQL 模式,Nacos 启动时会尝试连接 MySQL,如果连不上,启动流程会被打断,Tomcat 也会跟着启动失败。日志里通常会看到类似这样的信息:
text复制Caused by: java.sql.SQLNonTransientConnectionException: Could not create connection to database server.
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
还有更隐蔽的情况:连接串里 serverTimezone 没写,MySQL 8.x 会报时区错误;useSSL=false 没写,某些 MySQL 版本会警告但不至于失败;数据库账号密码不对,会报 Access denied for user。这些报错虽然会出现在数据库初始化阶段,但最终都可能导致 Spring Boot 启动失败,然后对外表现为 Tomcat 起不来。
我一直强调一个观点:看 Nacos 启动日志不能只看前 30 行,要看启动过程中第一个异常出现在哪一步。如果异常出现在 NacosDataSource 或数据库相关的地方,那问题就在数据库,而不是 Tomcat。
2.3 环境与配置问题:隐蔽但高发
这一类问题最常见的触发点是 JDK 版本和 JVM 参数。
Nacos 2.2.x 官方要求 JDK 8 及以上,但我实测过在某些高版本 JDK(比如 JDK 17)下,如果没有显式配置 --add-opens 相关参数,Nacos 会因为模块访问限制而启动失败。报错往往会绕到 Spring Boot 的启动过程里,最后也表现为 Unable to start embedded Tomcat。
另外,Nacos 的启动脚本 startup.sh 里有三个重要的 JVM 参数:
bash复制JAVA_HOME=/path/to/jdk
JVM_XMS=512m
JVM_XMX=512m
如果机器内存不足,JVM 分配不了指定大小的堆内存,Nacos 进程可能在 Tomcat 启动到一半时就被系统杀掉,日志里不一定能看到明显的 OOM,但进程就是起不来。我之前在一台只有 512M 内存的 ECS 上部署 Nacos,默认的 JVM 参数是 2G,结果启动失败后查半天才发现是内存不够,把 JVM_XMS 和 JVM_XMX 改成 256m 就好了。
3. 从现象到定位:我总结的一套排查顺序
在讲具体解决方案之前,先说说我自己的排查顺序。这套顺序我用了很久,能覆盖九成以上的启动异常,而且每一步都不用动配置,只做“观察”。
3.1 第一步查端口,先排除最廉价的因素
拿到 Unable to start embedded Tomcat 之后,我第一个动作永远是查 server.port 对应的端口有没有被占。不要先想着改端口,而是先确认是谁占了这个端口。
在 Linux 上我会用:
bash复制netstat -anp | grep 8080
如果输出结果是 LISTEN 状态,说明端口一直被某个进程占用。此时可以接着用 ps -ef | grep 端口号对应的PID 看进程是什么。有些时候是别的 Java 服务,有些时候是你上一个没关干净的 Nacos 进程。
之前我给一个同事排查,他反复改端口都没用,因为旧的 Nacos 进程一直卡在后台,占着 8080,每次新启动的 Nacos 都因为端口冲突起不来。杀掉旧进程后,连配置都不用改就好了。
如果端口没被占,进入第二步。
3.2 第二步看数据库,确认配置和连通性
如果配置里用了 MySQL 模式,我会先确认连接串、账号、密码有没有写对。查看 conf/application.properties 里这一段:
properties复制spring.sql.init.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
db.user.0=root
db.password.0=123456
注意几个点:db.num=1 表示单数据库模式,集群模式时多个库的 db.num 要对应数量,但首次排查先把 db.num 改成 1;db.url.0 里的 serverTimezone 在 MySQL 8.x 下必须显式指定;useSSL=false 建议加上,避免不必要的 SSL 连接尝试。
确认配置没问题后,我通常会在命令行手动连接一次数据库,确认账号权限和网络通不通:
bash复制mysql -h127.0.0.1 -uroot -p123456 -Dnacos
如果命令行能连上,但 Nacos 起不来,那就是 Nacos 侧的配置没生效,检查一下 application.properties 是不是改错了位置,或者文件编码是不是出了问题。
3.3 第三步查 JVM 与配置文件,排除环境因素
数据库没问题、端口没问题,那就要看环境和配置了。
先看启动日志里有没有 JVM 相关异常,比如 OutOfMemoryError、Could not reserve enough space for object heap,如果有,按前面说的方法调小 JVM_XMS、JVM_XMX。
再看 conf/application.properties 文件是不是被某些工具转换过编码格式。我在 Windows 上遇到过好几次因为用记事本保存 application.properties,导致文件变成 GBK 编码,Nacos 解析时中文乱码,最终启动失败的情况。这类问题日志里一般会提示 Invalid character 之类的信息,或者干脆解析不到关键配置。
3.4 快速检查清单
| 检查项 | 命令/操作 | 判断标准 |
|---|---|---|
| 端口占用 | netstat -anp | grep 8080 |
无监听进程 |
| 数据库连通 | mysql -h127.0.0.1 -uroot -p |
能进入 MySQL 命令行 |
| 数据库编码配置 | 查看 serverTimezone、useSSL |
连接串含时区参数 |
| JVM 内存 | free -m |
可用内存大于 JVM_XMX |
| 配置文件编码 | file application.properties |
显示 UTF-8 |
| 旧进程残留 | ps -ef | grep nacos |
无残留 Nacos 进程 |
这张表基本覆盖了 Unable to start embedded Tomcat 的常见诱因。接下来按部署方式展开说解决方案。
4. 不同部署方式的完整解决方案
同一个报错,在不同部署形态下,处理方式有差异。把单机、集群、Docker 三种方式单独拆开讲,避免一套操作套到八竿子打不着的环境里。
4.1 单机模式下的完整处理流程
单机模式是最简单的场景,确认以下几点即可解决:
- 修改控制台端口(如果 8080 被占):编辑
conf/application.properties,将server.port改成如 8848 或其他空闲端口,并重启。 - 确认数据库配置:如果不需要 MySQL,就用默认 Derby,不要去改
db.num和db.url。如果需要 MySQL,把数据库先建好,导入 Nacos 官方mysql-schema.sql脚本,再改连接串。 - 确认启动命令:单机模式用
startup.sh -m standalone启动,不要用默认的集群模式。
启动后可以看 logs/start.out 文件,确认日志尾部有没有出现:
text复制Nacos started successfully
看到这行说明 Tomcat 和 Nacos 服务都已经起来了。如果日志里卡住不动,说明还在等资源,用 jstack 看一下线程状态。
注意:Nacos 2.x 中默认数据库是 Derby,单机模式不会自动建表,第一次启动时 Nacos 会在 Derby 里创建自己的表结构。如果你之前曾经启动过 Nacos,再次启动时 Derby 数据文件可能损坏,此时可以删除
data/目录重新初始化。
4.2 集群模式下容易踩的坑
集群模式的启动流程比单机复杂很多,报 Unable to start embedded Tomcat 的概率也更高。常见坑位如下:
第一,cluster.conf 配置错误。Nacos 集群启动时会读取 conf/cluster.conf 文件,里面每一行代表一个节点,格式是 IP:port。如果这个文件不存在或者为空,Nacos 无法加入集群,可能直接启动失败。正确示例:
text复制192.168.1.10:8848
192.168.1.11:8848
192.168.1.12:8848
注意这里填的是 Nacos 主端口(如 8848),而不是偏移后的 gRPC 端口。
第二,db.num 配置和实际数据库数不匹配。集群模式下如果使用 MySQL,db.num 要和配置的库数量一致,例如两个库就写 db.num=2,然后依次配置 db.url.0、db.url.1。如果只有一个库,却把 db.num 写成 2,Nacos 启动时会尝试连第二个库,连不上就报错。
第三,三台机器的端口开放问题。集群节点之间需要互相访问 8848、9848、9849 等端口,如果防火墙没开,Nacos 启动时虽然能启动 Tomcat,但节点间通信失败会影响启动状态,甚至导致服务反复重启。
我遇到过一次比较典型的场景:三台服务器部署 Nacos 集群,第一台启动正常,第二台第三台启动时一直报 Unable to start embedded Tomcat。排查后发现问题出在第三台机器的 hosts 文件上,cluster.conf 里写的是服务器主机名而不是 IP,第三台机器解析不了其他主机名,导致集群注册失败。把 cluster.conf 里的主机名统一改成内网 IP 就好了。
4.3 Docker 部署中的端口与网络问题
Docker 部署 Nacos,由于多了容器网络和端口映射这层,问题定位会更绕一些。
比较典型的错误写法是这样:
bash复制docker run -d --name nacos \
-p 8848:8080 \
-e MODE=standalone \
nacos/nacos-server:v2.2.3
这里把宿主机的 8848 映射到了容器的 8080,但 Nacos 默认服务端口是 8080,容器内部监听的是 8080。实际上这样也能跑,但后续访问时总感觉别扭。更规范的写法是镜像内部直接用 8848 作为服务端口,把容器内 8848 映射到宿主机的 8848:
bash复制docker run -d --name nacos \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
-e MODE=standalone \
nacos/nacos-server:v2.2.3
Docker 部署时出现 Unable to start embedded Tomcat,多数是因为端口映射没包含 9848 和 9849,但 Nacos 2.x 的 gRPC 通信需要这两个端口。还有一种情况是 MySQL 地址写成了 localhost,这个在容器里指向容器本身,而不是宿主机。正确写法是使用宿主机在容器网络中的 IP,或者 host.docker.internal(Docker Desktop 支持),或者在自定义 bridge 网络里写宿主机的实际 IP。
提示:容器部署建议把 JVM 参数写在环境变量里,比如
-e JVM_XMS=256m -e JVM_XMX=256m,避免容器内存不足导致启动过程中被 OOM Killed。
4.4 从 1.x 升级到 2.x 的特殊配置
如果你是从 Nacos 1.x 升级到 2.x,需要额外注意几个变化。
Nacos 2.x 的默认数据源 Derby,但默认配置文件的注释里多了很多新选项。我用过最快的排查思路是:把官方发行包的 conf/application.properties 和当前配置做 diff,确认没有少关键项。
Nacos 2.x 还引入了鉴权配置:
properties复制nacos.core.auth.enabled=false
默认关闭。如果之前改过这个配置并且打开了鉴权,升级后密钥格式变了,可能导致启动异常。这时可以临时关闭鉴权,先让服务跑起来,再按官方文档把 nacos.core.auth.plugin.nacos.token.secret.key 配置为 Base64 格式的密钥。Nacos 2.2.0 版本开始对密钥格式有强校验,很多人升级后不知道这一点,一直报 invalid key 之类的错,实际就是密钥长度或编码格式不符合要求。
5. 顺着这条线,我踩过的几个相关深坑
报这个错的人,往往还会在后续使用中遇到几个衍生问题。这里一并整理,方便排查时对照。
5.1 控制台路径和端口描述混淆
Nacos 2.x 控制台默认路径是 /nacos,端口是之前提到的 8080。但网上很多教程还在用 http://localhost:8080/nacos 访问,如果你用的是 1.x,路径也一样,但 2.2 之后控制台做了重构,某些浏览器缓存可能会导致页面打不开。
这类问题和 Unable to start embedded Tomcat 的关联在于:很多人启动时报错后,改了端口或路径,但没清浏览器缓存,结果输入新端口还是看不到页面,又回头怀疑 Tomcat 没起来。我用过最简单的验证方式:直接访问 http://localhost:新端口/nacos,如果能弹出登录页,说明 Nacos 服务正常,问题在浏览器或代理。
5.2 Dubbo 集成时报 publish nacos metadata failed
有热词里提到 publish nacos metadata failed jtsupervise,这其实是 Dubbo 注册到 Nacos 后发布元数据失败的问题。出现这个报错,Unable to start embedded Tomcat 反而可能已经解决了,因为 Nacos 服务端已经能启动,但 Dubbo 客户端连不上 Nacos 的 gRPC 端口,导致元数据发布失败。
处理思路是确认 Dubbo 客户端和 Nacos 服务端的版本兼容性,以及网络是否能访问 9848 端口。Nacos 2.x 后,客户端默认用 gRPC 协议连接,如果只放行了 8848,没放行 9848,就会出现 Dubbo 能注册服务但发布元数据失败的情况。
5.3 安全配置引发的启动失败
如果你在生产环境开启了鉴权,还遇到过 invalid key: favax.crypto spec 之类的报错,这通常和密钥配置有关。Nacos 2.2 之后,nacos.core.auth.plugin.nacos.token.secret.key 要求使用 Base64 编码且原始长度不少于 32 字节。用一段足够长的随机字符串做 Base64 编码,再填到配置文件里,问题就能解决。
安全配置本身和 Tomcat 启动没有直接关系,但在启动校验顺序上,鉴权配置错误会导致 Spring 上下文初始化失败,最终也表现为 Unable to start embedded Tomcat。所以看到这个异常,不要排斥检查鉴权相关配置。
5.4 Windows 上服务启动报错 1053
在 Windows 上,如果把 Nacos 注册成 Windows 服务,偶尔会碰到 服务启动报错 1053,也就是服务启动超时。这个问题的根因和 Unable to start embedded Tomcat 很像,都是启动过程中卡住了,但 Windows 服务管理器有超时限制,Nacos 在超时内没完成启动就直接报 1053。
解决方式是不要用 Windows 服务来托管 Nacos,直接用 startup.cmd -m standalone 启动,或者把服务启动超时时间调大。另外,Windows 上 Nacos 默认使用控制台窗口运行,如果窗口被关闭,Nacos 就会退出,所以生产环境不建议在 Windows 上部署 Nacos 服务端。
6. 快速定位模板与最后的建议
最后给出一套我平时用的定位模板,直接照着检查,能省下不少重复试错的时间。
6.1 一个可以直接套用的启动检查脚本思路
在 Linux 下,我会把下面的检查步骤写在一个 shell 脚本里,快速输出 Nacos 启动核心状态:
bash复制#!/bin/bash
echo "===== 端口检查 ====="
ss -lntp | grep -E '8080|8848|9848|9849'
echo "===== Java 版本 ====="
java -version 2>&1
echo "===== 可用内存 ====="
free -m
echo "===== Nacos 进程 ====="
ps -ef | grep nacos | grep -v grep
echo "===== Nacos 启动日志 ====="
tail -100 logs/start.out
执行完这个脚本,端口、JDK、内存、进程、日志就都拿到了。多数情况下,异常信息已经足以帮你定位问题,不用再重复看控制台的原始输出。
6.2 我的几点习惯
在碰到这类启动报错时,我养成了几个习惯,分享出来供参考。
第一个习惯是,配置文件改动前先备份。Nacos 的 application.properties 涉及端口、数据库、鉴权、集群等多个维度,改错一个可能引发连锁问题。备份一份原文件,出问题时可以快速对比,比从网上重新下载安装包快得多。
第二个习惯是,启动前先看日志路径配置。Nacos 的日志默认写到 logs/ 目录,Windows 和 Linux 路径不同,如果你改了日志目录但目录不存在,会导致日志写不进去,排查问题时看不到任何有效信息。
第三个习惯是,不要一上来就改端口。先确认端口是否被占用,再判断是不是端口冲突。直接改端口的做法有时候能解决当前问题,但掩盖了真正的原因。比如旧进程残留占用了端口,不改端口,杀掉旧进程就解决了;改端口后,旧进程和新进程会同时存在,后续会有一堆逻辑混乱的问题。
说到底,Unable to start embedded Tomcat 不是一个复杂的故障,但它横跨端口、数据库、JVM、配置、网络多个层面,排错时需要按顺序、看 Caused by。把上面这些步骤走一遍,基本没有解决不了的情况。我每次帮别人排查这个报错,都会提醒一句:先看完整日志。不要被最外层那行异常吓到,真正的原因藏在堆栈的下一层,慢慢挖,总能挖出来。
