Self-host OpenUptime
Run your own copy on Cloudflare Workers or in Docker. Both use the same code and a Postgres database you control.
Pick one:
- Docker: one container plus Postgres, on any machine. Quickest to try.
- Cloudflare Workers: two Workers, a Queue and Hyperdrive in front of Postgres. Runs checks from Cloudflare's network.
Docker
One container runs the API, the web app and the check scheduler. The compose file also starts Postgres.
Requirements
- Docker with Compose v2
- A machine that can reach the services you want to monitor
Start it
git clone <your copy of the repository> openuptime
cd openuptime
cp .env.docker.example .env
# edit .env: set POSTGRES_PASSWORD and BETTER_AUTH_SECRET (openssl rand -hex 32)
docker compose up -d --buildOpen http://localhost:8080 and sign up. The schema is migrated automatically on every start and the data lives in the pgdata volume.
Put it on a domain
Run it behind a reverse proxy that terminates HTTPS (Caddy, nginx, Traefik, a load balancer) and set PUBLIC_URL in .env to the public address, for example https://status.example.com. Cookies, OAuth and links in emails all use this value, so sign-in will not work from a different origin than the one configured.
Settings (.env)
| Variable | Meaning |
|---|---|
POSTGRES_PASSWORD | Required. Password of the bundled Postgres. |
BETTER_AUTH_SECRET | Required. Signs sessions. Generate with openssl rand -hex 32 and keep it stable. |
PUBLIC_URL | Public origin. Default http://localhost:8080. |
PORT | Host port. Default 8080. |
EMAIL_FROM+ one transport | Optional outgoing email. Transport is RESEND_API_KEY, SMTP_URL (smtps://user:pass@host:465) or SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS (SMTP_SECURE=true for port 465). Without it emails are written to the container log. |
GITHUB_CLIENT_IDGITHUB_CLIENT_SECRET | Optional "Sign in with GitHub". |
PLAUSIBLE_DOMAINPLAUSIBLE_SRC | Optional Plausible analytics. Off when the domain is empty. |
Operate
- Upgrade: pull the new code, then
docker compose up -d --build. Migrations run on start. - Prebuilt image: tagged releases are published to
ghcr.io/<owner>/<repo>. SetOPENUPTIME_IMAGEin.env, thendocker compose pull. - Backup:
docker compose exec postgres pg_dump -U openuptime openuptime > backup.sql. - Several containers: checks are leased with
FOR UPDATE SKIP LOCKED, so more containers can share one database without duplicating checks. - Logs:
docker compose logs -f openuptime. A health endpoint is at/health.
Cloudflare Workers
Two Workers: openuptime-api (the API under /api/*, the minute cron and the queue consumer) and openuptime-web (the site, which reaches the API through a service binding).
Requirements
- A Cloudflare account and a domain on Cloudflare. The Workers Paid plan is needed only for the Queue mode below; the Free plan works in inline mode at small scale
- A Postgres database reachable from Cloudflare. It is accessed through Hyperdrive, so any hosted Postgres works (Neon, Supabase, RDS, your own)
- Node.js, pnpm and
wrangler, signed in withwrangler login
1. Get the code
git clone <your copy of the repository> openuptime
cd openuptime
pnpm install2. Choose how checks run, then create Hyperdrive
Checks run in one of two modes. The mode follows from whether the queues block exists in apps/api/wrangler.jsonc, so it is a config switch, not a code change.
| Mode | Plan | What it does |
|---|---|---|
| Queue (default) | Workers Paid | The cron fans due monitors out to a Queue, 25 per message, and the consumer runs them in parallel invocations. Scales to thousands of monitors. |
| Inline | Free or Paid | No Queue. The minute cron runs the checks itself, up to 40 monitors per minute, within the Free plan's 50-subrequest and 10 ms CPU limits. Small installs only. |
Queue mode (the file ships this way): create the queue once.
wrangler queues create openuptime-checksInline mode: delete the whole "queues" block from apps/api/wrangler.jsonc and skip the command above. Without a bound queue the Worker detects it and runs checks inline.
Then create Hyperdrive and copy its id into the hyperdrive block of apps/api/wrangler.jsonc:
wrangler hyperdrive create openuptime-prod --connection-string="postgres://user:pass@host:5432/db"3. Create the schema
DATABASE_URL="postgres://user:pass@host:5432/db" pnpm db:migrateIf your database role cannot CREATE SCHEMA (some hosted tenants), run DATABASE_URL=... node migrate-plain.mjs from packages/db-pg instead.
4. Point the config at your domain
In apps/api/wrangler.jsonc set routes (your-domain/api/*), BETTER_AUTH_URL and WEB_ORIGIN (both https://your-domain) and EMAIL_FROM. Remove the placement block, or set it to your own database host.
In apps/web/wrangler.jsonc set the custom-domain routes pattern and PRODUCT_URL (where "Powered by" links to; use your own origin).
5. Secrets
cd apps/api
wrangler secret put BETTER_AUTH_SECRET # openssl rand -hex 32
wrangler secret put CRON_SECRET # any long random string; guards /internal/*
# optional: GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET for "Sign in with GitHub"6. Deploy
cd apps/api && wrangler deploy
cd ../web && pnpm build && wrangler deployDeploy the API first: the web Worker binds to it. Then open your domain and sign up.
Settings
| Where | Variable | Meaning |
|---|---|---|
| api vars | BETTER_AUTH_URLWEB_ORIGIN | Public origin of the site. Used for cookies, OAuth and email links. |
| api vars | EMAIL_FROM | Sender address. Onboard the domain in Cloudflare Email Sending; without it emails are logged. |
| api vars | INVITE_TTL_DAYS | How long invitations stay valid. Default 7. |
| api vars | DISABLE_SIGNUP | Set to true to close registration (email and GitHub). Create your own account first; afterwards only people with a pending invitation can still sign up. |
| api secrets | BETTER_AUTH_SECRETCRON_SECRET | See step 5. |
| web vars | PRODUCT_URL | Target of the "Powered by" link on status pages. |
| web vars | PLAUSIBLE_DOMAINPLAUSIBLE_SRC | Optional analytics. Off when the domain is unset. |
How checks run
A cron trigger fires every minute and leases the monitors that are due. In Queue mode it packs them 25 per message into the openuptime-checks Queue, and the queue consumer runs each pack, records results, opens and resolves incidents and sends alerts. In inline mode the cron does all of that itself for up to 40 monitors per run.
Upgrade
Pull the new code, run the migration again (it only applies what is new), then deploy the API and the web Worker in that order.
Switching modes later
- Free to Paid: upgrade the plan, restore the
queuesblock, runwrangler queues create openuptime-checks, thenwrangler deploythe API. - Paid to Free: remove the
queuesblock and deploy the API again. You can delete the queue afterwards.
No data migration is involved. Monitors, history and incidents are untouched.
After install
- Create your first monitor, then a status page and, optionally, embed its state on your site.
- Automate with the API or connect an agent through MCP. Use your own domain in the examples.
自托管 OpenUptime
在 Cloudflare Workers 或 Docker 上运行你自己的实例。两种方式使用同一套代码和一个由你掌控的 Postgres 数据库。
二选一:
- Docker:一个容器加 Postgres,任意机器可跑,最适合快速试用。
- Cloudflare Workers:两个 Worker,加 Queue 和连接 Postgres 的 Hyperdrive,从 Cloudflare 网络发起检测。
Docker
一个容器同时运行 API、网页应用和检测调度器,compose 文件会一并启动 Postgres。
环境要求
- Docker 和 Compose v2
- 能访问被监控服务的机器
启动
git clone <你的仓库副本> openuptime
cd openuptime
cp .env.docker.example .env
# 编辑 .env:设置 POSTGRES_PASSWORD 和 BETTER_AUTH_SECRET(openssl rand -hex 32)
docker compose up -d --build打开 http://localhost:8080 注册即可。数据库结构在每次启动时自动迁移,数据保存在 pgdata 卷中。
绑定域名
在能终止 HTTPS 的反向代理(Caddy、nginx、Traefik、负载均衡器)后面运行,并把 .env 里的 PUBLIC_URL 设为公网地址,例如 https://status.example.com。Cookie、OAuth 和邮件里的链接都使用该值,用与配置不一致的域名访问会无法登录。
配置项(.env)
| 变量 | 说明 |
|---|---|
POSTGRES_PASSWORD | 必填。内置 Postgres 的密码。 |
BETTER_AUTH_SECRET | 必填。用于签名会话。用 openssl rand -hex 32 生成,并保持不变。 |
PUBLIC_URL | 公网地址,默认 http://localhost:8080。 |
PORT | 宿主机端口,默认 8080。 |
EMAIL_FROM+ 一种发信方式 | 可选的发信配置。方式为 RESEND_API_KEY、SMTP_URL(smtps://user:pass@host:465)或 SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS(465 端口加 SMTP_SECURE=true)。不配置时邮件只写入容器日志。 |
GITHUB_CLIENT_IDGITHUB_CLIENT_SECRET | 可选,启用 GitHub 登录。 |
PLAUSIBLE_DOMAINPLAUSIBLE_SRC | 可选的 Plausible 统计。域名为空则关闭。 |
运维
- 升级:拉取新代码后执行
docker compose up -d --build,迁移在启动时自动运行。 - 预构建镜像:发布版本会推送到
ghcr.io/<owner>/<repo>。在.env中设置OPENUPTIME_IMAGE,再执行docker compose pull。 - 备份:
docker compose exec postgres pg_dump -U openuptime openuptime > backup.sql。 - 多个容器:检测通过
FOR UPDATE SKIP LOCKED加租约,多个容器共用一个数据库也不会重复检测。 - 日志:
docker compose logs -f openuptime。健康检查接口为/health。
Cloudflare Workers
两个 Worker:openuptime-api(/api/* 下的 API、每分钟的 cron 和队列消费者)和 openuptime-web(站点本身,通过 service binding 访问 API)。
环境要求
- 一个 Cloudflare 账号,以及托管在 Cloudflare 的域名。只有下文的 Queue 模式需要 Workers 付费版;免费版可用内联模式,适合小规模
- 一个 Cloudflare 能访问的 Postgres 数据库。通过 Hyperdrive 连接,所以 Neon、Supabase、RDS 或自建均可
- Node.js、pnpm 和
wrangler,并已通过wrangler login登录
1. 获取代码
git clone <你的仓库副本> openuptime
cd openuptime
pnpm install2. 选择检测运行方式,并创建 Hyperdrive
检测有两种运行模式,是否保留 apps/api/wrangler.jsonc 里的 queues 配置块决定使用哪一种,只是配置切换,不需要改代码。
| 模式 | 套餐 | 说明 |
|---|---|---|
| Queue(默认) | Workers 付费版 | cron 把到期的监控项按每 25 个一条消息放入 Queue,由消费者并行执行,可扩展到数千个监控项。 |
| 内联 | 免费版或付费版 | 不用 Queue。每分钟的 cron 自己执行检测,每次最多 40 个监控项,受免费版 50 个子请求和 10 ms CPU 限制,仅适合小规模。 |
Queue 模式(配置文件默认如此):创建一次队列。
wrangler queues create openuptime-checks内联模式:从 apps/api/wrangler.jsonc 中删除整个 "queues" 配置块,并跳过上面的命令。没有绑定队列时 Worker 会自动改为内联执行。
然后创建 Hyperdrive,把得到的 id 填进 apps/api/wrangler.jsonc 的 hyperdrive 配置块:
wrangler hyperdrive create openuptime-prod --connection-string="postgres://user:pass@host:5432/db"3. 创建数据库结构
DATABASE_URL="postgres://user:pass@host:5432/db" pnpm db:migrate如果数据库角色没有 CREATE SCHEMA 权限(部分托管租户),改在 packages/db-pg 目录执行 DATABASE_URL=... node migrate-plain.mjs。
4. 把配置指向你的域名
在 apps/api/wrangler.jsonc 中设置 routes(你的域名/api/*)、BETTER_AUTH_URL 和 WEB_ORIGIN(都填 https://你的域名)以及 EMAIL_FROM。删除 placement 块,或改成你自己的数据库主机。
在 apps/web/wrangler.jsonc 中设置自定义域名的 routes,以及 PRODUCT_URL("Powered by" 链接的目标,填你自己的地址)。
5. 密钥
cd apps/api
wrangler secret put BETTER_AUTH_SECRET # openssl rand -hex 32
wrangler secret put CRON_SECRET # 任意足够长的随机字符串,保护 /internal/*
# 可选:GITHUB_CLIENT_ID 和 GITHUB_CLIENT_SECRET,用于 GitHub 登录6. 部署
cd apps/api && wrangler deploy
cd ../web && pnpm build && wrangler deploy务必先部署 API:web Worker 依赖它。然后打开你的域名注册即可。
配置项
| 位置 | 变量 | 说明 |
|---|---|---|
| api vars | BETTER_AUTH_URLWEB_ORIGIN | 站点公网地址,用于 Cookie、OAuth 和邮件链接。 |
| api vars | EMAIL_FROM | 发件地址。需在 Cloudflare Email Sending 中接入该域名;不配置则邮件只写日志。 |
| api vars | INVITE_TTL_DAYS | 邀请有效天数,默认 7。 |
| api vars | DISABLE_SIGNUP | 设为 true 关闭注册(邮箱与 GitHub 均适用)。请先创建好自己的账号;之后只有持有待处理邀请的人还能注册。 |
| api secrets | BETTER_AUTH_SECRETCRON_SECRET | 见第 5 步。 |
| web vars | PRODUCT_URL | 状态页 "Powered by" 链接的目标。 |
| web vars | PLAUSIBLE_DOMAINPLAUSIBLE_SRC | 可选统计。未设置域名则关闭。 |
检测如何运行
cron 每分钟触发一次并租约到期的监控项。Queue 模式下每 25 个打包成一条消息放入 openuptime-checks 队列,由消费者执行每个分组,记录结果、开启和解决事件并发送告警。内联模式下这些都由 cron 自己完成,每次最多 40 个监控项。
升级
拉取新代码,重新执行迁移(只会应用新增的部分),然后先部署 API、再部署 web Worker。
之后切换模式
- 免费版升级到付费版:升级套餐,恢复
queues配置块,执行wrangler queues create openuptime-checks,再wrangler deployAPI。 - 付费版降到免费版:删除
queues配置块并重新部署 API,之后可以删除队列。
不涉及数据迁移,监控项、历史记录和事件都不受影响。
安装之后
Last updated 2026-10-11最后更新:2026-10-11