cloudflare/cloudflared · warning

error setting read deadline

Error message

error setting read deadline: %w

What it means

GorillaConn.SetDeadline implements net.Conn by delegating to the underlying gorilla websocket connection. This error wraps a failure from SetReadDeadline, which happens when the underlying connection's deadline setter reports the connection is already closed or in an unusable state.

Solutions

  1. Check whether the connection was already closed — stop deadline timers on connection shutdown
  2. Ignore/treat as benign errors of type net.ErrClosed or use-after-close in deadline reset paths
  3. Ensure Close() cancels the goroutines/timers that call SetDeadline before closing
  4. If gorilla returns 'use of closed network connection', rebuild the connection rather than retrying the deadline

Example fix

// before
if err := conn.SetDeadline(time.Now().Add(pongWait)); err != nil {
    return err // aborts even during shutdown
}
// after
if err := conn.SetDeadline(time.Now().Add(pongWait)); err != nil {
    if errors.Is(err, net.ErrClosed) {
        return nil // connection is shutting down
    }
    return fmt.Errorf("set deadline: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

func canSetDeadline(c net.Conn) bool {
    return c.SetDeadline(time.Now()) == nil || !errors.Is(c.SetDeadline(time.Now()), net.ErrClosed)
}

Try / catch

if err := conn.SetDeadline(t); err != nil {
    if errors.Is(err, net.ErrClosed) || strings.Contains(err.Error(), "use of closed") {
        return nil // expected during shutdown
    }
    return err
}

Prevention

When it happens

Trigger: Calling SetDeadline (directly or via net.Conn plumbing like conn.SetDeadline in proxy/ssh/rdp over websocket code) after the websocket has been closed or the underlying TCP conn has errored.

Common situations: Deadline refresh timers racing connection shutdown; one side closed the tunnel while keepalive/ping loops still reset deadlines; use-after-close in proxy streams.

Related errors


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/71e96bb0528360b4. Report an issue: GitHub.

Appendix: source

Thrown at websocket/connection.go:73

	return copied, nil
}

// Write will write messages to the websocket connection
func (c *GorillaConn) Write(p []byte) (int, error) {
	if err := c.Conn.WriteMessage(websocket.BinaryMessage, p); err != nil {
		return 0, err
	}

	return len(p), nil
}

// SetDeadline sets both read and write deadlines, as per net.Conn interface docs:
// "It is equivalent to calling both SetReadDeadline and SetWriteDeadline."
// Note there is no synchronization here, but the gorilla implementation isn't thread safe anyway
func (c *GorillaConn) SetDeadline(t time.Time) error {
	if err := c.Conn.SetReadDeadline(t); err != nil {
		return fmt.Errorf("error setting read deadline: %w", err)
	}
	if err := c.Conn.SetWriteDeadline(t); err != nil {
		return fmt.Errorf("error setting write deadline: %w", err)
	}
	return nil
}

type Conn struct {
	rw  io.ReadWriter
	log *zerolog.Logger
	// writeLock makes sure
	// 1. Only one write at a time. The pinger and Stream function can both call write.
	// 2. Close only returns after in progress Write is finished, and no more Write will succeed after calling Close.
	writeLock sync.Mutex
	done      bool
}

func NewConn(ctx context.Context, rw io.ReadWriter, log *zerolog.Logger) *Conn {

View on GitHub (pinned to 2253eeeb25)