Nacos 启动直接抛出一句 Unable to start embedded Tomcat,服务起不来,日志还特别短。很多人第一次遇到这个报错的时候都是一头雾水,以为是 Nacos 本身的问题,折腾半天发现是端口被占了;还有的人是集群模式配了一半,MySQL 没起来,最后也归到这一句报错上。这篇文章我就把这个问题的定位思路和典型场景完整梳理一遍,从看日志到查端口,再到改内存、改数据库配置,一次说透。
这个报错本质上不是 Nacos 独有的,而是所有基于 Spring Boot 的内嵌 Tomcat 服务都可能会踩的坑。你只要理解了它的排查链路,以后不管启动什么服务碰到类似的启动失败,都能快速锁定根因。内容上我会覆盖 Nacos 1.x 和 2.x 的常见启动场景,适合刚接触 Nacos 的初学者,也适合部署集群时被环境问题卡住的人参考。
1. 报错现象与根因定位
1.1 先看清楚报错到底长什么样
不同环境、不同版本的 Nacos,报错日志的细节会有差异,但核心的异常信息基本一致。最常见的单机模式启动失败,日志末尾大概长这样:
log复制org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:142)
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:94)
...
Caused by: java.net.BindException: Address already in use: bind
at sun.nio.ch.Net.bind0(Native Method)
at sun.nio.ch.Net.bind(Net.java:433)
...
看到 BindException: Address already in use,那几乎可以断定是端口被占用。但有时候日志最后没有这么明显的 Caused by,只有一句 Error creating bean with name 'tomcatServletWebServerFactory',或者 Failed to start bean 'webServerStartStop',这两种情况其实也是同一个原因,只是 Spring Boot 版本不同,异常封装方式不一样。
还有一种情况,错误是 java.net.SocketException: Permission denied,这在 Linux 下比较常见,说明你用了 1024 以下的端口(比如 80、443),而当前用户没有权限绑定这个端口。这种场景在 Nacos 里不常见,因为默认是 8848,但如果你改过端口,就需要留意。
1.2 “Unable to start embedded Tomcat”只是表面现象
这句话翻译过来就是“内嵌 Tomcat 启动失败”,但它本身不是一个具体原因,而是一个结果。Spring Boot 启动一个 Web 服务的过程大致是:加载配置、创建 TomcatWebServer 实例、初始化 Connector、绑定端口、注册 Servlet 组件。这条链路里任何一个环节出问题,最后都会汇总成一句 Unable to start embedded Tomcat。
所以排查这个报错的核心原则很简单:不要盯着这行字看,要往上翻日志,找到 Caused by,从它往下的那几行才是真正的病因。很多人在群里提问,只贴最后一行,别人也只能靠猜。你如果学会了看完整日志,很多问题自己就能定。
这个思路能用在很多场景。比如你以后启动 Spring Cloud Gateway、Spring Boot Admin、Sentinel Dashboard,只要它们底层是 Spring Boot 内嵌 Tomcat,启动失败时排查路径基本一致。Nacos 只是其中一个典型案例。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 排查流程与工具准备
2.1 第一步:把日志翻全
很多人在终端直接启动 Nacos,日志刷得很快,报错一闪而过,截图都没来得及。我个人的建议是,不管启动成功还是失败,先去 logs 目录下把日志文件翻出来看。
Nacos 的日志目录默认在安装目录下的 logs/ 里,关键文件有这几个:
start.out:启动脚本的输出日志,启动失败时最先看它。nacos.log:主日志文件,里面会记录更详细的异常堆栈。config.log、naming.log:分别记录配置中心、注册中心模块的运行日志。
启动失败时,优先看 start.out,因为它和你在控制台看到的内容一致。如果 start.out 里信息不够,再看 nacos.log,里面一般有完整的堆栈。
这里有一个实操习惯:在启动前先把旧的 start.out 清空或备份,否则如果日志文件太大,定位问题的时候会非常费劲。在 Linux 下可以这样操作:
bash复制cd /opt/nacos/logs
> start.out
tail -f start.out &
cd /opt/nacos/bin
./startup.sh -m standalone
用 tail -f 实时盯着日志输出,一旦报错立刻就能看到上下文,比启动完后翻最后几十行要直观得多。
2.2 第二步:端口占用排查
端口冲突是 Unable to start embedded Tomcat 最常见的原因,没有之一。单机模式下 Nacos 默认占用这些端口:
| 端口 | 用途 |
|---|---|
| 8848 | 主 HTTP 端口,控制台、API 请求都走这里 |
| 9848 | 客户端 gRPC 请求端口(2.x 版本,8848 + 1000) |
| 9849 | 服务端 gRPC 通信端口(2.x 版本,8848 + 1001) |
| 8080 | 某些版本/配置下控制台默认端口,路径为 / |
如果你用的是 Nacos 2.x,只排查 8848 是不够的,9848 和 9849 也是必须检查的对象。这三个端口任何一个被占用,都有可能导致启动失败,或者启动看似成功但客户端连不上。
Linux 下查端口占用:
bash复制netstat -tlnp | grep -E '8848|9848|9849'
lsof -i:8848
lsof -i:9848
lsof -i:9849
Windows 下查端口占用:
bash复制netstat -ano | findstr 8848
tasklist | findstr 进程PID
假设 8848 被 PID 12345 占用,想确认它是什么进程,可以继续查进程信息。如果确实想释放端口,Windows 下可以用:
bash复制taskkill /F /PID 12345
Linux 下用:
bash复制kill -9 12345
这里要提醒一句,kill -9 之前一定要确认这个进程不是生产环境其他服务在用的端口,别为了启动 Nacos 把别的服务给杀了。优先考虑给 Nacos 换端口,这样更稳妥。
修改 Nacos 主端口的方式是编辑 conf/application.properties:
properties复制server.port=8848
改完之后重启即可。
2.3 第三步:确认 Java 环境
端口没问题的时候,下一个要怀疑的就是 Java 环境。Nacos 1.x 要求 JDK 8 及以上,Nacos 2.x 建议 JDK 8 或 JDK 11。这里说的 JDK 不是 JRE,JAVA_HOME 必须指向 JDK 的安装目录。
JDK 版本太低或者环境变量指向错了,启动时偶尔不会直接报版本错误,而是先报一堆类加载失败的堆栈,最终也可能落到 Unable to start embedded Tomcat 上。
先检查当前 Java 版本:
bash复制java -version
echo $JAVA_HOME
如果版本没问题,再检查启动脚本里有没有硬编码的 JAVA_HOME。Nacos 的启动脚本在 bin/startup.sh(Linux/Mac)或 bin/startup.cmd(Windows),脚本里有时候会判断 JAVA_HOME 是否为空,为空就报错退出。如果你是在 IDE 或自定义脚本里启动 Nacos,也要确认环境变量是否透传到了启动进程里。
另外一个容易被忽视的点:JDK 8 的早期版本(u144 之前)在跑 Spring Boot 2.x 的时候会有各种奇葩问题,比如 SSL 握手失败、类加载异常。Nacos 2.x 官方推荐 JDK 8u201 及以上,低于这个版本建议升级。
3. 典型场景的完整解决过程
3.1 场景一:8848 端口被占用
这个场景最直观,也最经典。我碰到过很多次,基本都是本机之前跑过 Nacos,进程没退干净,或者有别的中间件占了 8848。
完整的排查流程是:
- 先看
start.out,确认报错里有BindException: Address already in use。 - 执行
netstat -tlnp | grep 8848,确认占用进程的 PID。 - 判断该进程是否可以终止。如果是残留的 Java 进程,且确认没有在跑其他服务,杀掉即可。
- 如果端口被其他服务占用且不能终止,直接给 Nacos 换端口。
换端口的时候,除了改 server.port,还要注意 2.x 版本会自动在 server.port 的基础上加 1000/1001 作为 gRPC 端口。也就是说,如果你把主端口改成 8858,那么 gRPC 端口会自动变成 9858 和 9859。改完之后客户端连接的端口也要同步改,比如 Spring Cloud Alibaba 里配置的 server-addr 要指向新端口。
很多人改完 server.port 后忘了客户端那边的配置,启动日志里 Nacos 起来了,但服务注册不上去,客户端一直报连接超时。这个坑非常常见,务必留意。
3.2 场景二:控制台端口 8080 冲突
Nacos 的控制台默认路径是 http://ip:8848/nacos,但某些版本或某些自定义配置下,控制台端口不是 8848,而是 8080,路径是 /。这种情况你可以去 conf/application.properties 里看有没有类似 nacos.console.port 或者 server.port 的配置。
如果控制台端口被配置成了 8080,而本机的 8080 已经被其他 Web 服务占用(比如本地开发经常有各种微服务挂在 8080 上),那么启动时同样会报 Unable to start embedded Tomcat。
解决方案有两种:
- 把占用 8080 的进程停掉。
- 修改 Nacos 控制台端口,比如改成 9000:
properties复制nacos.console.port=9000
也可以直接把 server.port 改成一个不冲突的端口,控制台会跟着主端口走。启动完成后,访问 http://ip:9000/nacos,看到登录页就意味着启动成功。
这里有个小细节,如果 Nacos 的 application.properties 里有多个端口相关的配置,改的时候要分清主端口和控制台端口。主端口负责 API 请求和客户端通信,控制台端口只管页面访问。两个功能可以合并到同一个端口,也可以分开,取决于版本和配置。
3.3 场景三:JVM 内存参数过大导致启动失败
Nacos 启动脚本里默认配置了 JVM 参数,startup.sh 里有一段长这样的配置:
bash复制JAVA_OPT="${JAVA_OPT} -Xms512m -Xmx512m"
JAVA_OPT="${JAVA_OPT} -Xmn256m"
JAVA_OPT="${JAVA_OPT} -Djava.awt.headless=true"
如果你的机器物理内存比较小,比如只有 512MB 或 1GB,默认的 -Xmx512m 加上其他开销可能直接把内存耗尽,导致 JVM 无法分配足够的堆内存,进程启动失败。这种场景下日志里通常会看到 OutOfMemoryError 或者 Could not reserve enough space for object heap,但也有可能只是笼统地报启动失败。
调小内存参数的方式是编辑 bin/startup.sh,把 -Xms 和 -Xmx 改小。比如改成:
bash复制JAVA_OPT="${JAVA_OPT} -Xms256m -Xmx256m"
Windows 环境在 bin/startup.cmd 里找到类似 set JVM_MS=512m、set JVM_MX=512m 的配置,改成 set JVM_MS=256m、set JVM_MX=256m。
改完之后重启 Nacos,观察内存占用和启动耗时。如果你的机器是 2GB 内存以下,跑 Nacos 单机版建议至少留出 512MB 给 JVM,不然集群模式或高并发场景下会频繁 Full GC。
还有一个小技巧,如果你不确定改完内存参数是否有效,可以在启动后查看进程的启动参数:
bash复制ps -ef | grep nacos
如果你能在 Java 启动命令里看到 -Xms256m,说明参数生效了;如果还是 512m,说明你改错了脚本或环境变量被覆盖了。
3.4 场景四:数据库配置错误导致启动失败
Nacos 内置了 Derby 数据库,单机模式默认使用 Derby,所以很多人以为 Nacos 不依赖外部数据库。但如果你改了 conf/application.properties,把数据源切到了 MySQL,那么 MySQL 的连接状态就直接影响启动结果。
典型的错误配置包括:
db.num=1但db.url.0写错了。- MySQL 服务没有启动。
- MySQL账号密码不对。
- 数据库不存在或未初始化。
spring.datasource.platform被注释掉了。
日志里如果出现 Fail to get mysql connection 或 Communications link failure,那基本就是 MySQL 的问题。
我建议的排查顺序是:
- 先确认
conf/application.properties里spring.datasource.platform的值。如果设置成了mysql,说明走的是外部数据源。 - 检查
db.num和db.url.0、db.user、db.password是否填写正确。 - 本地先手动尝试用 MySQL 客户端连接一下,确认网络、账号、密码都没问题。
一个常见的坑是时区配置。MySQL 连接串如果没有加 serverTimezone 参数,数据库服务器和 Nacos 服务器时区不一致时,连接可能失败。URL 里建议加上:
properties复制db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
另外,db.num=2 的情况通常用于 MySQL 高可用配置,至少配置两个数据源地址,Nacos 会自己判断主的可用性。如果你只有一个 MySQL,却配了 db.num=2,启动时会因为缺少第二个数据源配置而失败。把 db.num 改为 1,或者补上第二个数据源即可。
3.5 场景五:集群部署时的 IP 识别问题
集群模式下,Nacos 节点之间需要互相通信,如果服务器有多个网卡,Nacos 可能识别错 IP,导致节点无法正常组网,极端情况下启动异常。
日志里如果出现 Nacos is starting 之后迟迟没有 Nacos started successfully,或者注册中心日志一直刷 no leader、not available 之类的信息,就要怀疑 IP 识别的问题。
解决方式是显式指定 Nacos 的 IP 地址。在 conf/application.properties 里加:
properties复制nacos.inetutils.ip-address=192.168.1.100
也可以设置环境变量 NACOS_IP 来指定:
bash复制export NACOS_IP=192.168.1.100
如果是 IPv6 环境,Nacos 2.x 对 IPv6 的支持整体还可以,但部分版本存在坑。如果你的服务器开启了 IPv6,但网络环境只有 IPv4,Nacos 识别到了 IPv6 地址导致跨节点通信失败,可以考虑在启动脚本里强制使用 IPv4 栈:
bash复制JAVA_OPT="${JAVA_OPT} -Djava.net.preferIPv4Stack=true"
这个参数的原理是让 JVM 在网络栈选择时优先使用 IPv4,避免在多栈环境下误选 IPv6 地址。集群部署时,节点间通信出问题有很多原因,IP 识别只是其中之一,但也是最容易忽略的一个。
4. 常见问题速查表与避坑经验
4.1 问题速查表
把启动过程里常见的报错关键字和对应的处理方案整理成一张表,方便遇到问题时快速定位。
| 报错关键字 | 可能原因 | 解决建议 |
|---|---|---|
BindException: Address already in use |
主端口或 gRPC 端口被占用 | 查占用进程并释放端口,或修改 server.port |
SocketException: Permission denied |
绑定了 1024 以下端口且权限不足 | 改用 1024 以上端口,或对可执行文件授权 |
OutOfMemoryError / Could not reserve enough space |
JVM 堆内存配置过大,机器内存不足 | 调小 startup.sh 里的 -Xms -Xmx |
Fail to get mysql connection |
MySQL 未启动、账号密码错误、URL 错误 | 检查 db.url.0、db.user、db.password |
NoClassDefFoundError |
JDK 版本过低或不兼容 | 升级到 JDK 8u201+ 或 JDK 11 |
Failed to start bean 'webServerStartStop' |
端口冲突或组件初始化失败 | 往上翻日志找 Caused by,重点查端口 |
java.net.BindException: Address already in use 出现在 9848/9849 |
gRPC 端口被占用 | 释放 9848 / 9849,或在防火墙里放行 |
no leader / 集群一直不可用 |
IP 识别错误、节点间网络不通 | 指定 nacos.inetutils.ip-address,检查防火墙 |
4.2 我踩过的几个坑
第一个坑是 Nacos 2.x 的 gRPC 端口。我第一次部署 2.x 版本的时候,以为只要把 8848 端口在防火墙里放行就万事大吉了,结果客户端一直报连接错误。后来才发现 2.x 去掉了 1.x 的 HTTP 长轮询机制,改成了 gRPC 双端口模式。主端口和 gRPC 端口之间是固定偏移关系:主端口加 1000 是客户端 gRPC 端口,加 1001 是服务端 gRPC 端口。如果你只开了 8848,没开 9848,客户端请求确实会失败。这一步是 2.x 和 1.x 最大的差异之一,升级版本的时候特别容易踩。
第二个坑是修改配置后进程没退干净就重新启动。Nacos 的启动脚本里没有强制杀旧进程的逻辑,如果你上次启动的 Nacos 没有正常停止,端口还被占着,这次启动大概率直接报 BindException。建议每次重启前确认旧进程确实已退出。
bash复制# 查看纳科斯相关进程
ps -ef | grep nacos
# 确认没有剩余进程后再启动
第三个坑是使用 MySQL 作为数据源时,数据库初始化脚本没有执行。Nacos 官方在 conf/ 目录下提供了 nacos-mysql.sql,切换到 MySQL 之前必须先把表结构和数据初始化好,否则启动时连数据库成功,但执行 SQL 会失败,报错同样很隐晦。我见过有人卡在这上面很久。
第四个坑是时区问题。如果你的 MySQL 和 Nacos 不在同一个时区,连接串里不加 serverTimezone 参数,某些 MySQL 驱动版本下会直接报错。加上参数之后问题立刻消失。
4.3 快速定位方法总结
如果你现在正在被这个报错卡住,按照下面的顺序排查,大概率能在几分钟内解决:
- 打开
logs/start.out,找到Caused by下面的具体异常。 - 如果是端口相关,立刻检查 8848、9848、9849,以及你改过的自定义端口。
- 检查 Java 版本和
JAVA_HOME是否正常。 - 检查
conf/application.properties里数据源配置是否与你的实际环境一致。 - 重启前确认旧进程已退出,确保没有残留进程占用端口。
这套流程不仅适用于 Nacos,也适用于所有基于 Spring Boot 的内嵌 Tomcat 服务。你只需要把端口换成对应的服务端口,思路完全一致。
从我个人的经验来看,Nacos 的启动问题九成以上都出在端口和配置上,真正是代码 bug 或者 Nacos 自身缺陷的情况非常少。养成一个好习惯:启动前检查端口、确认 JDK 版本、看一眼配置文件,启动时盯住日志,遇到问题先找 Caused by,然后把异常关键信息放进搜索引擎。这套基本功练熟了,不光是 Nacos,以后部署任何基于 Spring Boot 的中间件,你都能比别人更快定位问题。
最后再分享一个小技巧,如果你在服务器上启动 Nacos 后不知道是否成功,直接访问 http://ip:8848/nacos,能打开控制台登录页就是成功了。如果访问不了,回到 logs/nacos.log 看最后几十行,会有明确的错误信息。这个习惯帮我节省过大量排查时间。
