Your Website Needs an MOT
There is a moment, shortly after a website or application goes live, where the client exhales. The build is done. The invoice is paid. The thing is online, and people are using it. Whatever comes next, it feels like the hard part is behind them.
It is a reasonable feeling, and it is almost entirely wrong.
Software is not a purchase. It is more like a tenancy — something you take on with ongoing responsibilities that do not end when the lease is signed. The world the application was built in keeps changing: runtimes are updated, dependencies age, security vulnerabilities are discovered, browser standards evolve, accessibility requirements shift, and search engines quietly rewrite the rules by which they judge your content. None of this cares that the project was signed off. None of it waits for a convenient moment to be addressed.
If a website were a car, most clients would be driving one that had never been serviced, on tyres that were last checked in 2022, with a check engine light they had learned to ignore.
What Maintenance Actually Means
The word "maintenance" tends to conjure images of minor housekeeping — a content update here, a broken link fixed there. That is not what I mean. The maintenance burden on a live application is broader, quieter, and more consequential than most people who commission software ever realise.
The server itself. If your application runs on a VPS — a virtual private server you or your agency manages — the underlying operating system needs to be kept current. Ubuntu, Debian, CentOS: each has a support lifecycle, and when it ends, security patches stop arriving. A server running an end-of-life OS is not just unpatched — it is permanently unpatched, with a growing list of known vulnerabilities and no fixes coming. Rebooting for kernel updates, applying security patches, managing the lifecycle of the machine itself: this is not something that happens automatically, and it is not something most clients know to ask about.
The runtime. PHP, Node.js, Python — whichever language your application is written in, the runtime has its own version lifecycle. PHP 8.0 reached end of life in November 2023. Node.js releases have defined support windows. Running on an unsupported version means running without upstream security fixes. It also, progressively, means falling behind on performance improvements and language features that the rest of the ecosystem has moved on to.
The dependencies. Modern applications are built on packages — hundreds of them, in some cases. The Laravel ecosystem, the npm registry, Composer: the libraries your application depends on have their own bugs, their own vulnerabilities, their own update cycles. The most infamous illustration of what happens when a dependency goes unpatched is Log4Shell — a critical vulnerability in a Java logging library that sat unnoticed in millions of applications until it was exploited at scale in late 2021. The scale was possible precisely because nobody had been watching. Dependency vulnerabilities are not exotic edge cases. They are a routine, ongoing reality that requires routine, ongoing attention.
The database. Database versions age too. Query patterns that were efficient when the application was first built can degrade as data volumes grow. Schema decisions made early in a project's life can become constraints that slow everything down later. Keeping a database healthy is not a one-time task; it is part of the ongoing relationship with the application.
WCAG compliance. Web Content Accessibility Guidelines exist to ensure that digital products are usable by people with disabilities, and in many contexts compliance is not optional — it is a legal requirement. What is easy to miss is that compliance is not a state you achieve and retain permanently. Content changes, new features, third-party embeds: any of these can introduce accessibility regressions. An application that passed an accessibility audit at launch may not pass one eighteen months later, and the consequences of non-compliance — legal challenges, reputational damage, and above all, the exclusion of real users — do not disappear because the original build was done carefully.
SEO. Search engine algorithms are not static. Core Web Vitals, structured data requirements, indexing behaviour, mobile experience standards — the goalposts move, and an application that was well-optimised at launch can quietly slide down search rankings as the criteria it was optimised against are updated. SEO is not a one-time configuration. It is an ongoing discipline that requires someone to be paying attention.
SSL certificates. A small one, but worth naming: SSL certificates expire. Certificate authorities like Let's Encrypt handle auto-renewal in many configurations, but managed or purchased certificates require manual action. An expired certificate locks users out with security warnings — browsers are not subtle about it — and the damage to trust is disproportionate to the simplicity of the fix.
The Security Argument
The items above carry different levels of urgency depending on the application. An expired SSL certificate is embarrassing. An unpatched runtime vulnerability in an application handling personal data is a potential breach — and as I explored in a previous piece, the consequences of a breach are not limited to a difficult conversation. GDPR fines, regulatory obligations, reputational damage, and in some cases legal liability: these are the stakes that an unmaintained dependency tree is silently accumulating exposure to.
Security vulnerabilities in third-party packages are not announced with fanfare. They are published quietly in CVE databases, patched in new package versions, and discovered by bad actors who scan for the outdated versions. The window between disclosure and exploitation can be very short. Organisations that are watching and patching quickly are largely safe. Organisations that are not watching — because nobody is paid to watch — are exposed for as long as the vulnerability goes unaddressed.
The Errors Your Customers Never Report
There is a category of problem that sits alongside the patching and versioning concerns and is just as damaging, but quieter: the errors your customers encounter and simply do not tell you about.
When a checkout fails, most customers do not contact support. They leave. When an authentication error prevents someone from creating an account, they do not open a ticket — they try a competitor. When a third-party API your application depends on is deprecated or suffering downtime, the feature it powers silently breaks, and the business has no signal that anything is wrong until someone happens to notice, or a customer happens to mention it in passing.
Error tracking tools exist precisely to fill this gap. Sentry is the most widely known — it captures unhandled exceptions, failed requests, and unexpected states in real time, surfacing them with enough context to diagnose and fix quickly. Laravel Nightwatch does similar work in the Laravel ecosystem, with additional visibility into queues, schedules, and the kinds of background processes that fail invisibly. The point of both is the same: rather than waiting for a complaint that may never come, you have an immediate signal that something has gone wrong, what went wrong, and where.
The cost of not having this is real and largely invisible. A payment flow that fails for 5% of users is losing the business money every day — but without tracking, there is no way to know that 5% exists. A sign-up error that affects mobile users on a particular browser is silently capping growth. A broken API integration is making a feature that was demoed to a client appear to simply not work. None of these generate a support ticket. All of them generate lost revenue, lost users, or lost confidence.
Uptime monitoring sits alongside error tracking and is, if anything, more urgent. Knowing that your application is down — before your customers tell you, or before they stop trying altogether — is table stakes. I remember Tiny Rebel, a craft brewery based locally in Newport, running a promotion on Facebook for a new product launch. Thirty minutes later, a second post appeared apologising because the website had crumpled under the demand. That is lost sales at exactly the moment purchase intent was highest, and it is the kind of incident that lingers — customers who tried and failed once will think twice before trusting the next promotion.
The modern equivalent plays out every Black Friday. Retailers with no load testing, no scaling plan, and no monitoring discover that their website cannot handle the traffic they spent money driving to it. The worst version of this is a WordPress installation two major versions behind, running a deprecated plugin stack with security vulnerabilities the size of a motorway junction — straining under the weight of a traffic spike it was never tested against, and exposed to anyone paying attention to its CVE history. The traffic event and the security exposure are separate problems that share a root cause: nobody was watching, and nobody was maintaining.
I experienced a version of this firsthand when I was running a small web hosting company — a dedicated server through what was then 1&1 (now IONOS), managed through a Plesk panel, servicing a handful of clients. A developer got in touch wanting to migrate six WordPress sites across from his current host. He had performance issues with them, he said, though he did not elaborate much. I had capacity, he had clients, it seemed straightforward.
The sites went up. Within a short time, CPU and memory started spiking across the server and — more alarmingly — my mail server suddenly had a great deal more to say for itself than it should. All six sites shared the same contact form plugin. All six were running an unpatched version with a known security exploit. Someone had found it, and was now using all six simultaneously as a launchpad for sending spam at considerable volume. My server's IP was blacklisted before I had fully understood what was happening.
The cleanup was not quick. A new IP had to be provisioned, the sending reputation rebuilt, and his sites were blocked from accessing SMTP entirely. He found a new hosting provider not long after. The performance issues at his previous host were, in retrospect, probably the same story playing out — and the plugin, unpatched across all six sites, was the one common thread that nobody had thought to look at.
Two Models, Very Different Outcomes
When clients do have a maintenance arrangement in place, it tends to take one of two forms.
The first is what I think of as the annual invoice model. At some point, usually triggered by something breaking or someone asking, an invoice arrives: here is what we have updated this year. There is no visibility into what was done or when, no indication of what was not done, and no ongoing relationship with the codebase between interventions. It is reactive by nature, and reaction is always more expensive than prevention.
The second is a retainer model, and it is considerably more effective. A monthly retainer funds an ongoing relationship — an engineer who knows the codebase, is watching it, and is proactive about what needs attention. Tools like Dependabot surface dependency vulnerabilities as pull requests the moment they are known, so the engineer can review, test, and merge rather than discovering them when something breaks. Routine maintenance — package updates, server patching, performance reviews — happens on a cadence rather than in a panic. Problems are caught early, when they are cheap, rather than late, when they are not.
Crucially, that retainer fee is rarely just paying for time. It is typically covering the tooling the engineer uses to do the job properly — error tracking software like Sentry or Laravel Nightwatch, uptime monitors, dependency scanners, alerting infrastructure. These tools have a cost, and a good engineer is not running them out of goodwill. A retainer that bundles this in means the client benefits from professional monitoring they would struggle to procure or configure themselves, and the engineer has the visibility they need to actually be proactive rather than just available. The economics tend to work out: the monthly cost of a well-structured retainer is almost always less than the cost of a single incident it could have prevented.
The distinction is not merely organisational. It changes what is possible. An engineer with ongoing context on a codebase can spot a pattern before it becomes a problem. An engineer parachuted in annually, or only when something is visibly broken, is always catching up.
What a Maintenance Contract Should Look Like
Not all maintenance contracts are made equal, and a client with no technical background is poorly placed to evaluate what they are being offered. A few things worth looking for:
A clear scope of what is covered — server patching, dependency updates, performance monitoring, uptime monitoring — and what is not. "General maintenance" is not a scope; it is an excuse for ambiguity.
Visibility. Regular reports, however brief, of what has been reviewed and what action was taken. A maintenance arrangement that produces no evidence of its own activity is hard to evaluate and easy to neglect.
A proactive stance on known issues. Dependabot, Snyk, and equivalent tools are not expensive to set up and they remove the reliance on someone remembering to check. If a proposed maintenance arrangement does not include automated dependency monitoring, ask why.
Clarity on response times for security issues. A critical vulnerability in a public-facing application is not the same as a minor package bump — it needs to be treated differently, and the contract should say how.
The Analogy Holds
An MOT, in the strictest sense, tells you whether a car is roadworthy at a specific moment in time. What keeps it roadworthy is the regular servicing in between — the oil changes, the tyre checks, the things that would not trigger an MOT failure today but would become a problem on the motorway in six months.
A website is the same. A launch-day audit tells you the application was sound at launch. What keeps it sound is the ongoing attention that most clients do not know to ask for, and most agencies do not proactively offer, because there is no invoice moment to attach it to.
The clients I feel most concern for are the ones who genuinely do not know this is a conversation they should be having. They built something, they paid for it, and they moved on. Somewhere in the stack, a dependency is three major versions behind. The PHP version is approaching end of life. The SSL certificate expires in six weeks and nobody has noticed. None of it is urgent today. All of it will be, eventually.
Software does not stand still. The world it runs in does not stand still. The only question is whether someone is watching.
Have thoughts on this? Get in touch.

