A website migration is complete when the application, database and files agree on the new server and new activity reaches one production writer. Copying a web directory and changing DNS is not enough for a site that accepts orders, uploads, accounts or background jobs. Plan consistency and rollback before moving traffic.
Prepare the destination, copy a baseline, test using the real hostname, pause all writers for the final transfer and then cut over. If the destination has accepted new data, a rollback must preserve those writes. The exact backup and restore procedure depends on the application and database versions.
What this guide helps you do
- Inventory the dependencies and data that must move together.
- Test the destination hostname and TLS before changing public DNS.
- Plan a single-writer cutover and a rollback that preserves new activity.
Developers and website owners moving an existing application to a self-managed VPS while preserving its database and uploaded files.
Before you start- Authorised access to the application, source and destination servers, DNS and certificates.
- A restorable independent backup, an isolated rehearsal environment and an agreed maintenance window.
Inventory the application before copying it
Record runtime and database versions, extensions, service configuration, secrets, certificates, file ownership and permissions. Include uploads outside the web root, queues, sessions, scheduled jobs and external integrations. A configuration file pointing to the old database can make a destination appear healthy while it still depends on the source.
Keep the move separate from a major software upgrade where practical. Moving the same domain normally does not require changing application URLs. If the domain changes, follow the application’s supported migration method; blanket database text replacement can damage structured or serialized values.
Prepare DNS and preserve unrelated services
Record the existing A, AAAA and CNAME values and their TTLs. Lowering a TTL helps only after previously cached answers expire; it cannot remove answers already held by resolvers or clients. Allow for traffic reaching both servers during the transition.
Handle IPv6 deliberately: an unchanged AAAA record can continue sending visitors to the old server. If a CDN or proxy fronts the site, distinguish its origin configuration from public DNS. Leave MX and mail-related TXT records intact when email is staying where it is.
Coordinate database, uploads and background work
Make an initial transfer while preparing the destination, but treat it as a baseline. Use the database’s supported backup and restore procedure; copying a running database’s ordinary data directory is not a valid generic backup method. Protect exports and secrets, and confirm that the backup can be restored.
PostgreSQL pg_dump produces a consistent snapshot of one database during concurrent activity. It does not coordinate that snapshot with uploaded files, queues or cluster-wide roles. Other databases and storage engines have different guarantees.
For the final transfer, pause every writer: web requests, APIs, cron, workers and inbound callbacks. Arrange how delayed payment or webhook events will be retried or reconciled. Then take a coordinated final database export and file sync; maintenance mode alone may leave background writers running.
Test the real hostname before the DNS change
Configure the destination virtual host and a valid certificate for the production hostname first. Use authorised certificate issuance or secure certificate transfer as appropriate. This example preserves the hostname for HTTP and TLS while directing the connection to a specific address and bypassing configured proxies.
migration_host='www.example.com'
migration_ip='192.0.2.44'
curl --noproxy '*' \
--resolve "$migration_host:443:$migration_ip" \
--silent --show-error --connect-timeout 5 --max-time 15 \
--output /dev/null \
--write-out 'http=%{http_code} tls_verify=%{ssl_verify_result} total=%{time_total}\n' \
"https://$migration_host/"Replace the reserved examples with your hostname, destination IP and a side-effect-free URL you own. Check curl’s exit status and the HTTP result; do not disable certificate verification. Inspect redirects before following them, because they can lead back to the old server. This request is not complete application acceptance.
Rehearse application behaviour with isolated data
Check login, reads, uploads, permissions and the relationship between database records and stored files in a rehearsal copy. Isolate its database and queues. Disable or sandbox email, payments, webhooks and scheduled work so that a test does not notify customers or process real transactions.
A local hostname override can help browser testing, but confirm which server and dependencies it actually reaches. Matching file counts or checksums alone cannot prove application consistency. Record the checks that must pass after the final restore, including recent records and representative uploaded files.
Keep one production writer through the cutover
| Stage | Old environment | Destination |
|---|---|---|
| Preparation | Remains the production writer. | Isolated rehearsal data only. |
| Final transfer | All production writers paused. | Receives coordinated final data; writes paused. |
| New production | Read-only or maintenance; no independent jobs. | Sole production writer after final checks. |
After verification, change the intended DNS or proxy origin and enable destination writers deliberately. Cached DNS may still send users to the old server. Keep it read-only or in maintenance, or deliberately forward traffic to the new origin without a routing loop. Never let both copies accept independent orders or run the same scheduled jobs.
Preserve new writes before rolling back
Before the destination accepts writes, rollback can restore traffic to the old consistent state after checking write ownership. After any writes, reversing DNS alone risks stale data and divergence.
Freeze writes and preserve both states. Reconcile all inserts, updates, deletions and associated files since cutover using an application-supported procedure. Before restoring or reopening the old environment, verify that its application and database versions support the destination schema and exported data. Check jobs, payments and webhooks for duplicate effects. If compatibility or reliable reconciliation cannot be established, remain in maintenance and repair forward.
Observe before retiring the old environment
Watch application errors, recent business records, upload retrieval, queue depth, scheduled work and resource use. Confirm that backups now cover the destination and can be restored. Check access logs and remaining dependencies before concluding that old traffic has ended.
Keep the old environment protected for the agreed recovery window, with obsolete writers disabled. Retire it only when delayed traffic, pending events and recovery needs are resolved. Retain the migration record, final backup locations and the actual cutover time so that later investigations can follow the data history.
Review scope
Reviewed on 1 October 2026 against the linked primary documentation, including migration, DNS caching and curl behaviour. Command syntax was checked without executing a migration. No end-to-end installation, database restore or production change was performed; rehearse with your application and versions in an isolated environment.
Official references
Check the project’s current documentation for version and configuration details.
- WordPress — Migrating WordPress (new tab)
Application files, database scope and the difference between server and domain changes.
- PostgreSQL — pg_dump (new tab)
Consistent single-database exports and the separate scope of cluster-wide objects.
- Cloudflare DNS — Time to Live (new tab)
DNS caching and differences between proxied and DNS-only records.
- curl — Command-line manual (new tab)
Hostname resolution overrides, proxy bypass, TLS verification and bounded requests.