Skip to main content
Version: v3_uptime_v0.x.x

Uptime

Release Discord Test

Uptime middleware for Fiber that records in-process heartbeat history and serves a lightweight status page.

Compatible with Fiber v3.

Preview

Uptime dashboard preview

Install

go get -u github.com/gofiber/fiber/v3
go get -u github.com/gofiber/contrib/v3/uptime
go get -u github.com/gofiber/storage/redis/v3

Signature

uptime.New(config ...uptime.Config) fiber.Handler
uptime.RemoveService(ctx context.Context, store *fiberredis.Storage, keyPrefix, serviceID string) error

Basic usage

package main

import (
"github.com/gofiber/contrib/v3/uptime"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/log"
fiberredis "github.com/gofiber/storage/redis/v3"
)

func main() {
app := fiber.New()
store := fiberredis.New()
app.Hooks().OnPostShutdown(func(_ error) error {
return store.Close()
})

app.Use(uptime.New(uptime.Config{
App: app,
Store: store,
ServiceID: "api",
ServiceName: "API",
}))

app.Get("/", func(c fiber.Ctx) error {
return c.SendString("ok")
})

log.Fatal(app.Listen(":3000"))
}

Open:

  • http://localhost:3000/uptime
  • http://localhost:3000/uptime/api/status

Endpoint probes

By default, uptime records a heartbeat for the current process identified by ServiceID. You can also configure HTTP endpoints. Each endpoint is shown as a separate service on the dashboard and in the JSON API.

app.Use(uptime.New(uptime.Config{
App: app,
Store: store,
Endpoints: []uptime.EndpointConfig{
{
ID: "api-health",
Name: "API Health",
URL: "https://api.example.com/health",
Method: "GET",
Interval: 10 * time.Second,
Timeout: 3 * time.Second,
ExpectedStatusCodes: []int{200, 204},
},
},
}))

When ExpectedStatusCodes is empty, any 2xx or 3xx response is considered up. Failed probes do not write heartbeat slots, so the endpoint naturally moves to yellow, red, or down as slots are missed.

Every instance runs its own probes, so N replicas sharing a StorageKeyPrefix send N times the probe traffic to each target. A slot counts as up when any replica saw the endpoint up.

The first probe runs when New is called, before app.Listen has opened the port. Endpoints pointing at the app's own routes therefore miss their first slot or two on startup.

Redis

Redis state is stored through github.com/gofiber/storage/redis/v3. Create the Fiber Redis storage in your app and pass it to the uptime config. By default, that storage package connects to 127.0.0.1:6379 and database 0.

store := fiberredis.New(fiberredis.Config{
Addrs: []string{"127.0.0.1:6379"},
Password: "",
Database: 0,
})
app.Hooks().OnPostShutdown(func(_ error) error {
return store.Close()
})

app.Use(uptime.New(uptime.Config{
App: app,
Store: store,
ServiceID: "api",
StorageKeyPrefix: "fiber:uptime",
}))

The middleware uses Conn() from the Fiber Redis storage to run the uptime queries it needs. Use a dedicated Redis database or a distinct StorageKeyPrefix when multiple environments share the same Redis server. The caller owns the Redis storage lifecycle. Close the Redis storage after the Fiber app has shut down, for example from OnPostShutdown, so uptime can stop its background recorders first.

When multiple uptime instances share the same Redis database and StorageKeyPrefix, use the same Timezone and heartbeat or probe interval for the same ServiceID or endpoint ID. If those settings need to differ, use distinct service IDs, endpoint IDs, or storage prefixes.

Key expiry

Service, sample and daily keys carry a TTL of RetentionDays + 1 days that is re-armed on every write. Background maintenance normally removes them long before that, so the TTL only matters once no process is left to run the cleanup: a decommissioned service, a changed key prefix, an app that never comes back. Without it those keys would stay in Redis forever.

The services registry set is shared by all instances under a prefix and is never expired. A service that has never recorded a successful heartbeat also keeps its (small) service hash without a TTL. Use RemoveService to clear either.

Removing a service

When using the built-in Redis Store path, the dashboard lists every service found under StorageKeyPrefix, not just the ones in the current config. Services are never dropped automatically while their retention window keeps being refreshed, so renaming ServiceID or an endpoint ID, or removing an endpoint, leaves the old identifier behind as a row that reports down forever. Delete it explicitly:

err := uptime.RemoveService(context.Background(), store, "fiber:uptime", "old-endpoint-id")

Pass the same StorageKeyPrefix the middleware uses, or "" for the default. This removes the service, its history and its raw samples.

Remove the service from your config and restart before calling it. Calling it while the service is still being recorded takes it off the dashboard for good, but in-flight heartbeats will recreate partial keys; those carry the TTL above and expire on their own.

RemoveService applies to the built-in Redis path. Custom storage backends may expose their own removal operation.

Custom storage

For most users, the built-in Redis Store path remains the simplest integration. Storage is the advanced extension point for applications that need a custom persistence backend. Provide an implementation of github.com/gofiber/contrib/v3/uptime/storage.Store through Storage:

app.Use(uptime.New(uptime.Config{
App: app,
Storage: myStore,
ServiceID: "api",
}))

Custom stores must be ready before calling uptime.New and safe for concurrent use. The caller owns their lifecycle and should close underlying resources after the Fiber application has shut down; uptime does not close custom stores. Store and Storage are mutually exclusive, and StorageKeyPrefix applies only to the built-in Redis Store path. Redis remains the built-in storage integration.

Snapshots and custom UI

The dashboard and JSON API share an in-memory snapshot, limiting backing-store work on public status routes. It is held for one SampleInterval, or for the shortest interval any service in it is tracked at when an endpoint is probed faster than that, so a snapshot is never served past the point where its own current_status would have changed.

If a refresh fails, the last snapshot is served with storage.status set to degraded and the same fixed last_error label the live status uses; the backing store's own message goes to the log rather than the public payload. A failed refresh restarts the interval like a successful one, so an outage cannot turn every request into another attempt against the unavailable store.

The same snapshot payload is available at UI.Path + "/api/status" for custom dashboards.

Service insights

Each service card has an Insights button, collapsed by default. Expanding it shows four metrics and a daily availability trend for the same DaysToShow window as the existing uptime bars:

  • Availability: total up slots divided by total expected slots across valid days, rather than an average of daily percentages.
  • Downtime: the sum of the daily estimated downtime.
  • Affected days: valid days with at least one missed expected slot.
  • Stable streak: consecutive perfect days backward from the newest valid day, including today when every completed expected slot so far is up. Newest no-data days are skipped; an internal no-data gap or imperfect day stops the streak. N+ days means it reaches the window boundary and the service existed before that window, so the streak may be longer.

Days without data or expected slots do not contribute to the metrics and are not treated as downtime. With no valid days, all four metrics display , and the trend displays No data. Missing days create gaps in the trend.

Expanded cards stay expanded across automatic refreshes; reloading the page collapses them again. Charts are rendered only while expanded. The trend uses native SVG and Vanilla JavaScript, with zero additional dependencies, storage queries, or persistent data. Existing daily bars remain keyboard accessible.

Each service in the status JSON also includes a summary object with has_data, availability_rate, estimated_downtime_seconds, affected_days, stable_streak_days, and stable_streak_capped. When has_data is false, the numeric fields are zero and stable_streak_capped is false; clients should display no data rather than 0% availability. These metrics add no latency, incident, or alerting semantics and require no new configuration.

Dashboard favicon

The built-in dashboard includes an embedded favicon by default. Set UI.FaviconURL to override it with either a root-relative path served by the same application or an absolute HTTP(S) URL:

app.Use(uptime.New(uptime.Config{
App: app,
Store: store,
ServiceID: "api",
UI: uptime.UIConfig{
FaviconURL: "/assets/favicon.svg",
},
}))

Filesystem paths such as ./favicon.ico are not supported directly. Expose a local file through a Fiber route or static handler, then configure its URL. Remote favicon URLs cause each dashboard visitor's browser to contact that remote host, so a same-origin URL is preferred for private deployments.

Config

PropertyTypeDescriptionDefault
App*fiber.AppFiber app used to register the shutdown hook that closes the uptime runtime.Required
Nextfunc(fiber.Ctx) boolSkip the uptime handler when true.nil
ServiceIDstringStable service identifier for the current process. Required only when Endpoints is empty.""
ServiceNamestringDisplay name.ServiceID
ServiceDescriptionstringDisplay description.""
Endpoints[]uptime.EndpointConfigOptional HTTP endpoints to probe as tracked services.nil
SampleIntervaltime.DurationHeartbeat interval.3 * time.Second
RetentionDaysintNumber of days to retain daily history. Also sets the key expiry backstop.90
DaysToShowintNumber of days shown in snapshots and dashboard.30
Timezone*time.LocationTimezone for day and slot boundaries.time.Local
NodeIDint64Optional node value used for generated instance IDs.0
InstanceIDint64Explicit process instance ID.Generated
IDGeneratoruptime.IDGeneratorCustom instance ID generator.nil
Store*fiberredis.StorageFiber Redis storage instance from github.com/gofiber/storage/redis/v3.Required unless Storage is set.
Storagestorage.StoreCustom uptime storage backend.Required unless Store is set.
StorageKeyPrefixstringPrefix for uptime Redis keys. Applies only when Store is used."fiber:uptime"
UIuptime.UIConfigDashboard copy, favicon, and thresholds. FaviconURL accepts a root-relative path or absolute HTTP(S) URL. Threshold values are configurable in (0, 1]; zero uses the defaults.Embedded favicon, light English UI, green at 99.9%, yellow at 99%

EndpointConfig

PropertyTypeDescriptionDefault
IDstringStable endpoint identifier.Required
NamestringDisplay name.ID
DescriptionstringDisplay description.""
URLstringAbsolute http or https URL to probe.Required
MethodstringHTTP method used for the probe.GET
Headersmap[string]stringOptional request headers sent with each probe.nil
ExpectedStatusCodes[]intStatus codes that mark the endpoint up. Empty means any 2xx or 3xx.nil
Intervaltime.DurationEndpoint heartbeat interval.Config.SampleInterval
Timeouttime.DurationMaximum duration for one probe.5 * time.Second

Handler behavior

The Fiber handler serves:

  • /uptime
  • /uptime/
  • /uptime/api/status

GET and HEAD are supported. Other methods return 405 Method Not Allowed. Requests outside UI.Path, including unknown uptime subpaths, are passed to the next handler.

The handler matches request paths against UI.Path (default /uptime). When mounted under a Fiber group or Use prefix, the match is relative to that mount point.

The middleware does not read request bodies, capture response bodies, or wrap business handlers. Process heartbeats and endpoint probes are run by background tickers owned by the uptime runtime.

Performance notes

Store writes and endpoint probes happen on background tickers, not on every business request. Status requests read the backing store to build the response. Use Fiber's cache middleware if the dashboard or JSON API should be cached.

Concurrency safety

Uptime middleware instances are safe for concurrent use after construction. The snapshot payload is built from fresh store reads. Redis commands are issued through the Fiber Redis storage connection and are safe for concurrent use by the background recorder and Fiber handlers.

Config.App is required so New can register a Fiber shutdown hook that stops the uptime runtime. The caller owns the Redis storage lifecycle and should close it after the app has shut down, for example from OnPostShutdown or a signal handler that closes Redis after app.Shutdown returns.

Security notes

Mount the dashboard on an internal or protected route when uptime history should not be public. The middleware does not log request bodies, response bodies, authorization headers, cookies, or query strings. Endpoint probe response bodies are closed without being read. Avoid putting secrets in endpoint URLs because URLs may still appear in upstream infrastructure logs outside this middleware.