---
title: "What thirty seconds of checking would have caught"
description: "A catalogue of small, boring, real failures — a www that resolves nowhere, a redirect that drops to http, a response that is not actually compressed — each one cheap to see from outside."
url: https://harpywatch.com/blog/what-thirty-seconds-of-checking-catches
kind: article
author: "The HarpyWatch team"
published: 2026-10-27T09:00:00+00:00
tags: ["Deployment", "Monitoring", "SEO"]
publisher: "HarpyWatch"
---

# What thirty seconds of checking would have caught

None of the failures below is interesting. Not one requires a race condition, an
unusual client, or a bad day at your cloud provider. Each shows up in a single
request from outside, and each is routinely found weeks later by somebody who
was trying to buy something.

This is the catalogue, with the request that finds each one. Run them by hand
once; every one of them can be reintroduced later by a change you were not
thinking about.

## The redirect that quietly drops to http

You add a trailing slash to a URL. The server redirects to the canonical form —
and the redirect target is `http://`, not `https://`, because the rule was
written before TLS was terminated upstream, or because the proxy builds the redirect from
`X-Forwarded-Proto` and nothing upstream is setting it.

The browser follows it, the HSTS header sends it straight back to `https`, and
everything works. On a client without an HSTS entry — a first-time visitor, a
crawler, an embedded client — there is one plaintext request in the middle,
carrying whatever was in the URL.

```
curl -sI https://example.com/pricing/ | grep -i '^location'
```

If a `Location` starts with `http://`, you have found it. The reason this
survives so long is that anything reporting only the final status code reports
a 200 — the plaintext hop is in the middle. `redirect-chain` reports the whole
sequence, hop by hop.

## The `www` that resolves nowhere

You configured `example.com`. Somebody types `www.example.com`, because plenty of people and plenty of old
links still do, and there is no A record, no CNAME, and no
answer at all. Not a redirect, not an error page — a DNS failure, which the
browser renders as "this site can't be reached", which reads as *your company
does not exist*.

The reverse happens too: `www` resolves and the apex does not.

```
dig +short www.example.com
dig +short example.com
curl -sI https://www.example.com | head -1
```

Both names must resolve, both must serve TLS, and one must redirect to the
other. `dns-resolution` covers the first part continuously,
which matters because this often breaks at a zone edit months after launch
rather than on the day you set it up.

## A certificate for one hostname, serving two

Downstream of the last one. `www.example.com` resolves and redirects correctly,
and its certificate lists only `example.com`. A browser shows an interstitial before it ever
reaches the redirect, and a non-browser client — a webhook sender, an SDK — just
fails the handshake.

```
echo | openssl s_client -servername www.example.com -connect www.example.com:443 2>/dev/null \
  | openssl x509 -noout -text | grep -A1 'Subject Alternative Name'
```

Check every hostname you serve, not just the one you use. `ssl-cert-expiry` and
`cert-chain-trust` run per hostname for exactly this reason.

## Every log line is a favicon 404

A browser asks for `/favicon.ico` when it has no cached
icon for the origin, which is every first visit and every visit after the cache
is cleared. If it is not there,
that is a 404 per visitor, forever, in your access log and your error tracking.

The cost is not the icon. It is that one path is now a constant background rate in
your 404s, and the *real* ones — the dead link in the footer, the asset that did
not make it into the build — are a small fraction of a large number. Whether you notice them depends on
somebody having excluded this path from the view they look at.

```
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/favicon.ico
```

Same argument for `/robots.txt` and `/sitemap.xml`, which `robots-policy` and
`sitemap-health` cover, and which also affect crawling.

## Compression that stopped happening at the edge

Compression is configured. It is configured on the origin, and there
is now a CDN or load balancer in front that terminates the connection itself: it
may drop the client's `Accept-Encoding` on the way through, re-request from the
origin without it, or compress only the MIME types in its own default list,
which commonly covers `text/html` and not `application/json`. Nobody notices because nothing is broken — the page is just several times
larger than it should be, on every request, particularly on mobile.

```
curl -sI -H 'Accept-Encoding: gzip, br' https://example.com | grep -i content-encoding
```

No `Content-Encoding` line means no compression. Do it for the HTML, the
JavaScript bundle and an API response separately: they are frequently handled by
different rules. `compression` is the check; `page-weight` catches the same
problem from the other end, as a number that got bigger.

## A homepage frozen for twenty-four hours

Someone sets `Cache-Control: public, max-age=86400` on the homepage to make it
fast. It is fast. It is also stale for twenty-four hours in every CDN node and
every visitor's browser, including immediately after you deploy a fix.

The specific pain is that you cannot see it. You are testing with a hard refresh
and a warm session; your customers are being served yesterday's document, and
when you ask them to reload, they get it again. The worst version of this is on
an HTML document that references hashed asset filenames — cached HTML pointing
at asset URLs that no longer exist, which is a blank page rather than a stale
one.

```
curl -sI https://example.com | grep -iE 'cache-control|etag|age'
```

The rule of thumb: hashed assets get a long `max-age` and `immutable`; HTML
documents get a short one or `no-cache` with an `ETag`. `cache-policy` watches
that this stays true, because it is exactly the kind of setting that a
performance-minded change reintroduces.

## One subresource still on http

The page is served over `https` and pulls one script, one stylesheet or one
image over `http`. Browsers block the script silently — the console has a
message and the page does not, so the feature is simply absent for everyone
except the person who has the console open.

```
curl -s https://example.com | grep -o 'http://[^"'"'"']*' | sort -u
```

`mixed-content` is the continuous version. This is a very common outcome of a
copy-pasted embed code.

## Headers that only exist on staging

`Strict-Transport-Security`, `Content-Security-Policy`,
`X-Content-Type-Options`. These arrive by configuration, configuration differs
per environment, and the environment where somebody tested is not the one
serving customers.

```
curl -sI https://example.com | grep -iE 'strict-transport|content-security|x-content-type'
```

`security-headers` and `csp-policy` are the checks. The failure mode is not an
outage; it is that a control you believe is on has been off since the last
infrastructure change.

## Run the list, then stop running the list

Every command above needs nothing but `curl`. Running them once, today, will
probably find something.

The reason not to leave it there is that none of these live in your application
code. They live in a proxy config, a CDN rule, a DNS zone, a framework default —
places that change without a commit to your repository, and that a migration or
a new environment resets wholesale. A green result in March is a fact about
March.

HarpyWatch is the boring version of this list: the same requests, made from
elsewhere at a fixed interval, with one row per asset and one
column per check, and an alert when a cell changes colour. You could write all
of it with `curl` and cron. What you would then own is the part that is actually
work — keeping the check logic correct as your stack changes, deciding what
counts as a failure rather than a blip, and routing the result somewhere a human
reads. That is the difference, and it is a maintenance difference rather than a
capability one. It is also not free of failure: a checker has its own outages,
its own false alarms, and its own list of URLs that goes out of date if nobody
prunes it.
