End-of-Life Runtimes: What EOL Actually Means for a Production App

I'm Ty Wells, founder of Seatrial. A runtime reaching end of life gets less attention than a dramatic vulnerability, but it can matter more over time. This guide covers what EOL means, what it does not mean, and how to find out what your production app actually runs on.

What end of life actually means

Most language runtimes, including Node.js, Python, PHP, Ruby, Java and .NET, follow a published lifecycle that usually moves through three phases:

  1. Active support. Bug fixes and security fixes are released.
  2. Security-only support. Ordinary bugs are no longer fixed, but security issues still get patched.
  3. End of life. The upstream project stops shipping fixes of any kind for that version line.

The length of each phase differs between projects, and some projects change their policies. Do not rely on a blog post, including this one, for dates. Check the project's official release or support page for the current schedule.

The key point is that after EOL, a newly discovered flaw in that version will usually not be fixed upstream. Your app can sit on a version that was fine yesterday and become exposed the day a problem is published, with no patched release to move to.

What EOL does not mean

EOL is easy to misread in both directions.

  • EOL does not mean you are vulnerable right now. It means you have lost the safety net for future problems. The risk builds over time.
  • A supported version is not automatically safe. A supported line can still have known vulnerabilities in the specific patch release you run. Being on a supported major version but several patch releases behind is common.
  • Your distribution may still patch an EOL upstream version. Some Linux distributions and vendors backport security fixes into older packages under their own support windows. Then the version string looks old, but the package is maintained. The opposite also happens: a version that is fine upstream can be shipped by a distribution or image that no longer receives updates. Confirm the support window with whoever actually built the runtime you run.
  • Managed platforms set their own rules. Hosting providers and serverless platforms publish their own deprecation schedules. Those may block deployments or remove support on dates that differ from upstream EOL. Read your provider's current runtime policy.

Why it matters beyond security

An EOL runtime causes practical problems even before a security issue appears:

  • Dependencies move on. Libraries drop support for old runtimes, so you get stuck on old library versions too, and those may carry their own unfixed issues.
  • Upgrades get bigger. Moving one major version is usually manageable. Moving across several at once, under pressure after an incident, is not.
  • Questionnaires and audits ask. Customers or partners may ask whether you run supported software. It helps to know the answer before they ask.

Step 1: Find every place your runtime version is decided

The version you think you run and the version production runs often differ. Check each of these places.

On the machine or container:

node --version
python3 --version
php -v
ruby -v
java -version

Run these inside the production container or server, not on your laptop.

In the repo, look for anything that pins or implies a version:

  • Dockerfile: the FROM line, for example node:<version>, python:<version>-slim or php:<version>-fpm. Unpinned tags like latest make the result unpredictable.
  • package.json: the engines field, plus .nvmrc or .node-version.
  • .python-version, runtime.txt, pyproject.toml (requires-python).
  • composer.json: the require.php constraint and config.platform.php.
  • .ruby-version, Gemfile (ruby line).
  • Platform files your host reads, such as app or runtime config files, buildpack settings, and CI workflow files that set up a specific version.

Disagreement between these files is a finding in itself. If your Dockerfile says one version and your CI tests another, you are not testing what you ship.

Step 2: Check what your server reveals

Your live app may advertise its runtime in response headers. Look at them:

curl -sI https://your-app.example | grep -iE 'server|x-powered-by'

If you see something like X-Powered-By with a version number, two things follow:

  1. You now know at least one runtime version production reports.
  2. Anyone else can see it too. Consider turning version disclosure off in your web server or framework config.

Hiding the header reduces noise, but it does not fix anything. An EOL runtime with its header removed is still EOL.

Step 3: Look up the support status of each version

For each runtime and version you found:

  1. Go to the project's official release or supported-versions page.
  2. Note which phase your version is in: active, security-only, or EOL.
  3. Note the date the current phase ends.
  4. If you use a distribution package, vendor build or managed platform, check their lifecycle page as well, because theirs is the one that governs your patches.

Community-maintained trackers that collect EOL dates across many projects are handy for a quick overview. Still confirm against the official source before making a decision.

Record this in a short table in your repo: runtime, version, where it is declared, support phase, end date, owner. A text file you actually keep current beats a spreadsheet nobody opens.

Step 4: Treat "I don't know" as a finding

Sometimes you cannot tell what version is running. The header is stripped, the image tag is floating, or the host picks a default. Do not mark that as fine. An unknown version should go on your list as unresolved until you confirm it.

This is also how Seatrial's exposure check behaves. It reads server headers and what a repo declares (the composer PHP version, the Dockerfile, platform stack files and package files) and reports VULNERABLE, END OF LIFE, OK or UNKNOWN for each runtime. UNKNOWN is never reported as safe.

Step 5: Plan the upgrade before the deadline

Once you know your dates, work backward:

  • Upgrade one major version at a time where you can, and read that release's migration or upgrade notes.
  • Update your test environment first. Change CI to the target version, run the full suite, and fix what breaks.
  • Update every declaration together: Dockerfile, version files, engines or require constraints, and platform config. A partial update is how drift starts.
  • Check your dependencies' support for the target version before you commit to a date.
  • Deploy to a staging environment that matches production, then verify with the same commands from Step 1.

Aim to finish while your current version is still in security-only support, not after it has ended.

Step 6: Keep it from drifting again

  • Pin versions explicitly. Use specific image tags rather than latest, and keep a version file in the repo.
  • Add a CI check that fails if the runtime version in the build differs from the one you declared.
  • Put EOL dates on a calendar with a reminder months ahead, so the upgrade becomes planned work instead of an emergency.
  • Re-check after platform changes. Switching hosts, base images or buildpacks can quietly change your runtime.
  • Review the list periodically. Lifecycles get extended, shortened and redefined, so re-confirm dates with the official sources.

A quick check if you want one

If your code is in a public GitHub repo, Seatrial's free scanner at seatrial.io/scan checks it and unlocks the full results with an email. The scanner CLI, Sentinel, is free. Whether or not you use a tool, the steps above are the work: find every place your version is set, confirm its support status with the official source, treat unknowns as open, and upgrade before the date arrives.


Explore the $99 Founding Pilot

Explore the $99 Founding Pilot
All security monitoring guides