
最开始我是图省事直接在一台内网服务器上用Docker跑OnlyOffice DocumentServer网络拓扑很简单本机映射一个8080端口开发环境里直连IP访问一切岁月静好。直到我把它暴露到公网入口用Nginx做了一层反向代理噩梦开始了。页面能打开文档列表也能加载但一进在线编辑整个编辑器一直卡在加载动画控制台里刷出一堆红的editor.bin下载失败以及随之而来的api.js在跨域环节被拦、文档安全令牌格式不正确、甚至WebSocket反复断连。折腾了整整一个下午期间反复改Nginx配置、重启容器、清缓存最后发现真正的问题比我想象的要基础得多——甚至连“反向代理路径解析”这种入门概念都能把人绊得死死的。这篇文章不打算写得太理论我会把这次排障的完整过程、我踩过的每一个坑、最终能稳定运行的Nginx配置原样拆给你看。只要是打算用Docker部署OnlyOffice再用Nginx反代的人这篇应该能帮你少走至少两小时的弯路。1. 部署前先搞清楚OnlyOffice在Docker里的运行逻辑只有先弄清楚OnlyOffice容器对外暴露了哪些服务、各自跑在哪个端口、容器内部的路径是怎么组织的后面出了问题才知道该查哪里。1.1 OnlyOffice容器里的三个核心角色一个标准的onlyoffice/documentserver镜像启动后内部实际运行着多个服务但对外暴露的逻辑角色可以归成三个docservice文档编辑的核心后端服务负责文档解析、协同编辑、保存等业务逻辑对外主要走的是/documentserver/这个路径段。converter文档格式转换服务在线预览、格式互转都靠它在/ConvertService.ashx、/ResourceService.ashx等接口上提供能力。前端静态资源也就是你在浏览器里加载的editor页面、api.js、样式文件等。用Docker启动时官方镜像会把80端口暴露出来作为统一入口所有上述服务都通过这一个端口的路径区分来路由这也是Nginx反代时最容易出问题的地方——你把根路径/怎么转发、location怎么匹配直接决定了后续所有资源的加载顺序。1.2 最简安装命令没你想的那么简单网上很多教程给的是这种极简命令docker run -itd -p 8080:80 --name onlyoffice onlyoffice/documentserver然后告诉你浏览器访问http://ip:8080就能用了。实测确实能跑但只适合本地随便玩玩。一旦你开始配置存储、对接数据库、甚至只是重启几次容器就会体会到什么叫“配置全丢”。我实际部署用的挂载和参数是这样的docker run -itd \ --name onlyoffice \ --restartalways \ -p 8080:80 \ -v /data/onlyoffice/logs:/var/log/onlyoffice \ -v /data/onlyoffice/data:/var/www/onlyoffice/Data \ -v /data/onlyoffice/lib:/var/lib/onlyoffice \ -v /data/onlyoffice/db:/var/lib/postgresql \ onlyoffice/documentserver尤其是/var/www/onlyoffice/Data这个目录里面存了证书、密钥、个性化配置。我后续排查editor.bin问题时就发现如果容器重建而Data没有持久化生成的一些临时配置和密钥会变导致前面提到的安全令牌校验报错。所以四个目录建议从一开始就挂全省得后面返工。这里插一句容器启动后不要马上就开始高强度访问因为首次启动要做数据库初始化、密钥生成、字体缓存扫描我在配置比较低的机器上实测首次就绪可能需要一到两分钟。判断服务是否真正就绪可以看进程或者直接试探接口curl http://localhost:8080/healthcheck如果返回包含“true”之类的正常响应才说明可以继续配置Nginx了。2. Nginx反向代理在OnlyOffice场景下的三个“隐形坑”在纯内网直连时OnlyOffice前端请求的是http://IP:8080/...这种地址Nginx加了反代之后前端页面不知道自己被代理了它仍然会按照自己“看到的”地址去加载后续资源。这一节讲的三个坑每个单独拎出来都能写一篇文章但在这里它们合谋导致了我的editor.bin下载失败。2.1 路径拼接的“鬼打墙”proxy_pass末尾要不要带斜杠如果你用Nginx配一个location最常见的写法是location /documentserver/ { proxy_pass http://onlyoffice_backend/; }注意这里的proxy_pass结尾带了/。这种写法意味着浏览器请求/documentserver/api.js时Nginx会把/documentserver/这个前缀整个剥掉把后面的api.js拼到后端地址上最后实际请求的是http://onlyoffice_backend/api.js。这些约定本身没问题但OnlyOffice前端代码里生成资源URL时并不总是按固定前缀来它依赖X-Forwarded-*头来判断当前的对外访问路径。如果你在conf里只配了proxy_pass http://后端注意没带斜杠请求路径可能变成/documentserver/documentserver/api.js这种double路径而如果带了不该带的斜杠则变成/api.js这种裸路径。两种错误的表现很不一样路径少了documentserver段加载api.js时返回404编辑器白屏。路径多了重复段加载一切静态资源都404控制台全部是红叉。editor.bin之所以经常在日志里单独出现“下载失败”是因为它和api.js这类静态文件走的代理规则可能不一样。很多人图省事会专门为editor.bin写一条location反而把路由优先级搞乱。2.2 Host头与X-Forwarded-Proto的连锁反应OnlyOffice在生成下载链接、WebSocket地址时会参考请求头中的Host、X-Forwarded-Proto等字段来决定返回给浏览器什么样的URL。如果Nginx没有显式设置这些头浏览器访问https://office.example.com时后端却只看到http://的转发信息于是它生成的editor.bin下载地址就变成了http://office.example.com/...浏览器会先尝试用HTTP去请求这个HTTPS地址被拦截或者被重定向一圈表现就是下载失败、连接重置。我最初踩的就是这个坑Nginx配了SSL证书代理到后端却忘了同时转发X-Forwarded-Proto导致日志里全是Mixed Content类似的报错。2.3 WebSocket反向代理被漏掉OnlyOffice的协同编辑依赖WebSocket长连接路径是/websocket。Nginx的普通代理配置不会自动升级WebSocket协议必须在location里显式设置Upgrade头。这一步如果漏掉用户打开文档时页面能加载一部分但一旦涉及多人协同、光标同步就会不断重连。我在第一次部署时因为只关心editor.bin这个问题一度没注意WebSocket也在报错结果把editor.bin修好后又发现协作功能依然不稳定查了半天才发现是WebSocket没配好。所以如果你用Nginx反代OnlyOffice建议在一开始就把下面这份配置里的WebSocket location也一起写上别分成两轮来排障。3. editor.bin下载失败排查实录从502到200的全过程这一节是我这次排障最核心的经过一步步还原当时是怎么确认问题、又是怎么修好的。整个过程提供了一个很实用的排查范式不要对着配置文件猜要去浏览器看真实请求再回到后端日志里找依据。3.1 排查第一步在浏览器里看真实的请求链我先是打开文档编辑页面发现一直转圈按F12打开开发者工具切到Network面板刷新页面直接按“Fetch/XHR”过滤。很快看到一条红色状态的请求https://office.example.com/documentserver/editor.bin状态码是502再点开这条请求的详情看到请求URL完全正确说明前端生成的资源地址本身没问题。响应头里没有任何OnlyOffice相关的内容说明Nginx根本没有把请求转发到我预期的OnlyOffice后端或者后端服务根本没回应。时间非常短几毫秒就返回502说明不是后端超时而是Nginx层连接失败。这时我先直接在后端机器上用curl测试curl http://127.0.0.1:8080/documentserver/editor.bin结果返回了正常的二进制内容说明OnlyOffice容器本身没问题。问题就出在Nginx到容器的这一段路径上。3.2 排查第二步确认Nginx upstream是否真正可达我的Nginx配置里用了upstream块来定义后端upstream onlyoffice_backend { server 127.0.0.1:8080; keepalive 256; } server { listen 80; server_name office.example.com; client_max_body_size 100m; location / { proxy_pass http://onlyoffice_backend; 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; } }表面看没有大问题但Nginx对upstream中名为onlyoffice_backend的服务器也会做DNS解析和连接检查。如果upstream配置里写的是域名而该域名解析异常会出现间歇性的502如果写的是IP则一般不会出现解析问题。我检查了nginx -t配置格式通过重启Nginx后依然502。于是我从另一个角度去看Nginx的error.log是不是有更具体的报错信息。tail -f /var/log/nginx/error.log日志里出现了类似connect() failed (111: Connection refused) while connecting to upstream的记录。这说明Nginx所在机器到127.0.0.1:8080的TCP连接被拒绝了。但我刚才在服务器本机明明curl通了怎么换成Nginx就拒了。细想一下才发现我的OnlyOffice容器是用-p 8080:80启动的端口映射在docker0网桥的接口上。正常情况下从宿主机访问127.0.0.1:8080是可以通的。但如果Nginx运行在另一个容器或者另一台机器里那它访问的127.0.0.1:8080根本不是宿主机的OnlyOffice端口。这种情况在公司内网多机部署时很常见Nginx和OnlyOffice不在一台机器上配置却写的是127.0.0.1自然就连不上。3.3 真正原因反代时把editor.bin请求转发给了不存在的路径后来我换了一种方式排查直接在Nginx配置里临时写了一个显式的location来做调试location /documentserver/editor.bin { proxy_pass http://127.0.0.1:8080; }保存后reload再用curl测Nginx入口curl -I http://127.0.0.1/docu伺服器/editor.bin这次返回了200说明问题更隐蔽——不是Nginx到后端连不通而是通用location的处理方式把editor.bin的真实路径吃掉了。最后我想到打开Nginx的access.log对比我浏览器里的请求和后端实际收到的请求路径。真相立刻清楚了浏览器请求/documentserver/editor.bin后端实际收到/editor.bin也就是说我在某个位置的location中用了类似location /documentserver/ { proxy_pass http://onlyoffice_backend/; }尾部那个斜杠把documentserver这个路径前缀给剥掉了。而OnlyOffice容器内部的docservice其实默认监听在根路径下意思是它自己能正确处理/documentserver/editor.bin但你如果把前缀剥掉再转发就变成了它不认识的路由返回非200状态码浏览器自然就下载失败。3.4 最终修好的配置一套完整的反代模板这是我最后稳定运行了很久的Nginx配置核心片段你可以直接抄upstream onlyoffice_backend { server 127.0.0.1:8080; keepalive 32; } server { listen 80; server_name office.example.com; # 文件上传大小要放宽OnlyOffice编辑大文档时前端会把整个文件传给转换服务 client_max_body_size 100m; location /documentserver/ { proxy_pass http://onlyoffice_backend; # 注意这里proxy_pass后面没有斜杠保留完整的/documentserver/路径 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; } location /websocket { proxy_pass http://onlyoffice_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 86400s; proxy_send_timeout 86400s; } }核心就一句proxy_pass http://onlyoffice_backend;不要带末尾斜杠。这样浏览器请求/documentserver/editor.bin时后端收到的是/documentserver/editor.bin路径原样透传不再发生剥离。如果你必须使用子路径来访问比如想用https://office.example.com/onlyoffice/作为对外入口那就要额外处理资源前缀问题OnlyOffice对这种情况的适配需要额外配置环境变量或使用专门的DocumentServer集成版牵涉面比较大我这次就不展开讨论了。建议直接使用独立的子域名来反代省掉90%的路径兼容问题。4. 常见问题清单部署和集成阶段最容易翻车的几个点editor.bin的问题解决后我把这套环境又跑了一段时间期间陆续踩了其它几个与OnlyOffice、Docker、Nginx相关的坑。整理成一个速查表方便以后出问题直接对应。现象常见原因排查方向api.js无法访问页面白屏反代路径少了/documentserver/前缀或静态资源路径重复浏览器Network里看api.js的真实请求路径和后端日志收到的路径对比editor.bin下载失败proxy_pass尾部斜杠导致路径剥离WebSocket未配置Host头未透传看access.log的后端路径curl直接测容器检查Nginx error.log文档安全令牌的格式不正确OnlyOffice容器生成的JWT密钥与前端的token不一致容器重建后Data未持久化检查local.json里的jwt.secret确认/api/documentserver的令牌校验规则编辑完再打开提示“文件版本已更改该页面将被重新加载”WebSocket连接不稳定被重置反代超时时间太短浏览器和服务器时间偏差大检查Nginx代理超时参数确认WebSocket的Upgrade头正确传递多人协同编辑光标不同步、经常重连Nginx没有给/websocket做Upgrade转发keepalive配置不当直接websocat或浏览器观察WebSocket连接状态看是否反复101切换Docker容器反复重启Docker版本过低、内存不足、端口冲突使用docker logs查看崩溃原因适时增加--restartalways但要配合健康检查使用Docker Desktop启动时提示virtualization support not detectedWindows下Hyper-V或WSL2虚拟化功能未开启检查BIOS虚拟化开关确认Windows功能中Hyper-V、WSL2已启用4.1 关于“文档安全令牌”你需要知道的事OnlyOffice从7.x版本之后默认开启了JWT令牌校验我觉得很有必要单独说说。这本来是个安全机制但是很多人包括我一同事在自己写的程序中集成了java/springboot连接器之后经常遇到“文档安全令牌的格式不正确”或“Invalid token”之类的报错。原因在于你集成的时候前端或者后端发送给OnlyOffice的配置里需要一个token容器里存在/etc/onlyoffice/documentserver/local.json这个文件中的密钥要和token签名的密钥一致。如果你在部署容器时没有显式指定环境变量JWT_SECRET容器每次启动都会自动生成一个随机密钥导致你在代码里写死的token验证不过。解决办法很简单启动容器时指定它docker run -itd \ --name onlyoffice \ -e JWT_SECRETmy_super_secret_key_2024 \ ...然后在你的Java代码中比如你用org.onlyoffice.integration这类sdk来构造编辑器配置时签名用的密钥也要用同一个my_super_secret_key_2024。这样就不会再报令牌格式不正确了。4.2 关于Docker Desktop的兼容性提示在Windows上使用Docker Desktop跑OnlyOffice如果启动时提示virtualization support not detected之类的报错一般要检查两个方面BIOS里虚拟化技术VT-x或AMD-V是否开启。Windows功能里Hyper-V、Windows Hypervisor Platform、适用于Linux的Windows子系统WSL2是否都启用。我见过很多人卡在这一步以为Docker装好了就能跑Linux容器结果Windows层根本没把虚拟化放行。这里提醒一下改完BIOS或Windows功能后一定要重启电脑光保存设置是不够的。5. 与SpringBoot等开发环境集成时的独门建议OnlyOffice部署说到底是为了配合业务系统使用其中最常见的就是在Java/SpringBoot项目里做在线编辑文书。这一节专门补充几个开发层面的细节这些是标准Docker/Nginx部署教程里通常不会提到的。5.1 你自己的后端一定要透传正确的回调地址OnlyOffice的流程是这样的用户在前端编辑器里保存文档OnlyOffice服务器会把保存请求发到你配置的callbackUrl你的后端收到请求后再把内容持久化到自己的文件系统或数据库里。如果你的后端也走了Nginx反代那么callbackUrl必须是外部可访问的地址不能是localhost:8080或内网IP否则OnlyOffice容器那边访问不到。常见的错误写法是在生成编辑器配置时直接用Java后端的本地地址当作callbackUrl发布到服务器后OnlyOffice回调失败文档一直显示“保存中”或保存后无变化。正确的做法是把外部访问域名动态拼进callbackUrl比如String callbackUrl https://office.example.com/lanproxy/onlyoffice/callback?fileId fileId;然后Nginx里再做一个/lanproxy/onlyoffice/到Java应用的反向代理。这里有个细节要注意如果你的Java应用本身有context-path比如项目名是/oa那么callbackUrl里也要把这个路径带上前后端不一致就会回调404。5.2 Docker网络模式的选择会直接影响反代行为我在本机测试时用的-p 8080:80是桥接模式容器端口映射到了宿主机。如果要让Nginx和OnlyOffice都跑在同一个Docker网络中其实可以改用自定义网络加容器名这样Nginx的upstream写容器名也可以docker network create office-net docker run -itd --network office-net --network-alias onlyoffice --name onlyoffice -e JWT_SECRETxxx onlyoffice/documentserver docker run -itd --network office-net --name nginx -p 80:80 -v /path/nginx.conf:/etc/nginx/nginx.conf nginx此时Nginx的upstream可以写成upstream onlyoffice_backend { server onlyoffice:80; }这样甚至可以不用-p映射OnlyOffice端口减少暴露面。但我个人更推荐在真实生产环境里让OnlyOffice容器映射一个内网端口出来Nginx以宿主机作为跳板来代理这样网络链路更清晰排障时少一层容器网络干扰。5.3 控制容器日志大小防止磁盘写爆这个坑跟Docker本身有关。OnlyOffice容器会持续输出访问日志如果你Docker默认的log-driver没做大小限制时间一长/var/lib/docker/containers/xxx-json.log可能膨胀到几个GB直接把磁盘撑满届时OnlyOffice会出现各种诡异行为包括加载资源卡顿、editor.bin间歇性下载失败。在/etc/docker/daemon.json里加一段配置可以有效控制{ log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 } }然后重启Docker服务让配置生效。这个改动对所有容器都生效是“早改早受益”的那种。6. 线上环境里值得提前做的事配置改对了问题解决了事情其实还没完。OnlyOffice这套系统跟常规Web应用不同线上跑起来之后你几乎没法“等出问题再处理”因为一旦出问题用户手里的文档可能正打开着。我觉得有必要把几个线上化的建议一起写出来这也是我这次踩坑后复盘出来的备份动作。6.1 给容器加健康检查与自动拉起虽然--restartalways能保证Docker守护进程挂着时容器挂了会自动重启但如果容器处于“假死”状态进程还在、端口也监听但实际上内部服务已经没法正常响应了--restartalways是识别不出来的。一个更好的做法是配置OnlyOffice容器自己的健康检查。官方文档推荐用docker inspect --format{{json .State.Health}} onlyoffice但前提是你启动容器时定义了健康检查。可以直接在docker run时加docker run ... \ --health-cmdcurl -f http://localhost/healthcheck \ --health-interval30s \ --health-timeout5s \ --health-retries3 \ --health-start-period60s \ ...这样Docker就会周期性地探测/healthcheck接口一旦持续失败容器状态会变成unhealthy你再配合外部的监控服务比如Prometheus的docker exporter或简单点的云监控脚本就能及时收到告警。6.2 给Nginx加一个兜底错误页很多时候editor.bin下载失败用户看到的是一个白屏或者浏览器默认的错误页面既不知道联系谁也不知道发生了什么。我后来在Nginx配置里加了一个简单的自定义错误页一旦后端不可用至少能返回一段明确的话术location /documentserver/editor.bin { proxy_pass http://onlyoffice_backend; proxy_intercept_errors on; error_page 502 503 504 onlyoffice_down; } location onlyoffice_down { default_type application/json; return 200 {code:503,message:Sorry, the document service is temporarily unavailable. Please try again later.}; }这样前端至少能在代码里捕获到明确的错误信息而不是被浏览器拦截报错。这个思路也可以推广到其它关键资源路径上没必要等用户一窝蜂反馈“打不开文档”之后才去查日志。6.3 从这次排障里学到的一条通用排查方法我想借这次经历把一套比较通用的反代排查顺序分享出来以后遇到任何“通过Nginx访问出问题、直连后端却正常”的场景都按这个顺序来先用curl带同样的Host头、协议、路径直连后端确认后端本身是否就绪。看Nginx的access.log对比浏览器请求的URL和后端实际收到的URL路径是否一致。看后端日志或响应特征判断请求是否真的到达了预期服务。检查Nginx的error.log里的connect失败、超时、重写跳转记录。逐个排查代理头Host、X-Forwarded-Proto、X-Forwarded-For是否完备。确认Nginx与后端之间的网络链路同一台机器不同容器不同机器跨网段。只要你耐下心走完这一遍绝大多数的“反代后变白屏/下载失败/不断重连”问题都能定位到具体节点而不是在整份配置里大海捞针。7. 写在最后这套方案后续还能怎么调整可能有人会问既然Nginx反代OnlyOffice这么折腾那是不是直接不要Nginx用端口直连更稳我的看法是如果你只是在局域网里自用当然可以省掉Nginx但只要涉及到公网访问、HTTPS、多域名隔离、安全防护Nginx这层几乎躲不掉。与其绕开它不如把这套转发规则彻底吃透。另外如果你使用的是更复杂的场景比如Nginx本身还承载了其它业务域名那么建议把OnlyOffice的server块单独拆成一个配置文件用include方式加载方便独立维护和回滚。Nginx的reload是平滑的线上调整配置并不会中断已有连接这一点非常方便比改Docker环境变量再重启容器要稳妥得多。我个人在实际操作中的体会是OnlyOffice这套东西的稳定性一半靠容器运行环境一半靠代理层的转发是否足够“透明”。所谓透明就是不要让经过Nginx的请求在外部看来和直连后端有什么实质差别。路径别乱砍、协议头别丢、WebSocket别漏升级能做到这三点基本上就不会再出现像我这次editor.bin下载失败这类低级但磨人的现象了。如果后续你在这个基础上要继续扩展比如接入分布式存储、对接企业微信或钉钉的在线预览、或者在多节点之间做负载均衡只要掌握了这一套转发原理扩展起来都只是增加upstream数量或调整location匹配优先级的问题不会再有从零开始的恐慌感。真要说还有什么建议那就是在正式上生产环境之前一定不要嫌麻烦单独搭一台测试机把从编辑器打开、协同编辑、保存回流的完整链路验证一遍再切流量。这几十分钟的验证能省下后面无数个“用户报障你查半天”的夜晚。