Skip to content
  • FG-40
  • 12 min read
  • Last checked November 8, 2026

How to Fix WordPress Redirecting to the Old Domain or Staging URL After a Migration

Moved a WordPress site and it keeps sending you to the old domain or staging address? Find which layer is redirecting, then fix the database URLs safely, constants, DNS and CDN origin.

Run the health check

You moved a WordPress site to a new domain or a new server, typed the new address, and landed on the old one. Or the homepage works but logging in sends you to staging.example.com/wp-admin/, images still load from the old domain, or a cloned staging copy keeps bouncing you to the live site. Sometimes it is worse: the new domain shows a completely different site.

WordPress does not store relative links. It writes the full address, including the domain, into its settings, into every post and into page builder data. After a move, any of those stored addresses can trigger a redirect back to where the site used to live. But the database is not the only suspect: wp-config.php constants, DNS, a CDN that still fetches from the old server, and your own browser’s memory of an old 301 all produce the same symptom. The fastest fix comes from finding which one answered first.

What is happening

When you request the new address, several layers get a chance to send you elsewhere before WordPress even runs. Each leaves different fingerprints:

Where the old address lives Typical fingerprint Step
Your browser’s cache of an earlier 301 curl works, your browser does not; a private window works Step 1
DNS or a hosts file still pointing at the old server The old site, old content or old PHP version answers Step 2
A CDN or proxy whose origin is still the old server Response has CDN headers but old-server behavior Step 3
home and siteurl options, or WP_HOME and WP_SITEURL X-Redirect-By: WordPress header on the redirect Step 4
Old URLs inside posts, menus, widgets and builder data No redirect on the homepage, but links, images and logins go to the old domain Step 5
Multisite domain tables Redirect to the main site or its signup page Step 6
Leftover redirect rules copied from the old site Redirect with no WordPress header, or with a plugin’s name Step 7

If the browser shows ERR_TOO_MANY_REDIRECTS instead of landing on the old site, that is a loop, covered in FG-03.

Before you start

  • Back up the database now, before any search and replace. Over SSH in the site folder: wp db export before-url-fix.sql. The file appears in the current folder; move it outside the web root afterward.
  • Access you need: SSH with WP-CLI (strongly recommended for this one), SFTP or a file manager, your DNS provider and your CDN dashboard.
  • How to run commands. Commands starting with wp or grep run over SSH in the site’s folder, the one containing wp-config.php; on some hosts WP-CLI is called wp-cli. Commands starting with curl or dig run on your own computer: built in on macOS and Linux; on Windows use WSL, or curl.exe and Resolve-DnsName in PowerShell. Lines starting with define( are edits to wp-config.php.
  • Know the exact old address. Write down every form the old site used: https://staging.example.com, http:// versions, www and non-www, and any temporary URL your host gave you. You will search for each.

Step 1: Find out who is redirecting

curl -sI https://NEW-DOMAIN/

Run on your computer. Replace NEW-DOMAIN with the new address. -s hides progress and -I fetches headers only, without following redirects, so you see the first answer. Read these lines:

  • location: is where you are being sent.
  • x-redirect-by: WordPress means WordPress itself issued it (Step 4). A plugin’s name in that header points at that plugin (Step 7).
  • server: cloudflare or other CDN headers such as cf-ray mean the response passed through a CDN, so check Step 3 too.
  • No redirect at all (status 200) while your browser still redirects means the redirect is cached in your browser.

Rule out your browser’s cache

Browsers store 301 responses and replay them without asking the server again, often for a very long time. If you set up a redirect to the old domain at any point, even for a minute, your browser may still obey it. Test in a private or incognito window, which starts with an empty cache, or in a different browser profile. To clear it in Chrome, open DevTools (F12), go to Network, check Disable cache and reload with DevTools open, or clear “Cached images and files” for all time. In Firefox, find the site in History, right-click and choose Forget About This Site.

You cannot clear visitors’ browsers, which is why a wrongly placed 301 can keep misdirecting people after you fix it. When you redirect a site temporarily during a migration, use 302.

Step 2: Check DNS and your hosts file

If you are seeing the old site itself rather than being redirected, your computer may still be reaching the old server.

dig +short NEW-DOMAIN A
dig +short NEW-DOMAIN A @1.1.1.1

Replace NEW-DOMAIN with the domain (no https://). +short prints only the answer. The first asks your normal resolver, the second asks Cloudflare’s public resolver at 1.1.1.1, a fixed address. Good output from both is the new server’s IP address, which your host shows in the control panel. If they differ, DNS is still updating or your resolver has an old answer cached; it clears once the record’s TTL runs out. If both show the old IP, update the A record (and AAAA, if you have IPv6) at whoever runs your DNS. If your nameservers point at a CDN such as Cloudflare, that is where the records live, not at the registrar.

A very common cause after a migration is a hosts file entry you added to preview the new server and forgot. Check /etc/hosts on macOS and Linux, or C:\Windows\System32\drivers\etc\hosts on Windows, for a line with your domain, and remove it.

Test the new server directly

curl -sI --resolve NEW-DOMAIN:443:NEW_SERVER_IP https://NEW-DOMAIN/

Replace NEW-DOMAIN (twice) and NEW_SERVER_IP, the new server’s IP address from your hosting panel. --resolve makes curl connect to that IP for that name on port 443, ignoring DNS and any CDN, without editing your hosts file. If the new server answers correctly here but not through normal DNS, the problem is DNS or the CDN, not WordPress.

Step 3: Check the CDN or proxy origin

With a CDN in front, visitors never contact your server directly. The CDN fetches from an origin address you configured. After a host move, that origin often still points at the old server, which happily serves the old site or its redirect.

  • Cloudflare: open the domain’s DNS records and check the A and AAAA records for the bare domain and www. With the orange cloud on, these are the origin. Change them to the new server’s IP.
  • Other CDNs: look for an origin, origin hostname or backend setting in the CDN’s configuration.
  • Purge the CDN cache after changing it, because the CDN may have stored the old redirect. In Cloudflare: Caching, Configuration, Purge Everything.

To prove which server the CDN reaches, upload a small text file with a unique name, such as origin-check-RANDOM_WORD.txt, to the new server’s site folder and request it through the normal address. RANDOM_WORD is any word you make up. A 404 means the CDN is still fetching from somewhere else. Delete the file afterward.

Step 4: Fix home, siteurl and wp-config.php constants

wp option get home
wp option get siteurl
grep -n -E "WP_HOME|WP_SITEURL|WP_CONTENT_URL|COOKIE_DOMAIN" wp-config.php

Type as shown in the site folder. The first two print the Site Address and WordPress Address. The grep searches wp-config.php for constants that override those settings, with -n showing line numbers and -E allowing the | to mean “or”. Good output is the new address in both options and either no constants or constants with the new address. Bad output is the old domain anywhere. Constants win over the database, and while they exist the fields in Settings, General are grayed out, which is often why “changing it in the dashboard did nothing”.

wp option update home 'https://NEW-DOMAIN'
wp option update siteurl 'https://NEW-DOMAIN'

Replace NEW-DOMAIN with the full canonical host, with no trailing slash. Each prints Success: Updated 'home' option. (or Value passed for 'home' option is unchanged if it was already right). Then correct or delete any old constants in wp-config.php. A COOKIE_DOMAIN naming the old domain causes login redirects to the old site even when everything else is correct.

Step 5: Replace old URLs in the database without breaking serialized data

Fixing home stops WordPress’s own redirect, but thousands of old links usually remain in content, menus, widgets, theme settings and page builder data. Clicking any of them goes back to the old domain, and logins from links in old emails do the same.

Why a plain SQL replace breaks sites

Much of WordPress’s data is stored in PHP serialized format, which records the length of every string. A widget setting might be stored like this:

s:27:"https://staging.example.com";

This is an example of stored data, not a command. s:27 says “a string of 27 characters follows”. If you run an SQL REPLACE() that turns it into https://example.com (19 characters), the label still says 27. PHP cannot read the value, WordPress treats the whole setting as empty, and widgets, theme options, builder layouts or plugin settings silently disappear. The damage often goes unnoticed until someone opens a page that relied on them.

Use wp search-replace

WP-CLI unpacks serialized values, replaces inside them and repacks them with corrected lengths. Always do a dry run first:

wp search-replace 'https://OLD-DOMAIN' 'https://NEW-DOMAIN' --all-tables --skip-columns=guid --dry-run --report-changed-only

Replace OLD-DOMAIN and NEW-DOMAIN with the exact hosts, without trailing slashes, and keep the quotes. --all-tables includes tables from plugins that use a different prefix (by default only WordPress-registered tables are searched). --skip-columns=guid leaves post GUIDs alone; they are permanent identifiers that feed readers use, not links. --dry-run changes nothing and only reports. --report-changed-only hides tables with no matches. Good output is a table of tables, columns and replacement counts, ending in Success: N replacements to be made. where N is a number. If it says 0, check the old address you typed. When the counts look sensible, run the same command without --dry-run.

Then repeat for the other forms of the old address:

  • The http:// version and the www version, if the old site ever used them.
  • The escaped form used in JSON, which the block editor and many page builders store: search for 'https:\/\/OLD-DOMAIN' and replace with 'https:\/\/NEW-DOMAIN', again with a dry run first.

If a run reports fewer changes than you expect, add --precise, which makes WP-CLI process every row in PHP rather than letting the database prefilter. It is slower but thorough. Some page builders also ship their own tool (Elementor has Tools, Replace URL) and keep generated CSS files that must be regenerated afterward from their settings.

Find what is left

wp db search 'OLD-DOMAIN' --all-tables --stats

Replace OLD-DOMAIN with the bare old host. wp db search reads every table and reports matching rows, and --stats adds a count per table. Good output is a summary with no matches. Remaining hits are often email addresses on the old domain (leave those unless they should change) or values inside a cache table. Finish with wp cache flush and purge any page cache plugin and your CDN.

Step 6: Multisite networks

A multisite network stores its domains in more places, and WordPress decides which site to load by matching the requested domain against them. If nothing matches, WordPress sends you to the main site, often to its signup page, or to the address in the NOBLOGREDIRECT constant if one is set.

First check wp-config.php for DOMAIN_CURRENT_SITE and PATH_CURRENT_SITE; the domain must be the new main domain, without https://. Then list the sites:

wp db query "SELECT blog_id, domain, path FROM TABLE_PREFIXblogs; SELECT id, domain, path FROM TABLE_PREFIXsite;"

TABLE_PREFIX is the value of $table_prefix in wp-config.php, often wp_, so TABLE_PREFIXblogs becomes wp_blogs. wp_blogs holds one row per site and wp_site holds the network itself. Note that the domain column holds bare host names with no protocol, so a search for https://OLD-DOMAIN never touches it. Replace both forms:

wp search-replace 'https://OLD-DOMAIN' 'https://NEW-DOMAIN' --network --skip-columns=guid --dry-run
wp search-replace 'OLD-DOMAIN' 'NEW-DOMAIN' TABLE_PREFIXblogs TABLE_PREFIXsite --network --dry-run

Placeholders as above. --network searches the tables of every site in the network plus the shared tables. The second command limits the bare-host replacement to the two domain tables, where it is safe. Remove --dry-run from each when the reports look right. Each subsite also has its own home and siteurl; check one with wp option get home --url=SUBSITE_ADDRESS, where SUBSITE_ADDRESS is that site’s new address.

Step 7: Remove leftover redirect rules

Migrations copy redirect rules along with everything else.

grep -n "OLD-DOMAIN" .htaccess wp-config.php

Replace OLD-DOMAIN with the old host. Any line printed in .htaccess is a rule that names the old domain; a staging site often has a “redirect everything to live” rule that came across with the files. Also check, in the dashboard:

  • Redirect managers (Redirection, Rank Math, Yoast SEO Premium) for rules whose target is a full URL on the old domain.
  • Security plugins with a custom login address or “redirect to” settings.
  • SSL plugins and caching plugins that stored the old address in their own settings, which a search and replace might have missed if they keep files on disk.

Advanced: Why serialized data still breaks sometimes

Even wp search-replace cannot fix data that is already damaged. If someone ran a raw SQL replace before you, the length labels are already wrong and the values already unreadable. Symptoms are widgets reset to defaults, a theme back to its stock look, or builder pages rendering blank. The safest fix is to restore the pre-migration database and redo the replacement with WP-CLI. If that is impossible, rebuild the broken settings by hand rather than editing length labels in the database.

Two more edge cases. Values that are serialized inside JSON inside serialized data (some builders nest formats) may need the escaped search from Step 5 as well as the plain one. And data stored as base64, which some plugins use to hide settings, cannot be searched at all: the old domain is invisible to every tool until you change it in that plugin’s own settings screen.

For the cleanest staging and live workflow, always search and replace as part of the move itself, with the exact old and new hosts, and keep the database export from before the replacement until you have checked the site thoroughly.

When to contact your host

Open a ticket if the new server answers correctly with --resolve but DNS or a host-managed CDN still sends visitors to the old one, if you have no SSH access to run WP-CLI, or if a redirect appears with no WordPress header and nothing in .htaccess. Send:

  • The curl -sI output for the new address, and the --resolve test output, with times in UTC.
  • The old and new domains, and the old and new server IPs if you know them.
  • The output of the dig commands from Step 2.
  • What you have already replaced, and whether a database backup from before the change exists.

Still stuck?

Print a free one-page checklist to keep, or open the Emergency Room if the symptom does not match.

Get the free checklists

Keep reading

All field guides

Search the field manual

What is going wrong?

Press / to search from any page, Esc to close