How to Use Upptime to Build Your Own Status Page for Free
How to Use Upptime to Build Your Own Status Page for Free
Move the entire monitoring setup into a GitHub repository—Actions as probes, the repository as a database, Pages as a CDN, and Issues as an event log. Zero servers, zero monthly fees, yet somehow it creates a functional status page that can be viewed, queried, and keeps records. Call it black magic or the wisdom of the frugal—it just works.
Background
When operating a small product matrix consisting of over a dozen external services, the question “is it up or not” often becomes a mantra. A customer reports they can’t access it, you SSH in and run curl only to find it’s working fine; a few minutes later it goes down, but you weren’t watching this time. Commercial monitoring solutions (Pingdom, premium UptimeRobot, Datadog) can certainly solve this, but they either charge per site or per number of requests—for an independent developer, neither the cost nor the mental overhead is quite worth it.
More critically, the status page itself needs to be accessible to users. The ideal scenario: a domain (like status.hagicode.com), displaying real-time availability rates for each service, response time curves, historical events, with automatic logging and notifications during failures. The traditional approach requires assembling four components—a server running cron, a database storing historical data, a frontend site, and a CDN. Once these four are in place, the operational cost immediately outweighs the services being monitored itself—using a sledgehammer to crack a nut, and the nut still finds it crowded.
To address these pain points, we made a decision: move the entire monitoring solution directly to GitHub. The impact of this decision might be greater than you imagine—I’ll elaborate gradually.
About HagiCode
The solution shared in this article comes from our hands-on experience in the HagiCode project. HagiCode is an AI code assistant project that exposes over a dozen public services including websites, documentation sites, and download endpoints, all driven by the HagiCode-org/site main repository. These sites must remain stable and available, so status monitoring is not an option for us—it’s a necessity. The Upptime solution below is exactly what HagiCode uses in its actual production environment—I didn’t make this up.
Analysis: How Upptime Actually Works
The essence of Upptime is actually a GitHub repository template plus six workflows generated by the template. The key to understanding it lies in seeing clearly “who calls whom, when, and what output goes where.” Once you break it down, it’s not so mysterious.
Data Flow: Everything Driven by One Configuration File
The entire system revolves around a single declarative configuration file: .upptimerc.yml. HagiCode’s actual configuration structure looks roughly like this:
owner: HagiCode-orgrepo: upptime
sites: - name: HagiCode Website url: https://www.hagicode.com - name: HagiCode Docs url: https://docs.hagicode.com - name: Server Package Index url: https://index.hagicode.com/server/index.json # ... 14 sites total
status-website: cname: status.hagicode.com logoUrl: https://raw.githubusercontent.com/HagiCode-org/upptime/master/assets/upptime-icon.svg name: HagiCode Status introTitle: "**HagiCode Status**" introMessage: Real-time availability tracking for public HagiCode websites and download endpoints. navbar: - title: Status href: / - title: GitHub href: https://github.com/$OWNER/$REPOTwo points here are worth mentioning separately. First, sites can monitor both web pages (returning HTML) and pure JSON endpoints (like index.json)—Upptime only looks at HTTP status codes and response time, not content validation. Second, cname points to status.hagicode.com, which requires you to own that domain and point DNS to GitHub Pages—after all, even if it’s free, you still need to provide your own domain.
Division of Labor Among Six Workflows
All files under .github/workflows/ have a warning at the top: Do not edit this file directly!—they’re automatically updated from the template weekly, so you just need to edit .upptimerc.yml. Each workflow is triggered by cron, calling different sub-commands of the same action upptime/uptime-monitor@v1.42.6, with clear division of labor—which is quite convenient:
| Workflow | cron | Command | Purpose |
|---|---|---|---|
uptime.yml | */5 * * * * | update | Probe every 5 minutes, write to history/*.yml |
response-time.yml | — | response-time | Calculate response time statistics |
graphs.yml | — | graphs | Generate daily/weekly/monthly/yearly PNG curves |
summary.yml | — | summary | Update status table in README |
site.yml | 0 1 * * * | site | Build static site daily, deploy to Pages |
update-template.yml | 0 0 * * * | — | Sync upstream template weekly |
The core snippet from uptime.yml shows how the “probe” runs:
on: schedule: - cron: "*/5 * * * *"jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: token: ${{ secrets.GH_PAT || github.token }} - name: Check endpoint status uses: upptime/uptime-monitor@v1.42.6 with: command: "update" env: GH_PAT: ${{ secrets.GH_PAT || github.token }} SECRETS_CONTEXT: ${{ toJson(secrets) }}site.yml adds one extra step, pushing build artifacts to the gh-pages branch using peaceiris/actions-gh-pages@v4:
- uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GH_PAT || github.token }} publish_dir: "site/status-page/__sapper__/export/" user_name: "Upptime Bot" user_email: "73812536+upptime-bot@users.noreply.github.com"Data Persistence: Files as Database
Monitoring results aren’t stored in a database but are directly committed back to the repository as files. This sounds a bit wild, but it’s actually quite reliable in practice. Each site produces three types of artifacts.
Status snapshot history/{slug}.yml, for example history/hagi-code-website.yml:
url: https://www.hagicode.comstatus: upcode: 200responseTime: 96lastUpdated: 2026-06-17T00:22:34.485ZstartTime: 2026-03-24T10:07:32.531Zshields.io endpoint badge data source api/{slug}/response-time.json, uptime.json:
{"schemaVersion":1,"label":"response time","message":"739 ms","color":"yellow"}And response time curve graphs graphs/{slug}/response-time-{day,week,month,year}.png.
This “files as database” trade-off is actually quite well thought out: write-heavy, read-light, controllable scale (about 288 samples per site per day, storing increments rather than full logs), naturally versioned, zero infrastructure. The cost, of course, is that the repository keeps growing, and you occasionally need to check in on it.
Events and Notifications: Issues as Event Log
Failure logging relies on GitHub Issues,配合仓库自带的两个模板:.github/ISSUE_TEMPLATE/bug_report.md(user-reported issues)和 maintainance-event.md(planned maintenance). The maintenance template uses frontmatter to express time windows:
<!--start: 2021-08-24T13:00:00.220Zend: 2021-08-24T14:00:00.220ZexpectedDown: google, hacker-news-->Upptime parses these Issues and renders “under maintenance” and “past events” on the status page and README. Notifications rely on the Issue’s native watch mechanism, plus configurable webhooks, Slack, Telegram (declare notifications at the top of .upptimerc.yml—HagiCode’s example repo doesn’t currently enable this,毕竟能少一样是一样).
Solution: Replicate a Status Page in Five Steps
To replicate a HagiCode-style status page from zero to live, it takes five steps in total. Five steps, but each one isn’t long—take your time.
Step 1: Create Repository from Template
Don’t git clone and then modify—use GitHub’s “Use this template” to create a repository directly (like your-org/upptime). The template already includes all workflows, Issue templates, and the static site skeleton. After cloning locally, the only thing you need to manually edit is .upptimerc.yml—leave everything else alone.
Step 2: Edit .upptimerc.yml
Change owner/repo to yours, list the addresses to monitor in sites, configure the site in status-website. A minimal working version looks like this:
owner: your-orgrepo: upptime
sites: - name: Main Site url: https://example.com - name: API Health url: https://api.example.com/health expectedStatusCodes: - 200
status-website: cname: status.example.com # Delete if no domain, use default your-org.github.io/upptime name: Example Status introTitle: "**Example Status**" introMessage: Real-time service availability monitoring navbar: - title: Status href: / - title: GitHub href: https://github.com/$OWNER/$REPOAdvanced options: expectedStatusCodes limits acceptable status codes (default 200-399); headers customizes request headers (for endpoints requiring authentication); maxResponseTime marks slow responses. Use these as needed—take what you need.
Step 3: Configure Secret and Permissions
The workflow defaults to ${{ secrets.GH_PAT || github.token }}. github.token can handle the basic flow, but there are two limitations that will bite you:
- Workflows triggered by the default token won’t trigger downstream workflows (to prevent loops), breaking the chain “probe → create Issue → notify” in the middle.
- Insufficient permissions for cross-repository operations (like across organizations).
It’s recommended to create a new PAT (requires repo + workflow permissions) and store it as repository Secret GH_PAT. update-template.yml has a dedicated check: if there’s no GH_PAT, it skips automatic template updates and prints a warning, so this secret isn’t just optional—it’s key to peace of mind.
Step 4: Enable GitHub Pages
Repository Settings → Pages → Source select Deploy from a branch, branch select gh-pages, directory /root. site.yml automatically pushes build artifacts to this branch every day at 1 AM. If you configured cname, go to your DNS provider and add a CNAME record pointing to your-org.github.io.
It’s also good to manually trigger it once: go to the Actions page, find “Static Site CI” → Run workflow—no need to wait for the scheduled task, after all, seeing the result one second earlier means peace of mind one second earlier.
Step 5: Verify and Maintain
After pushing the configuration, go to Actions to see if “Uptime CI” runs every 5 minutes and if history/ starts appearing with *.yml files. The status page address is https://<your-org>.github.io/upptime/ or your custom domain. Later, adding sites or changing domains only requires editing .upptimerc.yml—workflows are fully automated. HagiCode has relied on this mechanism to maintain availability for 14 endpoints for over a year, with basically no worry.
Practice: We’ve Already Stepped Through the Potholes for You
Below are lessons accumulated from HagiCode’s actual operation—written out so you can take fewer detours.
Practice 1: Choosing Monitoring Granularity
HagiCode puts web pages (https://www.hagicode.com) and pure data endpoints (https://index.hagicode.com/server/index.json) in the same sites list. For JSON endpoints, Upptime requests and parses the HTTP status code but doesn’t validate content structure. If you need deep checks like “returns 200 but content is wrong,” you’ll need to use expectedStatusCodes plus external probes to supplement—Upptime itself only does black-box HTTP checking—it only looks at the face, doesn’t read the mind.
Practice 2: Clever Use of Response Time Badges
api/{slug}/response-time.json is a shields.io endpoint badge data source. HagiCode’s README extensively references these URLs:
https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2FHagiCode-org%2Fupptime%2FHEAD%2Fapi%2Fhagi-code-website%2Fresponse-time.jsonThis way, you can embed real-time response time badges in any Markdown (project README, blog, third-party pages), with colors driven by the numeric value in message and the color field. Note that using HEAD rather than master/main to reference raw files avoids widespread failures after branch renames—in the details lies stability.
Practice 3: Repository Size Control
Sampling once every 5 minutes, history/ accumulates significant volume over a year. Upptime uses incremental YAML rather than full logs, which is relatively restrained, but it’s still recommended to check the repository size periodically. If a certain site’s monitoring value decreases, just remove it from sites—you can also manually clean up corresponding historical files—after all, if you can’t bear to delete, the repository will eventually bloat up for you to see.
Practice 4: Real Usage of Maintenance Events
maintainance-event.md isn’t just for show. Before a planned release, open an Issue using the template, fill in start/end/expectedDown, and Upptime will mark the corresponding sites during this period as “scheduled maintenance,” not counting toward availability statistics, avoiding a normal release dragging down the full-year SLA. HagiCode’s expectedDown supports a comma-separated list of site names, corresponding one-to-one with sites[].name.
Practice 5: Boundaries Between Template Updates and Customization
The Do not edit this file directly! at the top of all .github/workflows/*.yml isn’t meant to scare you. update-template.yml overwrites these files with the upstream template every week. When you need custom behavior, the correct approach is to use officially supported configuration options in .upptimerc.yml (like skipTopics, customStatusWebsite, runnerSettings), not to modify workflows. If you really must modify workflows, either disable update-template.yml or fork and maintain the template yourself—the latter loses painless upgrades, weigh the pros and cons yourself.
Practice 6: Real Constraints of Free Quotas
GitHub Actions is free for public repositories with unlimited runtime—Upptime is designed to leverage exactly this. Private repositories have 2000 free minutes per month, while uptime.yml runs once every 5 minutes, about 1 minute each time—this alone is about 8640 minutes per month, which exceeds the quota. So the Upptime repository must be public—this is the premise of “free”—don’t make it private for confidentiality and then receive a bill, that would be awkward.
Summary
Returning to the initial question: monitoring a bunch of external services, is there really a cheap solution? HagiCode’s answer is—yes, and so cheap you’ll doubt if it’s real. Upptime breaks monitoring down into four GitHub native components:
- Probes = GitHub Actions cron
- Database = YAML/JSON files in the repository
- CDN = GitHub Pages
- Event log = GitHub Issues
What you get: real-time availability rates, response time curves, historical events, availability badges, custom domains, automatic notifications—all zero servers, zero monthly fees. The cost is keeping the repository public and occasionally caring about repository size. Compared to hand-rolling a monitoring system, this cost is already much lighter.
The reason this solution works is because of GitHub’s ecosystem’s sincere subsidy for open-source projects. If you’re also maintaining a small product matrix with multiple sites, I strongly suggest spending an afternoon setting it up—it’s much more worry-free than hand-rolling monitoring.
References
- Upptime Official Repository
- shields.io endpoint badge documentation
- GitHub Actions scheduled task documentation
- HagiCode status page example
Summary
For “How to Use Upptime to Build Your Own Status Page for Free,” a more robust approach is to first gradually validate key configurations, dependency boundaries, and implementation paths, then fill in optimization details.
When goals, steps, and acceptance criteria are clear, such solutions can typically enter actual delivery more smoothly.
开始使用 HagiCode
一次安装,几分钟上手
HagiCode for Windows 在 Microsoft Store 免费提供。打开商店即可安装并保持更新;也可以先对比各版本与定价,再决定从哪个渠道开始。