You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TLS-WebSocket连接失败报tls: unknown certificate错误求助

问题根因

WebSocket连接失败是两个独立配置错误共同导致的:

  1. Nginx未配置后端代理规则
    你当前的Nginx配置里仅在location /块中声明了WebSocket升级需要的协议头,但是没有配置proxy_pass将请求转发到Go服务监听的4000端口,反而配置了try_files直接返回本地静态文件,所有请求到Nginx就直接处理完了,根本不会到达后端Go服务,WebSocket自然不可能建立成功。
    另外你原来写的try_files $uri $uri/ =404对Svelte这类单页应用也不兼容,前端路由刷新会直接返回404。

  2. Go服务TLS证书链不完整
    你看到的http: TLS handshake error from [...] : remote error: tls: unknown certificate报错,是因为Go服务加载的是Let's Encrypt的cert.pem文件,这个文件仅包含站点的叶子证书,缺少Let's Encrypt的中间CA证书,客户端TLS握手时无法将证书回溯到受信任的根CA,就会判定证书未知不可信。你Nginx配置中使用的fullchain.pem才是包含完整证书链的文件,直接对外提供TLS服务时必须使用这个文件。


推荐解决方案(Nginx统一TLS终结,最易维护)

不要让Go服务直接对外暴露端口、单独处理TLS,统一由Nginx做443端口的TLS终结,静态资源由Nginx直接返回,WebSocket/API请求反向代理到本地监听的Go服务即可,Go服务不需要处理TLS,也不用单独维护证书,后续Let's Encrypt证书续期也不需要调整Go服务配置。

  1. 调整Go服务代码,仅监听本地回环地址,关闭TLS:
func (s Server) Init() {
    s.Sessions = make(map[string]*Session)
    http.HandleFunc("/", s.handler)
    // 仅监听本地127.0.0.1的4000端口,不对外暴露,不需要TLS
    err := http.ListenAndServe("127.0.0.1:4000", nil)
    if err != nil {
        fmt.Println(err)
    }
}
  1. 调整Nginx配置,区分静态资源和代理请求:
server {
    root /home/ubuntu/actions-runner/_work/client/client/public;
    index index.html index.htm index.nginx-debian.html;
    server_name myserver.io www.myserver.io;

    # 静态资源直接由Nginx返回,SPA路由统一回退到index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # WebSocket请求反代到本地Go服务,此处假设你的WebSocket服务挂载在/ws路径
    # 如果你的WebSocket服务直接挂载在根路径,调整location匹配规则优先级即可
    location /ws {
        proxy_pass http://127.0.0.1:4000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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;
    }

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/myserver.io/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/myserver.io/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {
    if ($host = www.myserver.io) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    if ($host = myserver.io) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80 ;
    listen [::]:80 ;
    server_name myserver.io www.myserver.io;
    return 404; # managed by Certbot
}
  1. 调整前端Svelte客户端的WebSocket连接地址,不要直接带4000端口连接Go服务,直接连接同域的反代路径即可,比如wss://myserver.io/ws,走默认443端口由Nginx处理。

备选方案(Go服务直接对外提供TLS)

如果因为特殊场景必须让Go直接监听4000端口对外提供TLS服务,只需要把Go代码中加载的证书文件从tls/cert.pem换成你Nginx用的fullchain.pem即可,同时保证运行Go服务的系统用户有读取Let's Encrypt证书目录的权限,否则会因为读不到证书文件启动失败。这个方案需要注意Let's Encrypt证书续期后Go服务要同步加载新证书,维护成本更高,不推荐使用。


内容的提问来源于stack exchange,提问作者Max Beier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 09:30:54