A Production Health Check Runbook for a Flask Blog: Homepage, Database, Uploads, and Auto Publishing

A small Flask blog can look healthy while the publishing system is already broken. The homepage returns 200, Nginx is running, and the server responds quickly. But today's article may not have been generated, the publish API may not have written to the database, or uploaded images may still exist only on the local machine. A production health check is useful only when it verifies outcomes, not just processes.

For a personal blog, the first version does not need a large monitoring stack. A practical runbook should answer a few direct questions every day: can users open the public pages, can the database be read and written, can uploaded assets be fetched, did the scheduled jobs actually run, and did today's expected article appear online?

1. A homepage check is necessary but not sufficient

The simplest check is still worth keeping: request the homepage and confirm that it returns a successful response. But this should be treated as an entry-point check, not as proof that the whole blog is healthy.

A useful homepage check records three things:

  • HTTP status code
  • response time
  • expected text in the rendered page

For example:

GET /
expect: 200
expect body contains: Stepnex
expect latency less than: 3000ms

If the site has separate Chinese and English entry points, check both. A bilingual site can fail in one language while the other language still looks fine. Template variables, article filters, and navigation links often depend on the active language, so the health check should follow real user paths instead of only testing the shortest URL.

2. Database checks should cover reads and writes

A blog is only healthy if its content database is healthy. Read checks are easy: count published articles, fetch the latest article, or query today's expected category. Those checks are safe and useful, but they do not prove that the application can still write.

The stronger version is a small write test. Create a dedicated health-check table with a name, timestamp, and random token. Insert a row, read it back, and delete it or keep it as a short audit trail. This verifies the connection, transaction handling, permissions, and encoding in one place.

If you do not want to add a table yet, start with targeted read checks. For an automated publishing blog, one important query is: how many published articles exist today after the scheduled publishing window? That single query catches many failures that a generic database ping will miss.

3. Publishing checks must verify the final result

A scheduled task can exit with code 0 and still produce no article. That happens when the script decides there is nothing to publish, when the source JSON is from an older date, or when the generation step never ran. Therefore, the publishing health check should verify the production database, not just the task result.

For a weekday tech article, the rule can be explicit:

today category: tech
required languages: zh, en
window start: today 09:00 Asia/Shanghai
expect: both languages exist online

The output should list missing languages directly. missing_languages=[zh,en] is much more actionable than a vague automation failure. It tells the operator whether to generate content, retry publishing, or inspect a partial publish.

4. Uploaded assets need their own checks

Markdown can reference an image path even when the image is not present on the production server. This is common when posts are generated locally and then published through an API. The article body is saved correctly, but the upload directory was never synchronized.

A health check can scan the article payload or the latest published articles for /static/uploads/ paths, then request each public URL. At minimum, verify:

  • the status code is 200
  • the content type is an image type
  • the response size is above a reasonable minimum
  • the URL uses the public site host

If your publish script syncs uploads over SSH, failed syncs should include the exact local and remote paths. The goal is to make the error repairable without opening the server and guessing where the file should have gone.

5. Automation logs should prove today's execution

When a daily article is missing, the first question is always the same: did the automation run? A useful system should answer that immediately. Each job should leave a small structured record with the task name, start time, end time, exit code, generated slug, published article ID, and a short error summary.

A compact JSON record is enough:

{
  "task": "daily-tech-article",
  "date": "2026-06-15",
  "generated": true,
  "published_languages": ["zh", "en"],
  "error": ""
}

This is better than a long plain-text transcript because another script can read it and decide what to do next. For a small blog, structured local logs often solve the real operational problem before a full observability platform is necessary.

6. Fallback jobs must distinguish missing content from failed publishing

A fallback publisher is useful, but only if it knows what failure it is handling. There are two different cases: today's article files were never generated, or the files exist but publishing failed. Retrying the publish API only helps the second case.

A good fallback sequence is:

  1. decide whether today is a publishing day
  2. determine the expected category
  3. check whether the bilingual JSON files were updated today
  4. query production for today's articles
  5. generate content if the files are missing
  6. republish only if the files exist but the online records are missing
  7. query production again after publishing

This sequence separates activity from outcome. The task may have run, but the result may still be missing. The health check should make that difference visible.

7. Health check output should point to the next action

The best health checks reduce repair time. Instead of saying only failed, they should explain what failed and what action is needed.

A useful failure report looks like this:

status: failed
reason: today article json not generated
expected_category: tech
online_count_after_09:00: 0
next_action: generate zh/en payload and publish

This tells you the issue is not the public page, not the database query, and not necessarily the API. The missing step is content generation. When the output is this specific, manual recovery is much faster and future automation is easier to improve.

Summary

A Flask blog production health check should follow the actual content path: public page, database, upload directory, scheduled job, generated article payload, publish API, and final database record. Checking only whether a process ran is not enough. The useful question is whether the expected article, in the expected languages, exists online after the scheduled window.

Start with a small runbook and make every check actionable. When the system can clearly say whether the problem is missing generation, failed publishing, broken uploads, or an unavailable page, daily operations become much easier to recover and much easier to automate.