amir20/dozzle · error

err.Error()

Error message

err.Error()

What it means

streamEvents fails with a 500 when support_web.NewSSEWriter cannot initialize the Server-Sent Events writer, most commonly because the http.ResponseController/Flusher or streaming is unavailable (e.g. response already hijacked/committed, or a proxy interferes with the connection setup). The raw error is returned in the body.

Solutions

  1. Configure the reverse proxy to disable response buffering for /api/ endpoints (proxy_buffering off / X-Accel-Buffering: no).
  2. Retry the /api/events/stream connection; transient disconnects are handled by the frontend reconnect logic.
  3. Ensure clients connect over HTTP/1.1+ or HTTP/2 where streaming is supported.
Defensive patterns

Strategy: retry

Try / catch

const es = new EventSource('/api/events/stream');
es.onerror = () => { es.close(); setTimeout(connect, backoffMs()); };

Prevention

When it happens

Trigger: GET /api/events/stream when the underlying response writer cannot be set up for SSE streaming (flusher unavailable, context already canceled, or the connection broke during setup).

Common situations: Reverse proxies buffering responses or rejecting upgrade-style streaming; client disconnecting exactly during connect; HTTP/1.0 clients or gateways that do not support chunked streaming.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07). Data as JSON: /api/errors/b07bb5c2b697e27c. Report an issue: GitHub.

Appendix: source

Thrown at internal/web/events.go:27

	"github.com/amir20/dozzle/internal/container"
	docker_support "github.com/amir20/dozzle/internal/support/docker"
	support_web "github.com/amir20/dozzle/internal/support/web"
	"github.com/amir20/dozzle/types"
	"github.com/rs/zerolog/log"
)

const (
	eventBufferSize = 64
	statBufferSize  = 128
	// minimum gap between retries of a host whose container list failed to refresh
	staleHostRetryInterval = 5 * time.Second
)

func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
	sseWriter, err := support_web.NewSSEWriter(r.Context(), w, r)
	if err != nil {
		log.Error().Err(err).Msg("error creating sse writer")
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer sseWriter.Close()

	// buffered so a momentarily slow client can't stall the shared per-host store loop,
	// which broadcasts to every subscriber with a blocking send
	events := make(chan container.ContainerEvent, eventBufferSize)
	stats := make(chan container.ContainerStat, statBufferSize)
	availableHosts := make(chan container.Host)

	h.hostService.SubscribeEventsAndStats(r.Context(), events, stats)
	h.hostService.SubscribeAvailableHosts(r.Context(), availableHosts)

	userLabels := h.config.Labels
	if h.config.Authorization.Provider != NONE {
		user := auth.UserFromContext(r.Context())
		if user.ContainerLabels.Exists() {
			userLabels = user.ContainerLabels

View on GitHub (pinned to d9463cbe21)