Description
InstaMigrate provides authenticated REST API endpoints that enable full WordPress site migrations between servers. It handles database export/import with binary-safe hex encoding, file archiving and transfer, and serialization-aware search-replace for domain changes.
Features:
- Database export with binary-safe hex encoding (preserves serialized PHP data)
- Database import with streaming line-by-line parsing (handles large dumps)
- File upload, download, archiving (tar.gz/zip), and extraction
- Serialization-aware search-replace for domain migrations
- Dual authentication: API key header or WordPress Application Passwords
- WP-CLI detection (informational, reported in status endpoint)
- Admin UI showing API key and endpoint documentation
Important: This plugin grants full database and filesystem access via its REST API. Deactivate and delete it immediately after your migration is complete.
Installation
- Upload the
insta-migratefolder to/wp-content/plugins/ - Activate the plugin through the ‘Plugins’ menu in WordPress
- Go to Tools > InstaMigrate to find your API key
- Use the API key in the
X-Insta-Keyheader for all REST API requests - Remove the plugin after migration is complete
FAQ
-
Where do I find my API key?
-
Go to Tools > InstaMigrate in your WordPress admin. The API key is displayed there and can be regenerated if needed.
-
Is this plugin safe to leave active?
-
No. This plugin exposes powerful migration endpoints. Always deactivate and delete it after your migration is complete.
-
Does it handle serialized data during search-replace?
-
Yes. The search-replace engine correctly unserializes PHP data, performs replacements recursively, then re-serializes with updated byte counts.
-
What authentication methods are supported?
-
Two methods: (1)
X-Insta-Keyheader with the plugin API key, or (2) WordPress Application Passwords via HTTP Basic Auth (requires admin privileges).
Reviews
There are no reviews for this plugin.
Contributors & Developers
“InstaMigrate” is open source software. The following people have contributed to this plugin.
ContributorsTranslate “InstaMigrate” into your language.
Interested in development?
Browse the code, check out the SVN repository, or subscribe to the development log by RSS.
Changelog
1.8.1
- New:
/theme/activateswitches the active theme, installing it from wordpress.org first if it isn’t present. Defaults to Twenty Twenty-One, which supports WordPress 5.3 and newer — a single theme that works across the whole supported range. This matters before a core change: WordPress ships a theme matched to each release, so the active theme is often newer than the core version being moved to, and it then calls functions that don’t exist there and takes the whole site down. The response returns the previous theme so the change can be reversed. A request with no body at all does the whole step — it activates Twenty Twenty-One and installs it first if the site doesn’t have it. On multisite it changes only the current site unless you ask for the whole network. - New:
/core/updateupdates WordPress core to a requested version. Intended for the case where the site you’re copying from runs a newer WordPress than the site you’re copying to — a migration replaces plugins, themes and the database, but not WordPress itself, so without this the destination keeps its older core and a plugin that needs the newer one can fatal on load. Raise-only by design: a request at or below the current version is reported as skipped and never downgrades. Multisite is refused outright — a network shares one copy of WordPress, so a core change there can’t be limited to one site or undone per site. Hosts that require FTP/SSH credentials for filesystem writes return a distinctfs_credentials_requirederror rather than a generic failure. - New:
/plugins/deactivate-plugindeactivates plugins without deleting them, so the change can be undone — the response returns the previous active-plugin list as a restore point. Passall_except_selfto deactivate everything except InstaMigrate in one call (useful before a core update). Network-activated plugins are included on multisite. InstaMigrate refuses to deactivate itself, so a site is never left without a way back in. - Fix:
/files/search-replaceno longer crashes the site when theextensionsvalue is sent as plain text (for exampletxt) rather than a list. It previously produced a “critical error” page with no usable error message; it now accepts a list, a comma-separated string, or a JSON array, and always returns a proper error if something else goes wrong. - Fix:
/files/mkdirnow keeps new directories insidewp-content, matching every other file endpoint. Previously it would create a directory anywhere the site’s user could write. A path given relative towp-contentstill works exactly as before — it is simply anchored there instead of to an undefined location — so existing callers are unaffected. Only a path pointing outsidewp-contentis now refused. - Fix: the plugin-install, plugin-delete, plugin-deactivate, theme-activate, core-update and file-search-replace endpoints now always return a structured JSON error when something unexpected goes wrong. Previously an error raised outside the guarded section — including from another plugin’s own activation or deactivation code — produced a bare HTTP 500 with no response body, which is indistinguishable from the site being down.
1.8.0
- New: skip files and folders during a migration.
/files/archivenow honors anexcludeparameter (a JSON array or CSV of wp-content-relative paths/globs) — matched files and whole directories are left out of the archive. Default (no exclude) is byte-identical to before. - New:
/files/listendpoint lists a wp-content directory one level at a time, with file and directory sizes, so a migration UI can show what’s on the site and let you choose what to skip. Confined to wp-content (no access outside it). - Fix: archive and database-dump temp files now use a unique name (not just a per-second timestamp), so two exports of the same directory in the same second can’t collide on one filename and have a CDN serve a stale copy.
1.7.9
- New: added a disk-usage endpoint that reports how much space the site uses (files plus database).
- New: the status endpoint now also returns the site’s admin email address.
1.7.8
- Fix:
/files/archive(tar.gz) no longer 500s on largeuploads//plugins/directories. It now streams a POSIX ustar tar straight into a gzip sink one file at a time (constant memory, single pass) instead of building the whole tar in memory and re-reading it to compress (two passes that OOM’d as an uncatchable PHP fatal). Output is unchanged on the wire (still a.tar.gz); long paths beyond ustar’s 255-char limit are reported in a newlong_pathsfield instead of producing an unextractable entry. - Fix: archiving a directory that contains the plugin’s own temp dir (e.g.
uploads/, which holdsuploads/instamigrate/) no longer self-includes the half-written archive — the temp dir is skipped during the walk. This removes a runaway-growth timeout and the long-standing silently-ignoredexcludebehaviour for the temp dir.
1.7.7
- Security: hardened the
source_urlSSRF guard. IP validation now normalizes addresses with inet_pton and tests an explicit CIDR denylist instead of PHP’s range filter flags — closing bypasses via IPv4-mapped IPv6 (e.g.::ffff:169.254.169.254), NAT64 (64:ff9b::/96) and CGNAT (100.64/10). The host is resolved once, every returned address is validated, and the validated IP is pinned into the request (CURLOPT_RESOLVE) so DNS rebinding between check and connect can’t reach an internal address. - Fix:
/db/importnow deletes asource_url-pulled temp dump on every exit path (no orphaned gz left inuploads/instamigrate/), and guards the magic-byte read against an unreadable file (clean 500 instead of a fatal). - Fix:
/db/exportskip_regenerableno longer over-matches — it uses a Wordfence allowlist (never skipswfconfig/wfls_*config + 2FA secrets) and skips only Elementor telemetry (e_events), never form submissions.ignore_tablesnow also accepts suffix-only table names. - Improvement:
/files/fetch-probecaps the body it buffers (origins that ignore Range can return a full body), avoiding a memory spike on large artifacts.
1.7.6
- New: Server-to-server pull.
/db/importand/files/extractaccept asource_urlparam and fetch the artifact directly from the source site (SSRF-guarded: http/https only, blocks loopback/private/reserved/link-local targets), so a destination can pull large archives straight from the source instead of routing every byte through a migration client. New/files/fetch-probeendpoint reports whether the destination can reach a given source URL, so callers can fall back to chunked upload when it can’t. - New:
/db/exportcan skip the data of regenerable/log tables (opt-in viaskip_regenerable=1and/or an explicitignore_tablesCSV) to shrink huge dumps — the DROP+CREATE schema is still emitted so dependent plugins don’t fatal, only the rows are skipped. Default behavior is unchanged. - New:
/files/extractacceptsverify=1to count any archive entries missing after extraction and reportcomplete:false+missing— surfacing silent ZipArchive/Phar truncation on very large file trees instead of a false success.
1.7.5
- Performance:
/db/exportnow compresses the SQL dump inline in a single pass at gzip level 1, instead of writing the full uncompressed dump to disk and then re-reading and re-compressing it at level 9. This removes a second full pass over the dump and the most CPU-intensive step of the export — on large databases the level-9 re-compression was the main cause of edge-timeout (HTTP 524) failures. The emitted SQL is byte-for-byte unchanged and the import side auto-detects gzip by magic bytes, so the compression level is fully transparent.
1.7.4
- New:
/insta-migrate/v1/plugins/install-activate-pluginendpoint. Installs a plugin from a URL (or.zip) via WordPress’sPlugin_Upgraderand activates it. Powers App-driven post-migration plugin actions, so an integrating App can request specific plugins be installed on the destination as part of the migration. Authenticated viaX-Insta-Key. - New:
/insta-migrate/v1/plugins/delete-pluginendpoint. Accepts aplugin_filesarray, deactivates each plugin and removes it viadelete_plugins(). InstaMigrate is filtered out of the request (bothinstamigrate/insta-migrate.phpandinsta-migrate/insta-migrate.phpdirectory variants) so the endpoint cannot disable its own access. Authenticated viaX-Insta-Key.
1.7.3
- New: Bridge auto-discover. Plugin polls the tracker every 5 minutes (and on every visit to the InstaMigrate admin page) to see if a bridge migration has been assigned to this site, and starts itself automatically. Removes the need for site owners to copy/paste tracker URL, migration ID, and shared secret. Discover URL is pinned (HTTPS, cert verification on); 60-second outbound rate limit with a manual “Check now” button to bypass.
1.7.2
- New:
push_filebridge action. Source plugin can now multipart-POST a local file directly to a remote URL — typically the destination plugin’s/files/uploador/db/importendpoint. Enables source-bridge inversion for sites where the source is firewalled (Imunify360 IP bans, custom WAFs) but the destination is reachable normally. The source plugin polls outbound, dest receives via its existing endpoints, no tracker upload sink required.
1.7.1
- Fix: Bridge self-trigger curl timeout raised from 500ms to 5s. On slow PHP-FPM hosts the original 500ms budget could abort before TCP connect + TLS handshake completed, breaking the bridge tick chain. WP-Cron is the safety net but can’t fire on dead/CF-blocked sites.
1.7.0
- New: Bridge Mode. Plugin can run as an outbound-polling agent for sites where inbound REST is filtered (Cloudflare JS challenge, WAF blocking
/wp-json/). The plugin POSTs to the tracker for the next command, dispatches it via the existing internal handlers, posts the result back, and self-triggers the next tick usingCURLOPT_RESOLVEto the origin IP for CF bypass. WP-Cronbridge_safety_tickresumes the chain on the next admin pageview if a tick dies mid-flight. - Admin UI: “Bridge Mode” card in Settings InstaMigrate with Start / Stop / status fields.
1.6.0
- Performance: DB export now uses multi-row INSERT statements (10-50x faster imports)
- Performance: DB import streams gzip directly (no decompress-to-disk step)
- Performance: DB import uses transaction batching for legacy single-row dumps
- Performance: Added MySQL tuning flags (autocommit=0, unique_checks=0) during import/export
- Backward compatible: v1 single-row dumps still import correctly with auto-batched transactions
1.5.0
- Internal version alignment (header and constant now both report 1.5.0)
- No functional changes from 1.4.0
1.4.0
- Security: Permission callbacks now require
manage_network(super admin) on multisite, instead ofmanage_optionswhich subsite admins may have - Security: File concat (
/files/concat) output restricted to plugin temp directory only - Security: File search-replace (
/files/search-replace) restricted to uploads directory only - Security: File upload (
/files/upload) destination restricted to uploads directory only - Security: File search-replace now uses WP_Filesystem for reads and writes instead of direct PHP file functions
- Change: Temp directory moved from
wp-content/insta-migrate-tmp/towp-content/uploads/insta-migrate-tmp/per wp.org guidelines
1.3.0
- Removed all exec()/shell_exec() calls per wp.org plugin guidelines
- Removed
/wp-cliREST endpoint (required exec) - Archive creation and extraction now use PharData/ZipArchive exclusively
- Binary detection (has_wp_cli) now uses hardcoded path checks only
1.2.0
- New:
/db/fix-prefixendpoint — fixes usermeta meta_key and options option_name values after a migration where the table prefix changed. Prevents users losing roles/capabilities when the sed prefix-rewrite missed single-quoted values.
1.1.0
- New:
/files/concatendpoint — concatenate chunked file uploads on the server (for large archives exceeding PHP upload limit) - New:
/files/search-replaceendpoint — search-replace strings within static files on disk (CSS/JS/JSON), used for Elementor CSS domain replacement after DB search-replace - Fix: Numeric columns (INT, BIGINT, DECIMAL, etc.) now exported as plain numbers, not hex-encoded. Prevents MySQL from misinterpreting hex literals as integers.
1.0.0
- Initial release
- Database export/import with hex encoding
- File upload, download, archive, extract
- Serialization-aware search-replace
- Dual authentication (API key + Application Passwords)
- WP-CLI detection in status endpoint
- Admin UI with API key management