I maintain a dozen or so client sites. A few Drupals, plenty of WordPress and a couple of things written from scratch that are hard to pigeonhole. On top of that come staging sites, servers, mail and all those pieces that are not the website but without which the website does not work.
Support work has one unpleasant property: you learn about problems as late as it is possible to learn about them. The client writes when they notice, and they usually notice after a few days, and usually the worst possible version of the situation - "the site has been down since this morning", although it had been down since Thursday.
The second unpleasant property: the last deployment always takes the blame. Even if it was two weeks ago, and in the meantime the host changed the PHP version, an auto-update swapped out a plugin, and the certificate failed to renew.
Monitoring solves both. Not because it sends alerts - any pinger can do that. Because it gives you a timeline on which you can see what changed and when. Below is how I have it set up working with Drupal, WordPress and custom projects.
How I arrange monitors per project
Every client is a separate project, and a project usually holds this set:
- the home page and two or three page templates with a different rendering path (with WooCommerce: a category and a product page; in Drupal: a listing view and a node),
- one page that always hits the database and cannot be cached - the cart, the search, a form. That is the one that shows first when something is happening with the database or with PHP-FPM,
- an API endpoint, if the front end takes anything from it,
- DNS records for the domain and the mail,
- services outside HTTP - the mail server, a database on a separate host, a NAS holding the client's backups.
When onboarding a new client I add a dozen or so addresses at once instead of clicking through each separately. Intervals are set per monitor: critical things every minute, less important ones every five or fifteen. The range runs from one minute to twelve hours, so for a site sitting behind a CDN cache and changing once a quarter there is no sense in burning the allowance.
A practical detail that saves one misunderstanding at the start: the IP addresses the monitoring queries from are listed in the application. I allow them in the firewall or in the WAF rules before anybody bans the monitoring for "suspicious traffic every minute".
A deployment: before, during and after
This is the moment the tool earns its keep most.
Before. I glance at the health audit score and the response time chart, so I know what the state before the change looked like. Without that I have nothing to compare against afterwards, and I am left with "I think it used to be faster".
During. I pause the monitors for the duration of the work. The break window is recorded in the history and is not counted towards uptime, so in the client's report I do not have to explain where 99.1% came from in the month we did the migration.
After. This is the most interesting part. The health audit compares each run with the previous one and shows the difference, not another report from scratch. After a deployment I see plainly:
- which checks changed status, and in which direction,
- whether the page content changed and whether the HTML structure changed,
- which headers disappeared and which appeared, and how their values changed,
- whether the server changed (
Server: nginxturning into something else usually signals the host reconfigured something), - by what percentage the HTML grew or shrank,
- whether the number of redirects changed and whether the final address after redirects changed,
- whether the TLS certificate was renewed.
That is exactly the list you reconstruct by hand over half an hour when debugging a regression: curl -I against the old version, against the new one, comparing headers, checking whether the cache still works. Here I get it ready-made, with a date on it.
The things I catch this way most often:
- A
noindexon production. In WordPress it is the "Discourage search engines from indexing this site" checkbox carried over from staging along with the database. In Drupal it is the metatag configuration or anX-Robots-Tagfrom the server config. That single mistake can cost a client a quarter of traffic, and absolutely nothing about it is visible. - A vanished
Cache-Control. After an nginx configuration change or after page cache was turned off in Drupal "for a moment, just for testing". The audit checks that header separately, so it surfaces the next day rather than a month later on the transfer bill. - Mixed content. A classic after a migration, when part of the assets kept the old
http://in the database. - Vanished security headers - HSTS, CSP, X-Frame-Options, cookie flags. They do not disappear on their own, but they do disappear with every larger move in the server configuration.
- Leaked software versions in
ServerandX-Powered-By. They come back after every environment rebuild, if nobody pinned it in the configuration for good. - A lengthened redirect chain. Two hops instead of one, because a rule was added and nobody removed the old one.
The audit history is kept as a change log, not a list of runs: an entry appears when something broke, got fixed or changed, plus a control entry once a week. So when the question is "when did this start", I am not going through thirty identical reports - I see five dates on which anything moved at all.
Problems between deployments, the most infuriating kind
Support is to a large degree failures nobody caused. Nobody did anything, and it stopped working. A few types I have under control precisely thanks to monitoring:
Auto-updates. WordPress updates plugins by itself if somebody once switched that on. Drupal updates less often, but a host can bump the PHP version without warning. The effect is always similar: something stops rendering, or the site starts spilling warnings.
For that I have content monitoring. Each monitor takes up to five phrases in one of two modes:
- must contain - a fragment of text from a section that renders dynamically. It disappears when a template blows up or a view stops returning results.
- must not contain - and here I enter what turns up on a white screen:
Fatal error,Warning:,Deprecated:, "There has been a critical error on this website", "The website encountered an unexpected error".
This is WSOD detection and the silent PHP errors no availability monitoring will ever see, because the server politely answers with a 200.
Cron and queues. If a project has a status endpoint or anything returning JSON, I attach an API monitor. It compares the shape of the response with the one it had at configuration time: it checks whether the fields still exist and still have the same type, not whether their content changed. A changed article title is not an incident; a vanished field very much is.
This catches the failure you cannot see from the outside: the API answers 200 while the front end feeding on it stops working. With a headless Drupal on jsonapi, or with the WP REST API, that is the most common side effect of a module or plugin update. For GraphQL you can additionally set a condition that the errors path must not exist - because GraphQL politely answers with a 200 even when the backend is down.
Slow creep. The most insidious case: nothing falls over, everything just gets slower. Server response time (TTFB) is measured every minute and shown as a median and a P95, day by day. That gives a baseline the recent days are compared against.
When the median jumps from 250 ms to 900 ms and stays there, I have a date. And with a date I also have the answer to whether it was our change or something on the host's side. Usually the latter, and then I have something to argue with in the ticket.
Certificates. Automatic renewal works right up until it stops. Certificate monitoring warns 30, 14 and 7 days before expiry, and a certificate error marks the site as unavailable. Thirty days is realistically as long as it takes to sort things out with the client's host.
DNS. Records are usually changed by somebody who is not me: the client, the registrar, the previous agency that "still had something there". I keep monitors on the domain's A record, the mail MX and the TXT with SPF. The match mode is chosen per monitor: "contains" requires the declared values to be present and tolerates extra records; "exact" requires agreement down to a single record - and only the latter will detect an added record, which is what somebody else's interference in your zone looks like. For MX and SPF I keep the exact mode.
A vanished MX record is my favourite failure of the "everything works" variety: the site is up, the panel is up, only the contact forms have been falling into the void for a week.
Things that are not websites
For everything that does not speak HTTP there is the TCP port monitor: the application opens a connection on the given port and measures how long it took to establish. I use it for mail servers, databases on a separate host and clients' backup NAS boxes.
It has a practical advantage over querying a panel over HTTP: the web interfaces of such devices can be slow to wake and generate false alarms even though the device was available the whole time. The port answers immediately. And if something sits on a slow link and the odd misreading still happens, there is a reduced sensitivity option - the monitor then gives the host more time before calling it unavailable.
There is also the inverse mode: a monitor watching that an address does not answer. That is how I keep staging sites locked to an IP and admin panels behind a block. If a staging site starts answering publicly, I get an alert before Google indexes it and before somebody finds a copy of the production database there.
Alerts set up so that they do not become background noise
Monitoring that shouts about everything stops being read within a week. So I set alerts separately for each monitor and separately for each type of event: availability, expiring certificate, health degradation.
There are three channels: e-mail, browser push notifications and phone notifications through ntfy. In my case push goes to the desktop during working hours, ntfy to the phone around the clock for production monitors, and staging and secondary things go by e-mail only. The times in e-mails are in my time zone, which saves one mistake when diagnosing something at three in the morning.
It is also worth knowing that the health degradation alert is triggered only by the part of the score I genuinely influence - the score of the page itself, calculated without Core Web Vitals data from CrUX. A sensible decision: CWV can move without any change on our side, and alerts about that would be pure noise.
In the morning I look at the "Needs attention" screen: what is down, what came back, whose certificate is expiring. If it is empty, I am having a quiet day.
The client can see this too, and that is good for both sides
Two things I set up for every larger client.
A team account. The technical person on the client's side (or a colleague of mine) gets their own account and is added to the team. I share selected monitors with them - they see the statuses and statistics and set their own alerts and their own channels, but by default they change nothing and delete nothing. If somebody needs more freedom, there is a separate editor permission: they can change the settings of shared monitors, but still cannot add new ones or remove existing ones.
The effect is that I stop being the only bottleneck. The alert goes to two people, and whoever is free reacts.
A public status page. I gather the client's selected services on one page and send them the link, optionally protected with a password. The client checks for themselves whether their site works instead of writing to me asking "is it down for you too?". On support contracts it is also the simplest proof that somebody is watching - and at the annual rate conversation it is an argument that makes itself.
On top of that comes the availability history for the last 90 days and the uptime percentage for the period. When settling support work, or in a "should we change hosting" conversation, that is the only currency that works: specific dates, the duration of each outage and the reason for it.
A sensible minimum per project
If I had to reduce this to a list I tick off for a new client:
- HTTP monitors on the home page and two or three significant templates, every 1-5 minutes.
- One monitor on a page that cannot be cached, because that is the one that will show a database or PHP problem.
- Content monitoring with a phrase that must be there, and an error message that must not.
- SSL certificate monitoring with an alert at 30/14/7 days.
- A health audit with a regression alert - the best deployment watchdog I know.
- DNS monitors on A, MX and TXT (SPF), with MX and SPF in exact mode.
- A TCP port monitor on everything that is not a website: the mail, the database, the NAS.
- An API monitor, if the front end feeds on JSON.
- A team account for the person on the client's side, and a status page with the link in an e-mail.
That is fifteen minutes per project. It pays for itself at the first "what happened to this site?", which you answer with a date, a time and a list of changes instead of a promise to look into it.