Migrating a Drupal 7 site to Drupal CMS
Your Drupal 7 site keeps running exactly as it is. Next to it — on the same account, on the same server — you install a fresh Drupal CMS site, and then you let Drupal's own migration engine pull the Drupal 7 site's content, files and users across into it. You review the result, repeat the run until you're happy, and when content parity lands you swap the domain over. At no point is your live site in the write path.
That's the whole shape of it, and this page walks it step by step. Everything here is self-service: the panel tasks you already know, your own shell, and Drupal core's built-in upgrade tool.
Two different "migrates" — keep them straight. This page involves two
unrelated things that share a word. BOA's Migrate Site panel task moves a
site between platforms or renames it to a new domain — it never touches the
content's shape. Drupal's Migrate API (the engine behind core's
/upgrade tool) reads a Drupal 7 database and rebuilds its content inside a
modern Drupal site. In this workflow you use both: the Migrate API for the
content crossing, and the Migrate Site task at the very end, purely as a
rename.
What this workflow is — and isn't
- Both ends live on your account. The Drupal 7 source and the Drupal CMS target sit side by side, so the migration runs entirely inside one server — no dumps to ship anywhere, no third machine, and the source's files are directly readable by the target while it imports.
- Content crosses; the site is rebuilt. Drupal's Migrate API brings your content, files, taxonomy and users into the new site. Your Drupal 7 theme and custom modules do not cross — the front end is a fresh start on Drupal CMS's modern toolset. Plan the theming work as its own project.
- It's a rehearsal until you say otherwise. The target site is disposable by design. Run the migration, inspect, delete and redo — your Drupal 7 site serves visitors throughout, untouched.
If your Drupal 7 site currently lives elsewhere — another host, a local machine — bring it onto your account first: Importing an existing site. And if what you actually want is Drupal 7's other exit — the one that keeps your site's structure without a rebuild — that's the Drupal 7 → Backdrop upgrade, a different road entirely.
Step 1 — Get the Drupal CMS platform
The target needs a Drupal CMS platform on your account. If CMS (or the
version-numbered Drupal CMS entry) is already in your Platform drop-down,
skip ahead.
If it isn't, request it the usual way: add the CMS keyword to your
~/static/control/platforms.info (remember the file replaces your list —
list everything you want, or use ALL), then trigger the on-demand upgrade
with ~/static/control/run-upgrade.pid. Both files, and their gotchas, are
covered in
Choosing and managing platforms.
No shell handy? A support request naming the CMS keyword does the same.
Drupal CMS runs on the server's default PHP — no version switching needed.
Step 2 — Install the target site
Create a new site on the Drupal CMS platform, exactly as on Creating and managing a site, with two deliberate choices:
- Pick a test-copy domain —
dev.as the first label, for exampledev.newsite.example.com. While you rehearse, that name keeps search engines and bots served a 404 instead of your half-migrated content, and — unlike deeper test labels — it can still get a real HTTPS certificate if you enable HTTPS on it; the full list of protected labels is on Cloning and moving a site. The final production name arrives later, by rename. - Pick the Drupal CMS install profile —
drupal_cms_installer— in the Install profile field; the distribution installs itself around it.
One expectation to set, because it catches everyone: the panel installs the complete Drupal CMS package unattended — the browser setup wizard you may know from trying Drupal CMS elsewhere never runs here, and the new site's front page looks deliberately plain. That's a healthy install, not a broken one — the Drupal CMS page shows what to expect.
Step 3 — Switch on the core migration modules
Drupal's upgrade tool ships in core, switched off. On the target site,
log in as the admin (the panel's Log in button on the site's page gets
you there without a password), open Extend (/admin/modules), and under
the Migration group enable:
- Migrate
- Migrate Drupal
- Migrate Drupal UI
Two things you'll see are normal. The last two are labelled (Deprecated) — that's upstream Drupal's label, meaning they're headed for contrib in some future major release; today they are the supported Drupal 7 upgrade path, and core asks you to confirm you want deprecated modules — continue. And the confirmation adds a fourth module for you, Password Compatibility: it's what lets your imported users keep logging in with their existing Drupal 7 passwords.
While you're on this page, one more decision: any content feature your Drupal 7 site uses must be switched on here before you migrate, or its data is skipped. The headline example is Comment — Drupal CMS deliberately ships without it, so if your Drupal 7 site has comments, enable Comment now or they won't cross. Don't worry about getting the list perfect: the upgrade's review screen (Step 5) shows you exactly which of the old site's modules have nowhere to land, and you can come back here.
One last piece of target preparation: clear out the starter content.
Open Content (/admin/content) and delete the content items a fresh
Drupal CMS ships with (you'll find a draft Privacy policy page there).
The reason is ID numbers: your Drupal 7 content arrives keeping its
original IDs, and an imported item whose ID is already taken by an
existing content item fails to import rather than replacing it —
quietly costing you your oldest content. An empty content list means every
ID is free. (The Home page is not affected — it isn't a content item.)
When you're done, the upgrade tool is live at /upgrade on the target site.
Step 4 — Prepare the source and collect its coordinates
The upgrade form asks for two things about your Drupal 7 site: its database credentials and its document root. Both are yours to read from your own shell — and there's one small preparation command to run.
The document root first — ask Drush:
drush @www.example.com status
The Drupal root line is the source platform's absolute path — something
like /data/disk/o1/distro/002/drupal-7.105.2-prod. Note it exactly; the
upgrade form needs this real server path (the ~/platforms/... shortcuts in
your home directory won't do — the importing site's PHP can't follow them).
The database credentials live in the source site's settings.php, right
under that root:
grep -A8 "databases\['default'\]" ~/platforms/002/drupal-7.105.2-prod/sites/www.example.com/settings.php
You'll see the database name, username, password, host and port in plain
text. That's by design — on BOA every site's settings.php carries its own
literal credentials, and your shell user is allowed to read them for exactly
moments like this.
Finally, one preparation command, and it's the step everyone would otherwise
lose an afternoon to. Ægir keeps a site's files location in settings.php
rather than in the Drupal 7 database — but the upgrade tool reads the
database — so without help it assumes the stock sites/default/files and
quietly imports no files at all. Mirror the real value into the database
with one Drush command:
drush @www.example.com vset file_public_path sites/www.example.com/files
This is safe to run on the live site: settings.php keeps overriding the
setting at runtime, so the site's behaviour doesn't change — the value just
becomes visible to the migration. (If the site also uses private files, do
the same for file_private_path with its real path.)
Step 5 — Run the upgrade
On the target site, go to /upgrade and follow the wizard:
- Version — pick 7.
- Source database — fill in the credentials you just collected:
database name, username, password; host and port as
settings.phpshows them. - If the credentials step refuses with a pathauto error. Validation can
stop with "The pathauto_pattern plugin must define the source_module
property". Current Drupal CMS platforms are built with that already
corrected: BOA's build adds
source_module: pathautoto pathauto'sd7_pathauto_patternsmigration definition (upstream pathauto issue3588684), so an older platform that predates the correction is the cause.
Install the target site on a current Drupal CMS platform — Step 1's
platforms.infoplusrun-upgrade.pidroute brings one in — and re-enter the wizard. - Files — for Document root for public files, give the source
platform's absolute root path from Step 4. With the
file_public_pathcommand from Step 4 done, the importer resolves each file's real location under that root and copies your uploads across. - The conflict warning. Before the report, the tool warns that content may be overwritten and asks you to press "I acknowledge I may lose data. Continue anyway." That reads scarier than it is here: it fires because the target isn't a bare Drupal — Drupal CMS ships starter menu links and files, and the import replaces the ones whose ID numbers collide with incoming Drupal 7 items. On a rehearsal target that's exactly what you want — you cleared the content items in Step 3, and the remaining starter menu links and files are placeholders. Acknowledge and continue. (This is also why you migrate into a fresh site, never one you've already started building by hand.)
- Review the "What will be upgraded?" report. Two tables: the old site's modules that will be upgraded, and those that won't — modules with no counterpart installed on the new site, whose configuration and content would be skipped. If something you care about is in the wrong table (remember Comment from Step 3), go back to Extend, enable the counterpart, and re-enter the wizard — the report updates to match. When the tables look right, press Perform upgrade.
- Watch it run. Configuration imports first, then content. On a modest site it's minutes; large media libraries take longer.
Step 6 — Review what landed
The batch drops you back on the target site, and the first impressions are delightfully strange: the site now believes it's your Drupal 7 site — its name, slogan and settings crossed over — while wearing Drupal CMS's theme. Walk it like an editor and like a visitor: content in place, images attached, menus sane, users present (they can log in with their old passwords). A few classic artifacts to expect and fix:
- The front page may 404. Drupal 7's default front page is the
/nodelisting, and that setting migrates — but the target has no/nodepage. Point the front page somewhere real under Configuration → Basic site settings. - Basic pages may arrive unpublished. A content type that already exists on the target — Basic page being the everyday case — receives imported items through Drupal CMS's editorial workflow, and they can land as drafts. Your content types that didn't exist on the target (a Drupal 7 Article, say) are recreated as plain types and keep their published state. Review the Content list and publish what should be live.
- A few broken block placements. Block positions migrate from a theme the target doesn't have, so odd corners of the site may show "This block is broken or missing" — remove or re-place them under Structure → Block layout.
- The review tables told the truth. Anything the report listed as not upgraded really isn't there. That's your worklist for the next rehearsal (enable counterparts, or accept the loss deliberately).
Step 7 — Iterate on disposable targets
One rule shapes the rehearsal loop, and the tool states it itself if you
revisit /upgrade: an upgrade runs once per site. There is no
re-running it on the same target to top up or retry — the UI refuses, and
rollbacks aren't supported through it. That's not a limitation to fight;
it's what the panel's cheap site tasks are for:
- Iterate by replacing the target. Adjust whatever the last run taught you — modules on the target, cleanup on the source — then Delete the target site, repeat Steps 2–5 on a fresh one, and compare. Every rehearsal is as clean as the first, and your Drupal 7 site serves visitors untouched throughout.
- Want a frozen source? The importer reads the source's live
database. For rehearsals that don't chase a moving target,
Clone the
Drupal 7 site to a
dev.name and, in the browser lane, aim the upgrade form at the clone's credentials instead (read them from the clone's ownsettings.php; same platform root; run the Step 4vsetagainst the clone). Clones arrive with cron off and no crawler exposure, so the copy holds perfectly still for as many runs as you need.
Step 8 — Cut over by rename
Because the migration runs once per target, the final run is the cutover build: when a rehearsal has proven the process end to end, do one last fresh run at a quiet moment — content edits on the Drupal 7 site paused briefly — and that target becomes the site. The swap is the standard two-rename move from Cloning and moving a site — no site can take a domain while another site holds it, so free the name first:
- Rename the Drupal 7 site out of the way — Migrate Site, change
only the Domain name, to something like
old.example.com. It stays installed and serving there: your instant fallback and your archive. - Rename the target into place — Migrate Site on the Drupal CMS site, change only the Domain name, to the production domain.
- Re-enable HTTPS for the renamed site — a certificate belongs to one exact name, so a rename always turns HTTPS off; switch it back on for the production domain (HTTPS for your sites).
- Switch scheduled cron on for the new production site, and off for the retired Drupal 7 one (Scheduled cron).
Your visitors are now on Drupal CMS. The Drupal 7 site is intact at its retirement name — keep it until you're certain, then retire it properly (a final backup or export, then Delete).
The command-line lane (advanced)
Everything above runs through the browser, and for a one-off migration
that's the path to take. But the Drupal CMS platform also ships Drupal's
full migration pipeline as code — the migrate_plus, migrate_tools
and migrate_upgrade contrib modules sit in the codebase, switched off —
so the same crossing can be driven entirely from your shell: scriptable,
repeatable, and with per-migration control the wizard doesn't offer.
Reach for it when you're rehearsing many times or automating the runs.
This lane uses the site's own bundled Drush (vdrush), so the ground
rules from Drush basics apply: run the
Unlock Local Drush task on the Drupal CMS platform first, connect as
your oN.ftp user, and cd into the platform root (the directory
holding vendor/) before the commands below — as its own command, not a
cd ... && vdrush ... one-liner, which the limited shell refuses. When
you're done, re-lock the platform with Lock Local Drush.
Switch on the pipeline on the target site — this covers Step 3's core modules too (Password Compatibility included, as a dependency), and Migrate Drupal UI isn't needed in this lane. Step 3's other preparations still stand unchanged: enable a counterpart for any content feature the old site uses, and clear the target's starter content first, for the same ID-collision reason:
vdrush @dev.newsite.example.com pm:install migrate migrate_drupal migrate_plus migrate_tools migrate_upgrade
(For a site created moments ago, its modern alias may not be listed by
drush11 aliases yet — until it is, replace the alias with
--root=<platform root>/web --uri=dev.newsite.example.com on every
command.)
Then wire up the source, and this is where the panel serves the lane
best: run the Migration source task on the target site and pick your
Drupal 7 site, and the old site's database arrives as a named migrate
connection — read-only, enforced by the database server, with no
credentials to collect or paste anywhere.
Reading another site's database
covers exactly what that access is and how to remove it when you're done.
The task wires the database, not the files, so Step 4's
file_public_path preparation still applies and the document root is
still yours to collect:
vdrush @dev.newsite.example.com migrate:upgrade --legacy-db-key=migrate --legacy-root=/data/disk/o1/distro/002/drupal-7.105.2-prod --configure-only
If Migration source isn't offered on your instance, the same command
takes the Step 4 credentials directly, with the host and port exactly as
settings.php shows them:
--legacy-db-url="mysql://DBUSER:DBPASS@DBHOST:DBPORT/DBNAME" in place of
--legacy-db-key=migrate.
To rehearse against a frozen source in this lane, Clone the Drupal 7 site as
in Step 7 and re-run Migration source on the target pointing at the clone —
the clone is one of your own sites, so it is offered like any other; repeat the
Step 4 vset against the clone so its files cross too.
--configure-only is this lane's review screen: it derives one migration
per piece of the old site and saves the set as configuration, importing
nothing yet. Inspect it — the row counts come straight from your Drupal 7
database:
vdrush @dev.newsite.example.com migrate:status --group=migrate_drupal_7
Run the import with failures tolerated — without the flag, one failed row halts the entire group:
vdrush @dev.newsite.example.com migrate:import --group=migrate_drupal_7 --continue-on-failure
Two behaviours to know about, both of which look like breakage and have routine fixes:
- A missing counterpart can stop the run mid-group. The classic case
is comments again: a Drupal 7 site that used them, migrating into a
Drupal CMS that ships without the Comment module. The import aborts
partway ("Plugin ID 'd7_comment_type' was not found"), and the
migration it interrupted is left showing Importing in
migrate:status, which blocks the next run. The fix is three steps: enable the counterpart (pm:install comment), re-run themigrate:upgrade --configure-onlycommand so the set is re-derived with the comment migrations included, and reset the stuck one (migrate:reset-status upgrade_d7_field_instance, or whichever migration the status table shows stuck) — then import again. - Partial counts are the report, not a failure. Some migrations
finish below their totals by design — Drupal 7 actions, blocks and
theme settings whose destination doesn't exist on Drupal CMS fail or
are skipped row by row. Read
migrate:messages upgrade_<name>for any migration whose numbers surprise you; it's the content migrations — nodes, users, terms, files, aliases — that should reach their totals.
Unlike the wizard, this lane isn't once-per-site: re-running
migrate:import picks up unprocessed rows and previously imported
migrations are left alone, so a run interrupted mid-group simply
continues. The disposable-target habit from Step 7 is still the cleaner
rehearsal loop — but here it's a choice, not a rule. Everything in
Step 6's review list applies to this lane unchanged.
Where to go next
- Drupal CMS on BOA — what the platform ships and how it stays current.
- Cloning and moving a site — the Clone and Migrate Site mechanics this workflow leans on.
- Importing an existing site — bringing a Drupal 7 site in from another server first.
- Reading another site's database
— the Migration source task behind the command-line lane's
migrateconnection. - Upgrading from Drupal 7 to Backdrop — the other exit from Drupal 7, for sites that want continuity instead of a rebuild.