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

仅用Nginx静态服务器部署SSE异常:连接频繁断开重连排查

Nginx实现SSE时连接自动断开重连的问题解决

我花了一整天尝试用Nginx实现Server-sent-events(SSE),先是不通过proxy_pass直接实现,后来又试了proxy_pass,但用JavaScript的EventSource连接端点时,收到第一个事件后连接就断开并自动重连,完全失去了SSE长连接的意义,跟轮询没区别。我的需求是在生产环境部署静态Nginx服务器时,通过SSE发送当前代码提交哈希(这个哈希会在Docker构建或运行时写入Nginx配置)。下面是我接近可行的配置,不管用不用proxy_pass,两个端点都存在同样的问题:

Nginx配置

# load_module /usr/lib/nginx/modules/ngx_http_headers_more_filter_module.so;
# user       www www;  ## Default: nobody
worker_processes  1;
# error_log  logs/error.log;
# pid        logs/nginx.pid;
# worker_rlimit_nofile 8192;

events {
    worker_connections 1024;
}

http {
    # send_timeout 1800;
    # keepalive_timeout 10m;
    # client_max_body_size 128M;
    # proxy_max_temp_file_size 0;
    # proxy_buffering off;
    # server_names_hash_bucket_size 256;

    server {
        listen 8080;
        server_name localhost;
        # index index.html;
        # root /usr/share/nginx/html;

        location = /sse {
            proxy_set_header Host $host;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_pass http://proxy;
            proxy_buffering off;
            proxy_request_buffering off;
            proxy_cache off;

            # proxy_connect_timeout 159s;
            # proxy_send_timeout   600;
            # proxy_read_timeout   600;
            # proxy_buffer_size    64k;
            # proxy_buffers     16 32k;
            # proxy_busy_buffers_size 64k;
            # proxy_temp_file_write_size 64k;
            fastcgi_keep_conn on;

            # add_header Connection "keep-alive";
            # add_header X-Accel-Buffering "no";
            # chunked_transfer_encoding on;
            # more_clear_headers "Content-Length";
            # default_type text/event-stream;
        }
    }

    server {
        listen 8081;
        server_name localhost;

        location = /sse {
            # add_header Connection "keep-alive";
            # expires off;
            # default_type text/event-stream;
            default_type "";
            add_header Content-Type "text/event-stream; charset=utf-8";
            add_header Cache-Control "no-store";
            add_header Access-Control-Allow-Origin "*";
            chunked_transfer_encoding off;
            push_stream_eventsource_support on;
            
            add_header X-Accel-Buffering "no";
            # chunked_transfer_encoding off;
            # keepalive_timeout 100s;
            return 200 "event: ping\ndata: {\"time\": \"foo\"}\n\n";
        }
    }

    upstream proxy {
        server 127.0.0.1:8081;
    }
}

JavaScript客户端代码

document.addEventListener('DOMContentLoaded', () => {
  const source = new EventSource('http://localhost:8080/sse');

  source.addEventListener(
    'open',
    function (e) {
      console.log('Connection was opened.', e.readyState);
    },
    false
  );

  source.addEventListener(
    'error',
    function (e) {
      console.log(e.readyState, e);
      if (e.readyState == EventSource.CLOSED) {
        // Connection was closed.
      }
    },
    false
  );

  source.addEventListener('ping', function (event) {
    console.log('ping', event);
  });
});

现象

控制台先输出Connection was opened.,接着能成功收到ping消息,但3秒后这个过程会重复,连接是从Nginx端主动关闭的。用小型express.js服务器测试SSE是正常工作的。


问题原因与解决办法

核心原因

Nginx的return指令会一次性发送完响应后立刻关闭连接,而SSE依赖长连接来持续传输事件。EventSource检测到连接关闭后,会触发默认的自动重连机制,导致你看到的“轮询”现象。

解决步骤

1. 移除return,改用持续输出的方式

要维持SSE长连接,不能用return返回一次性响应。如果你的需求是发送一次提交哈希后,用心跳维持连接,可以借助Nginx的echo模块(需提前安装/加载)来实现:

修改8081端口的/sse location配置:

location = /sse {
    default_type text/event-stream;
    add_header Cache-Control "no-store";
    add_header Access-Control-Allow-Origin "*";
    add_header X-Accel-Buffering "no";
    proxy_buffering off;
    chunked_transfer_encoding on;
    keepalive_timeout 65;

    # 发送初始的提交哈希事件
    echo "event: commit\ndata: {\"hash\": \"YOUR_COMMIT_HASH_HERE\"}\n\n";
    echo_flush;

    # 每隔30秒发送心跳,维持长连接
    echo_while true;
        echo_delay 30;
        echo "event: heartbeat\ndata: {\"time\": \"$(date +%Y-%m-%dT%H:%M:%S)\"}\n\n";
        echo_flush;
    echo_end;
}

这里用echo_while true循环发送心跳,确保连接不会被Nginx主动关闭。

2. 优化proxy_pass配置(如果需要代理)

如果必须使用proxy_pass,调整前端Nginx的代理配置,确保长连接被正确维持:

location = /sse {
    proxy_set_header Host $host;
    proxy_http_version 1.1;
    proxy_set_header Connection "keep-alive"; # 保持长连接,不要设为空
    proxy_pass http://proxy;
    proxy_buffering off;
    proxy_request_buffering off;
    proxy_cache off;
    proxy_read_timeout 86400; # 延长读取超时,避免Nginx主动断开
    proxy_send_timeout 86400;
    add_header X-Accel-Buffering "no"; # 禁止缓冲
}

同时确保后端(8081端口)的配置已经改成上述持续输出的模式。

3. 关键配置要点

  • 必须设置X-Accel-Buffering: no,禁止Nginx缓冲响应,确保事件实时推送。
  • 禁用proxy_buffering和proxy_request_buffering,防止缓冲导致连接提前关闭。
  • 设置足够长的proxy_read_timeout和keepalive_timeout,避免Nginx因超时主动断开长连接。
  • 避免使用return、rewrite这类一次性返回响应的指令,必须让服务器持续输出内容来维持连接。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 02:59:54