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
- Configure the reverse proxy to disable response buffering for /api/ endpoints (proxy_buffering off / X-Accel-Buffering: no).
- Retry the /api/events/stream connection; transient disconnects are handled by the frontend reconnect logic.
- 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
- Disable proxy buffering for /api/ SSE endpoints.
- Avoid proxies that terminate streaming connections aggressively.
- Implement exponential backoff reconnects on the client.
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
- Unauthorized user
- Failed to save alert
- Preview failed
- cloud search failed
- No reader available from stream
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.ContainerLabelsView on GitHub (pinned to d9463cbe21)