# Migrate an existing site

**Goal:** you have a working Drupal site hosted somewhere else. When you're done, its code, database, and files are on a Cloud Platform [environment](/start-here/glossary/#environment), verified, and ready for DNS cutover.

A hosting move keeps code and data intact and works for a modern Drupal site (Drupal 10 or 11). If your site is on Drupal 7, WordPress, or another CMS, rebuild it as a modern Drupal site first (for migrations, see [drupal.org/docs](https://www.drupal.org/docs)), then move the hosting. For subscription setup and DNS administration, see [docs.acquia.com](https://docs.acquia.com/acquia-cloud-platform).

## What you'll have when you're done

- The site's code in the application's Git repository, deployed to an environment.
- The database and user-uploaded files imported and verified.
- A cutover plan with DNS timing handled.

## Prerequisites

- A Cloud Platform application (your subscription's welcome material or [docs.acquia.com](https://docs.acquia.com/acquia-cloud-platform) covers creating one)
- [acli installed and authenticated](/cloud-platform/cli/quickstart/), and an [SSH key registered](/start-here/glossary/#ssh-key)
- A local copy of the site: codebase, database dump or working local install, and the user-uploaded files directory

## Steps

Jump to a task:

- [Run the pre-flight checklist](#run-the-pre-flight-checklist)
- [Import the code](#import-the-code)
- [Import the database](#import-the-database)
- [Import the files](#import-the-files)
- [Verify on non-production](#verify-on-non-production)
- [Cut over](#cut-over)

<Steps>

1. ### Run the pre-flight checklist

   Work through these before importing anything; each one is cheaper to fix now than after cutover.

   - **Composer-managed layout with `docroot/` as the web root.** Cloud Platform expects the [drupal-recommended-project](https://github.com/acquia/drupal-recommended-project) shape: top-level `composer.json` and `vendor/`, Drupal inside `docroot/`, contributed code in `docroot/modules/contrib`, custom code in `docroot/modules/custom`. A site that isn't Composer-managed yet should be converted first; [Migrating a Drupal website into a Composer-managed build](https://docs.acquia.com/acquia-cloud-platform/migrating-drupal-website-composer-managed-build) on docs.acquia.com is the step-by-step. Patched core or module files must become actual patch files applied via Composer, or the first `composer update` erases them.
   - **User-uploaded files must not live in the repository.** The platform stores files on a network filesystem outside the docroot and symlinks `docroot/sites/*/files` to it. Move any `files/` directories out of what you'll commit; they travel separately below.
   - **Incompatible modules.** Some modules conflict with the platform outright, most famously Boost (it fights [Varnish](/start-here/glossary/#varnish)) and anything that sets a session cookie for anonymous users (it silently disables Varnish; see [cookie pitfalls](/cloud-platform/caching/stale-content/#when-something-goes-wrong)). Check yours against [Modules and applications incompatible with Cloud Platform](https://docs.acquia.com/acquia-cloud-platform/modules-and-applications-incompatible-cloud-platform). Also swap the Database Logging module for core Syslog, and drop dev tooling (Devel, `views_ui`) from production.
   - **InnoDB everywhere.** Confirm no tables still use MyISAM:

     ```sql
     SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
     WHERE TABLE_SCHEMA = 'mydb' AND ENGINE <> 'InnoDB';
     ```

   - **What won't transfer:** server-level customizations (custom Apache/Nginx config, cron entries in the OS crontab, PHP extensions outside the platform's set, direct MySQL server settings). Each needs a platform-native replacement: [scheduled jobs](/cloud-platform/configure/cron/) for crontabs, [per-environment settings](/cloud-platform/configure/settings/) for PHP and MySQL-session tweaks, `.htaccess` for rewrite rules.

2. ### Import the code

   Two settings adjustments in `docroot/sites/default/settings.php` first:

   1. Add the platform's database require line at the end of the file (`acli api:environments:database-php-config-find myapp.dev my_db` returns an environment's PHP database configuration). This is what makes one `settings.php` work on every environment: the platform injects the right credentials per environment, so nothing is hardcoded. Details in [per-environment settings](/cloud-platform/configure/settings/#override-settingsphp-including-the-database).
   2. Remove any old host-specific database credentials, base URLs, or file-path settings from the previous host.

   Then build and deploy. Your application comes with a Git repository, but environments run built code, and a Composer-managed repository typically ignores `vendor/` and `docroot/core`, so pushing it there as-is would deploy a tree the environment cannot serve.

   [`push:artifact`](/cloud-platform/reference/acli/#pushartifact) runs `composer install` and commits the built result (vendor directories and scaffold files included, even though your repository ignores them). It pushes that commit to the branch the dev environment has deployed, which deploys it:

   ```bash
   cd my-site
   git add docroot/sites/default/settings.php
   git commit -m "Add Cloud Platform database settings"
   acli push:artifact myapp.dev
   ```

   If your repository does commit `vendor/` and `docroot/core`, plain `git push` does the same job:

   - Add the application's repository as a remote (the URL is on the application's overview page, or via `acli`).
   - Push your branch.
   - Point the environment at it with `acli api:environments:code-switch` (the [code-workflow quickstart](/cloud-platform/code-workflow/quickstart/) walks that flow).

3. ### Import the database

   From your local site directory, dump and push in one step:

   ```bash
   acli push:database myapp.dev
   ```

   `push:database` reads your local site's database and imports it into the chosen environment's database. It names the target before it writes, and the confirmation defaults to yes, so read the environment name in the question rather than pressing Enter:

   ```text
   $ acli push:database myapp.dev

    Overwrite the my_db database on dev with a copy of the database from the current machine? (yes/no) [yes]:
    > yes

       ✔ Creating local database dump
       ✔ Uploading database dump to remote machine
       ✔ Importing database dump into MySQL on remote machine
   ```

   The local connection comes from `ACLI_DB_HOST`, `ACLI_DB_NAME`, `ACLI_DB_USER`, and `ACLI_DB_PASSWORD` (see the [acli reference](/cloud-platform/reference/acli/#environment-variables)); most local stacks set them for you. If you'd rather stay in drush, `drush sql:sync` between a configured source alias and the Acquia alias does the same job (aliases via [`acli remote:aliases:download`](/cloud-platform/reference/acli/#remotealiasesdownload)); either way, rebuild caches afterwards:

   ```bash
   acli remote:drush myapp.dev -- cr
   ```

   ```text
    [success] Cache rebuild complete.
   ```

4. ### Import the files

   Uploaded files go to the environment's network filesystem, not into git. Either push them with acli:

   ```bash
   acli push:files myapp.dev
   ```

   ```text
    Overwrite the public files directory on dev with a copy of the files from the current machine? (yes/no) [yes]:
    > yes

       ✔ Pushing public files directory to remote machine
   ```

   or rsync directly for more control over a large files directory, swapping `myapp.dev@[ssh-host]` for the environment's SSH URL, the `ssh_url` field that `acli api:environments:find myapp.dev` prints (shaped like `myapp.dev@myappdev.ssh.hosted.acquia-sites.com`):

   ```bash
   rsync -avz ./files/ myapp.dev@[ssh-host]:/mnt/files/myapp.dev/sites/default/files/
   ```

   Both are incremental: a second run copies only what changed, and neither deletes files that exist only on the environment.

   The platform maintains the `docroot/sites/default/files` symlink into the environment's network filesystem automatically. The mount point behind that symlink varies by platform generation, which is why the rsync target above is the stable `/mnt/files/[site].[env]/` path rather than whatever the symlink resolves to.

5. ### Verify on non-production

   On the Dev or Stage environment (`myapp.dev` or `myapp.test`), before any DNS change:

   - **Site health:** `acli remote:drush myapp.dev -- status` reports bootstrap, database, and Drupal version; then click through the site on its Acquia-provided domain.
   - **Files arrived:** spot-check media-heavy pages; a page with broken images means step 4 missed a directory.
   - **Errors:** watch the PHP error log while browsing ([tail logs](/cloud-platform/cli/everyday-workflows/#tail-logs-from-an-environment)); a migrated site's first pageviews surface missing modules and bad paths immediately.
   - **Caching behaves:** anonymous pages should show `X-Cache: HIT` on the second request; see [fix stale content](/cloud-platform/caching/stale-content/) for the header-reading routine. This is also the moment to [enable memcached](/cloud-platform/caching/guide/#enable-memcached-if-your-site-benefits) and install Acquia Purge, per the platform's own [pre-launch checklist](https://docs.acquia.com/acquia-cloud-platform/checklist-migrating-your-website-cloud-platform).
   - **Background work:** recreate the old host's cron entries as [scheduled jobs](/cloud-platform/configure/cron/) and confirm they log output.

   Repeat the import against prod when dev checks out (push the same branch or promote code, then re-run steps 3 and 4 against `myapp.prod` with fresh data if content changed in the meantime).

6. ### Cut over

   DNS is administered wherever your domains live, and domain/SSL setup on the platform side is on docs.acquia.com ([Managing domains](https://docs.acquia.com/acquia-cloud-platform/managing-domains), [SSL](https://docs.acquia.com/acquia-cloud-platform/ssl-cloud-platform)). Two timing notes that save real pain:

   - Lower your DNS TTL to 5–10 minutes a few days before cutover, so the switch propagates fast; restore it after.
   - Cloud Platform autoscales, but scaling takes a moment to take effect, and rapid changes in traffic can outrun it. So if you expect a significant change in traffic around launch, notify [Acquia Support](https://acquia.my.site.com/s/contactsupport) in advance.

   Keep the old host running until traffic has fully drained and the new site has survived a day of production load.

</Steps>

## When something goes wrong

**Broken images after import**: the files landed outside the symlinked path, or a multisite directory was missed; re-check the `/mnt/files/[site].[env]/sites/[sitedir]/files` target in step 4.

**White screen or database errors on first load**: the require line from step 2 is missing or not last in `settings.php`, or old host credentials are still being read.

**Everything is slow compared to the old host**: the site probably isn't caching; work through [fix stale content](/cloud-platform/caching/stale-content/) to see whether responses are cacheable at all.

## Next steps

- [Ship code to environments](/cloud-platform/code-workflow/quickstart/): the day-to-day deploy workflow now that the site lives here.
- [Manage configuration](/cloud-platform/code-workflow/configuration/): get the migrated site's configuration into the repository, and importing on deploy.
- [Run scheduled jobs](/cloud-platform/configure/cron/) and [per-environment settings](/cloud-platform/configure/settings/): replacing what the old host's crontab and php.ini did.
- [How caching works](/cloud-platform/caching/guide/): the biggest behavioral difference from generic hosting.
