— Article — № 007

007 —Drupal

Drupal 7 end-of-life: a checklist for clients still on it

Drupal 7 went unsupported in January 2025. The sites still humming along on it in mid-2026 are not unsupported by accident, they are unsupported by neglect.

Closed leather logbook with silk ribbon, brass pen nib on red wax seal, dated card on dark oak desk.
Hero · staged still№ 007

A senior engineer at a Dutch agency we work with logged into a client's Drupal 7 admin last week and found fourteen contrib modules with no Drupal 10 equivalent, a Features module written by a contractor who left in 2018, and a node_revision table at 9.2 million rows. The brief was "a small refresh of the homepage". The actual job is a multi-month migration that nobody has budgeted for.

This is the standard situation in 2026. Drupal 7 reached end-of-life on 5 January 2025 after three separate extensions. Anybody still running it is doing so on borrowed time, vendor patches, or willful ignorance. None of those scale. The checklist below is what we walk through with agencies and in-house teams who have inherited an old Drupal legacy site that did not get migrated in time. It is not a sales pitch for a rewrite, because in roughly a third of cases the right answer is to extend, harden and defer. The trick is knowing which third.

What end-of-life actually changed

The mechanical change on 5 January 2025 was the end of community security advisories from the Drupal Security Team for core and contrib. New CVEs against Drupal 7 stop being triaged at drupal.org/security. Existing advisories stay published, but no new ones land unless an ELTS vendor publishes one against their own patched fork.

The practical change is slower and uglier. Contrib maintainers stop responding to issue queues. Composer-installed dependencies start failing because their D8+ counterparts dropped PHP 7 support. Hosts begin to refuse PHP 7.4 on shared plans. The site keeps serving traffic, but every piece of infrastructure around it is rotting in parallel.

The audit you run before anything else

You cannot plan this kind of migration from a project brief. You plan it from the actual codebase. The first thing we do on the audit is the same five commands, in this order:

drush status
drush pm-list --type=module --status=enabled --no-core
drush sqlq "SELECT COUNT(*) FROM node_revision"
drush sqlq "SELECT name, schema_version FROM system WHERE type='module' AND status=1"
find . -path ./sites/default/files -prune -o -name "*.module" -print | xargs wc -l

The first tells you the PHP version, MySQL version, and Drupal point release. The second is the migration risk register. The third tells you whether the migration job fits in a maintenance window or needs to be a streamed cutover. The fourth catches modules with a pending DB update, which is the single most common reason a legacy-to-D10 migration explodes mid-run. The fifth measures custom module surface area, which is what you will actually have to rewrite by hand.

From those five outputs you can give the client a real estimate within a day. Without them you are guessing, and guesses on these migrations are wrong by a factor of three.

Pick a path, then commit

There are exactly three honest paths for an unsupported Drupal site in 2026. Hybrid plans tend to be the worst of all three.

Path A: extended vendor support. Vendors like Tag1, MyDropWizard and HeroDevs sell paid security patches against Drupal 7 core and the long tail of common contrib modules. Pricing is roughly €2,000 to €8,000 per year depending on contrib coverage. This is the right path when the site has under two years of expected life, the content team is stable, and rebuilding does not unlock new business value. It is the wrong path when contrib coverage is thin (check the vendor's coverage list before signing) or when you are also fighting PHP version EOL on the host.

Path B: migrate to Drupal 10 or 11. Use the core Migrate, Migrate Drupal and Migrate Drupal UI modules. Build a fresh Drupal 11 site, point its migration source at the existing database, and run the migration in a dry mode first:

drush migrate:status --group=migrate_drupal_7
drush migrate:import --group=migrate_drupal_7 --limit=100
drush migrate:messages d7_node_article

The hard part is never core content. It is the contrib modules with no D10 equivalent (start with Upgrade Status), the Features-exported configuration that no longer maps to D10's config system, and the custom modules whose hook_ implementations need to become services and event subscribers. Budget twice as long as you think for those three.

Path C: rebuild on something else. Sometimes the right answer is not Drupal 10. If the site is a content marketing site with five editors and no commerce, a headless CMS with a static front end will outlast another Drupal cycle and cost a quarter as much to operate. We have moved such sites to Statamic, to Sanity plus Next.js, and in two cases to WordPress where the editorial team strongly preferred it. The migration script is harder (no Migrate module to lean on) but the long-term maintenance burden drops.

Stopgap hardening for the holdouts

Some clients will not commit to an exit path this quarter. Fine. Do not leave the site naked while they decide. The minimum hardening for an unsupported install is four changes.

Block PHP execution in sites/default/files at the web server. Drupal 7 has historically had file-upload-based RCE paths, and the directory permissions alone are not enough on shared hosts:

<Directory "/var/www/html/sites/default/files">
    <FilesMatch "\.(php|phtml|php3|php4|php5|php7|phar)$">
        Require all denied
    </FilesMatch>
    Options -ExecCGI
    RemoveHandler .php .phtml .phar
</Directory>

Lock the MySQL user down to the minimum grants the site actually needs. The default install instructions tell you to grant ALL PRIVILEGES, which is fine during install and reckless afterwards:

REVOKE ALL PRIVILEGES ON drupal_prod.* FROM 'drupal'@'localhost';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE TEMPORARY TABLES,
      LOCK TABLES, EXECUTE
   ON drupal_prod.* TO 'drupal'@'localhost';
FLUSH PRIVILEGES;

Disable the modules that have a history of CVEs and that the site does not actively use. The usual suspects: PHP Filter (should already be off, check anyway), Webform if no forms are active, RESTful Web Services, Services. Run drush pm-list --status=enabled and challenge every single line.

Add an offsite database dump on a schedule. Not the host's "managed backup". A real mysqldump shipped to S3 or B2 with a 30-day retention. When the site does fall over in 2026, it falls over hard, and the host's backup window is often shorter than the time it takes for anyone to notice.

The migration dress rehearsal

If you have committed to Path B, do not migrate against production. Build a staging clone of the source database (with mysqldump | gzip | scp, nothing fancy), point a fresh Drupal 11 install at it, and run the full migration on a workstation or a disposable VM. Log every warning. The first run will surface a hundred problems. The second, after you have written the field mappings for your custom content types, will surface ten. The third should be clean.

Keep a running list of every contrib module that needed a manual replacement and every custom hook that needed a rewrite. That list is what you hand the client at the end of the migration as the maintenance baseline for the new site. They will not read it. You will be glad you wrote it the first time a CMS quirk shows up in year two.

When we built Pier we spent more time on Drupal 7 sites than on any other CMS, because the inheritance pattern is so consistent: the agency that built it is gone, the contractor who patched it is gone, and the only person who knows where the custom code lives is the editor who has been working around it for nine years. The way we ended up handling it was to expose every file and table on the server through a single chat surface, with one-click version history on every change, so the engineer doing the audit can rummage through the live site without breaking anything. The MySQL editor sits in the same window as the file tree, which is what an audit on this CMS actually needs.

The smallest thing you can do today: run the five audit commands above on every old Drupal site you are still responsible for, paste the output into a single text file per client, and email yourself the file. You will not have time to do this in the quarter your inbox catches fire.

— Questions —

Is Drupal 7 actually unsupported now?

Yes. Community security support ended on 5 January 2025 after three extensions. New CVEs against Drupal 7 are no longer triaged on drupal.org. Paid ELTS exists but covers a limited contrib set.

Can I upgrade Drupal 7 directly to Drupal 11?

Not in place. You build a fresh Drupal 11 site and migrate the D7 database in using the core Migrate Drupal modules. There is no upgrade path that preserves the existing codebase.

How long does a typical Drupal 7 to Drupal 10 migration take?

Three to nine months for a non-trivial site, depending on contrib module count and custom code volume. Budget twice your first estimate. The contrib gaps and Features config are where time goes.

Is paid ELTS worth it in 2026?

For sites with under two years of expected life and stable contrib coverage from the vendor, yes. For everything else, the annual fee is better spent funding the migration that is already overdue.