Timeout middleware for Fiber wraps handlers with a timeout. When a handler execution exceeds the specified timeout duration, the request is canceled and an error is returned.
Signatures
func New(handler fiber.Handler, timeout time.Duration, timeoutErrors ...error) fiber.Handler
func NewWithContext(handler fiber.Handler, timeout time.Duration, timeoutErrs ...error) fiber.Handler
Usage
import (
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/timeout"
"time"
)
Basic Usage
app.Get("/", timeout.New(func(c fiber.Ctx) error {
// Simulate long operation
time.Sleep(5 * time.Second)
return c.SendString("Hello, World!")
}, 3*time.Second))
// Request will timeout after 3 seconds
Global Timeout Middleware
app.Use(timeout.New(func(c fiber.Ctx) error {
return c.Next()
}, 30*time.Second))
app.Get("/api/users", getUsers)
app.Get("/api/products", getProducts)
// All routes will have 30 second timeout
Custom Timeout Handler
app.Get("/slow", timeout.New(
slowHandler,
5*time.Second,
)))
With Custom Timeout Errors
import "errors"
var ErrDatabaseTimeout = errors.New("database timeout")
app.Get("/data", timeout.New(
dataHandler,
10*time.Second,
ErrDatabaseTimeout,
))
Configuration
Next
func(fiber.Ctx) bool
default:"nil"
Defines a function to skip this middleware when it returns true.
OnTimeout
fiber.Handler
default:"nil"
Executed when a timeout occurs. If not set, returns fiber.ErrRequestTimeout.
Defines custom errors that are treated as timeouts. When the handler returns one of these errors, it’s handled as a timeout.
Defines the timeout duration for all routes. If set to 0, no timeout is applied.
Default Config
var ConfigDefault = Config{
Next: nil,
Timeout: 0,
OnTimeout: nil,
Errors: nil,
}
Common Use Cases
API Request Timeout
api := app.Group("/api")
api.Use(timeout.New(func(c fiber.Ctx) error {
return c.Next()
}, 15*time.Second))
api.Get("/users", getUsers)
api.Post("/users", createUser)
Custom Timeout Response
app.Get("/process", timeout.NewWithContext(
processHandler,
10*time.Second,
)).Use(func(c fiber.Ctx) error {
err := c.Next()
if err == fiber.ErrRequestTimeout {
return c.Status(503).JSON(fiber.Map{
"error": "Request processing took too long",
"retry": true,
})
}
return err
})
Database Query Timeout
func getUserWithTimeout(c fiber.Ctx) error {
return timeout.New(func(c fiber.Ctx) error {
// Database query
user, err := db.GetUser(c.Params("id"))
if err != nil {
return err
}
return c.JSON(user)
}, 5*time.Second)(c)
}
app.Get("/users/:id", getUserWithTimeout)
Skip Timeout for Specific Routes
app.Use(func(c fiber.Ctx) error {
// Skip timeout for webhooks
if strings.HasPrefix(c.Path(), "/webhooks") {
return c.Next()
}
return timeout.New(func(c fiber.Ctx) error {
return c.Next()
}, 30*time.Second)(c)
})
Long-Running Tasks
app.Post("/reports/generate", timeout.New(
func(c fiber.Ctx) error {
// Generate report asynchronously
jobID := generateReportAsync()
return c.JSON(fiber.Map{
"job_id": jobID,
"status": "processing",
})
},
2*time.Second, // Quick response, actual work happens async
))
app.Get("/reports/:job_id", func(c fiber.Ctx) error {
status := checkJobStatus(c.Params("job_id"))
return c.JSON(status)
})
Streaming with Timeout
app.Get("/stream", timeout.New(
func(c fiber.Ctx) error {
c.Set("Content-Type", "text/event-stream")
c.Set("Cache-Control", "no-cache")
c.Set("Connection", "keep-alive")
// Stream data
for i := 0; i < 10; i++ {
fmt.Fprintf(c, "data: %d\n\n", i)
c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(
func(w *bufio.Writer) {
w.Flush()
},
))
time.Sleep(1 * time.Second)
}
return nil
},
15*time.Second,
))
Different Timeouts per Route Group
// Fast endpoints - 5 second timeout
fast := app.Group("/fast")
fast.Use(timeout.New(func(c fiber.Ctx) error {
return c.Next()
}, 5*time.Second))
// Slow endpoints - 30 second timeout
slow := app.Group("/slow")
slow.Use(timeout.New(func(c fiber.Ctx) error {
return c.Next()
}, 30*time.Second))
Best Practices
Set Reasonable Timeouts
// Too short - may timeout legitimate requests
app.Use(timeout.New(handler, 100*time.Millisecond)) // ❌
// Reasonable - enough time for most operations
app.Use(timeout.New(handler, 30*time.Second)) // ✅
// Too long - defeats the purpose
app.Use(timeout.New(handler, 5*time.Minute)) // ❌
Handle Timeout Gracefully
app.Use(func(c fiber.Ctx) error {
err := c.Next()
if err == fiber.ErrRequestTimeout {
log.Printf("Request timeout: %s %s", c.Method(), c.Path())
return c.Status(504).JSON(fiber.Map{
"error": "Gateway Timeout",
"message": "The server took too long to respond",
})
}
return err
})
Combine with Context Cancellation
app.Get("/data", timeout.NewWithContext(
func(c fiber.Ctx) error {
ctx := c.Context()
select {
case <-ctx.Done():
return ctx.Err()
case result := <-fetchData():
return c.JSON(result)
}
},
10*time.Second,
))
Monitor Timeout Occurrences
var timeoutCounter int64
app.Use(func(c fiber.Ctx) error {
err := c.Next()
if err == fiber.ErrRequestTimeout {
atomic.AddInt64(&timeoutCounter, 1)
log.Printf("Total timeouts: %d", atomic.LoadInt64(&timeoutCounter))
}
return err
})
Environment-Based Timeouts
timeout := 30 * time.Second
if os.Getenv("ENV") == "development" {
timeout = 5 * time.Minute // Longer timeout for debugging
}
app.Use(timeout.New(func(c fiber.Ctx) error {
return c.Next()
}, timeout))
Notes
- Timeout middleware should be registered before other middleware and routes
- When a timeout occurs, the handler is interrupted and
fiber.ErrRequestTimeout is returned
- Use
NewWithContext when you need context cancellation support
- Timeouts don’t prevent the handler from running; they just stop waiting for it
- Consider using context cancellation in handlers to properly clean up resources
- Setting
Timeout: 0 disables timeout protection
- Streaming responses may need longer timeouts or no timeout at all