Production database access from the staging
-
I’m really surprised that Migro is asking for the production database credentials on the staging site.
Our site is hosted on a server that doesn’t allow remote access to the database, which is completely standard and secure. On top of that, our staging site is hosted on a local machine connected to the internet.
We can’t use Migro, but more importantly, given these security practices, we strongly advise against using Migro on your websites.
Why did you choose to connect directly to the production database to handle the migration instead of using the WordPress REST APIs?
Thank you for your clarification.
-
Hi Patrice,
Thanks for writing, and for being direct about it. Your question is fair and deserves an answer. I also have a way to make your setup work with Migro today, which I will come to below.
Why the database, and not the WordPress REST API
The short version: WordPress core’s REST API cannot carry the data a content migration actually consists of.
Core REST exposes posts, terms, media and a small allow-listed slice of metadata. A content migration needs more than that, for example:
- Custom fields. WordPress only exposes post meta that a developer explicitly registered with
show_in_rest, and it refuses protected keys (anything beginning with an underscore) outright. Most theme and plugin custom fields are neither registered nor public. Over core REST they cannot be read or written at all. Migro copies a post’s custom fields as they are stored. - Update instead of duplicate. Migro marks each migrated post with a pairing key so that the second migration updates the post the first one created, rather than making a copy. That marker is a protected meta key, which core REST will not write. An update-in-place model cannot be built on it.
- Reading the destination before changing it. Migro takes a backup of the rows it is about to overwrite, and skips items that are already identical. Both need the stored values, not the filtered output an API chooses to render.
The paid version goes further into the same places, page builder layouts in post meta and WooCommerce’s own custom tables, but the constraint is not a paid-tier one. It applies to the free plugin as well.
So the direct connection was not chosen over REST for convenience. It was chosen because core REST does not have the surface area. A REST-only build of Migro would be a different and much smaller product.
On security
I want to separate two things that are being merged in your message.
For most Migro users, no remote database access exists in the first place. Staging and production sit on the same server, so the connection is local, over
localhostor a Unix socket. Nothing crosses the network, no port is opened, and no firewall rule is relaxed. Migro never asks a host to expose MySQL to the internet, and it has no mechanism to do so.Your setup is the harder one, and deliberately so: two separate machines, with production correctly refusing outside connections. That is a compatibility limit on our side, not an unsafe practice on yours, and I would not want anyone loosening a hardened host to accommodate a plugin.
Either way, credentials never leave your site. They are not sent to us or to any third party, and they play no part in telemetry or licensing. At rest they are encrypted with libsodium’s
crypto_secretboxunder an envelope scheme: the data key is itself wrapped with a key derived by HKDF-SHA256 from yourwp-config.phpsalts, so the database holds only an inert blob. A stolen dump, a SQL injection, or a leaked backup yields nothing without the wp-config file. If the salts are rotated, decryption fails closed with a visible admin notice instead of degrading quietly, and if Sodium is unavailable Migro rejects the save rather than storing anything in a weaker form.There is a configuration that works today
Your production host is right to refuse remote MySQL, and you should not change that. You do not have to.
If you have SSH access to production, you can forward the database port over that SSH session from the machine running staging:
ssh -L 3307:127.0.0.1:3306 user@your-production-hostThen in Migro’s connection settings on staging, set the Database Host to
127.0.0.1:3307. MySQL stays bound to localhost on production and closed to the internet. The traffic travels inside the SSH session you are already trusted to open, and no port is exposed to anyone.The second piece is that your staging site is on a local machine, so production can never reach it. Migro detects this and turns on Passive Mode: the pairing stays two-way, but every migration is started from the staging side. Media transfers outbound over HTTPS to production, which needs nothing special.
I will add this to the documentation, since you are not the only person in this position and it should not have taken a support ticket to surface it.
What is coming
A connection mode that needs only a site URL and a WordPress application password is in active development. Because Migro runs on both sites, the two installs talk to each other over dedicated, typed
migro/v1endpoints rather than core REST, which is what closes the capability gap described above. Direct database access then becomes an advanced option for the narrow set of cases that genuinely require it, rather than a prerequisite.One honest limitation: WooCommerce’s custom tables are the last piece still needing direct access, and will remain so in the first release of that mode.
If you would like, I can email you when it is ready for testing. Your setup, a local staging machine against a locked-down production host, is exactly the case it is built for, and I would value your feedback on it.
Best regards,
AlHi Al,
Thank you very much for your detailed response and explanations. It was very helpful to understand the technical architecture behind Migro and the constraints regarding core WordPress REST APIs.
We tested the SSH tunnel approach with our hosting provider (IONOS) from our local staging server (running on YunoHost). Unfortunately, IONOS’s shared infrastructure drops the MySQL handshake packet during local port forwarding (system error 11), making the SSH tunnel connection impossible in our specific setup.
Since we cannot expose our production database directly, we will wait for the upcoming version utilizing the custom
migro/v1API endpoints.Please feel free to email me or reach out when the new release is ready for testing—we would be more than happy to test it in our environment and provide feedback.
Thanks again for your time and assistance!
Best regards,
Patrice
Hi @patoudss,
Following up on this: Migro 2.7.0 shipped today, and it changes the answer to your original question. Pairing now works through WordPress itself, using an Application Password, and the database fields are optional. If production allows a database connection Migro still uses it, for speed, but on a host like yours that blocks it, HTTP-only pairing works the whole way, no SSH tunnel needed. Connection Settings shows you which mode a pairing is using.
That should let you pair your local staging with production directly. I’d like to know whether it works for you, given the trouble the tunnel gave you.
Thanks for pushing on this. It is a better plugin for it.
Best regards,
Al - Custom fields. WordPress only exposes post meta that a developer explicitly registered with
You must be logged in to reply to this topic.