{"id":220,"date":"2026-06-07T08:42:06","date_gmt":"2026-06-07T08:42:06","guid":{"rendered":"https:\/\/blog.offshorekaka.in\/?p=220"},"modified":"2026-08-27T05:43:31","modified_gmt":"2026-08-27T05:43:31","slug":"create-staging-site-on-offshore-hosting","status":"publish","type":"post","link":"https:\/\/offshorekaka.in\/blog\/create-staging-site-on-offshore-hosting\/","title":{"rendered":"How to Create a Staging Site on Offshore Hosting"},"content":{"rendered":"<div class=\"ok-pro-panel\">\n<p><span class=\"ok-pro-kicker\">Tutorial<\/span><\/p>\n<h2>Somewhere to break things safely<\/h2>\n<p>A staging site is a full copy of your live site on a subdomain, with its own files and its own database, where you can update plugins and try changes without any of it reaching visitors.<\/p>\n<p>Two things make a staging site useful rather than a liability: it must have a <strong>separate database<\/strong>, and it must be <strong>invisible to search engines<\/strong>. Get either wrong and staging becomes the problem instead of the safety net.<\/p>\n<div class=\"ok-pro-grid\">\n<div class=\"ok-pro-card\"><strong>Time<\/strong><span>30 minutes to set up.<\/span><\/div>\n<div class=\"ok-pro-card\"><strong>Must have<\/strong><span>Its own database. Not the live one.<\/span><\/div>\n<div class=\"ok-pro-card\"><strong>Must not<\/strong><span>Be indexable, or reachable by the public.<\/span><\/div>\n<\/div>\n<\/div>\n<div class=\"ok-toc\"><strong>On this page<\/strong><a href=\"#subdomain\">The subdomain<\/a><a href=\"#copy\">Copying the site<\/a><a href=\"#db\">Its own database<\/a><a href=\"#urls\">Fixing the URLs<\/a><a href=\"#hide\">Keeping it out of Google<\/a><a href=\"#push\">Pushing changes live<\/a><a href=\"#faq\">FAQ<\/a><\/div>\n<h2 id=\"subdomain\">The subdomain<\/h2>\n<p>Create <code>staging.yourdomain.com<\/code> in your panel, or add a vhost with its own document root on a plain <a href=\"https:\/\/offshorekaka.in\/offshore-vps-server\/\">VPS<\/a>. Give it its own directory, separate from the live site.<\/p>\n<p>Issue a certificate for it too. A staging site over plain HTTP behaves differently from the live one, and browsers block mixed content in ways that send you chasing bugs that do not exist on production.<\/p>\n<h2 id=\"copy\">Copying the site<\/h2>\n<pre><code>mkdir -p \/var\/www\/staging.yourdomain.com\ncp -a \/var\/www\/yourdomain.com\/. \/var\/www\/staging.yourdomain.com\/\nchown -R www-data:www-data \/var\/www\/staging.yourdomain.com<\/code><\/pre>\n<p>The <code>-a<\/code> flag preserves permissions and ownership, and the dot after the source path copies hidden files such as <code>.htaccess<\/code>. Miss the dot and you copy the visible files only, which on Apache means your permalinks stop working and the cause is not obvious.<\/p>\n<h2 id=\"db\">Its own database<\/h2>\n<p>This is the part that matters most. If staging points at the live database, then deleting a test post on staging deletes it from the live site, and a plugin update that mangles data mangles it for real visitors.<\/p>\n<pre><code>mysqldump --single-transaction --quick -u root -p livedb &gt; \/tmp\/live.sql\nmysql -u root -p -e \"CREATE DATABASE stagingdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\"\nmysql -u root -p stagingdb &lt; \/tmp\/live.sql\nrm \/tmp\/live.sql<\/code><\/pre>\n<figure class=\"wp-block-image size-large ok-inline-visual\"><img decoding=\"async\" src=\"https:\/\/offshorekaka.in\/blog\/wp-content\/uploads\/2026\/08\/ok-db-export-import.webp\" alt=\"Terminal exporting the live database and importing it into a separate staging database\" loading=\"lazy\" width=\"2296\" height=\"396\"><figcaption>Export from live, import into a database that only staging uses.<\/figcaption><\/figure>\n<p>Then point the staging copy at it, in <code>\/var\/www\/staging.yourdomain.com\/wp-config.php<\/code>:<\/p>\n<pre><code>define( 'DB_NAME', 'stagingdb' );\ndefine( 'DB_USER', 'staginguser' );\ndefine( 'DB_PASSWORD', 'a-different-password' );<\/code><\/pre>\n<p>Before you go further, confirm it. Change the site title on staging and reload the live site. If the live title changed too, staging is still on the live database and you need to stop and fix it now.<\/p>\n<h2 id=\"urls\">Fixing the URLs<\/h2>\n<p>The copied database is full of absolute URLs pointing at the live domain, so staging will pull images from live and redirect you to live when you log in.<\/p>\n<pre><code>wp search-replace 'https:\/\/yourdomain.com' 'https:\/\/staging.yourdomain.com' \n  --path=\/var\/www\/staging.yourdomain.com --dry-run<\/code><\/pre>\n<figure class=\"wp-block-image size-large ok-inline-visual\"><img decoding=\"async\" src=\"https:\/\/offshorekaka.in\/blog\/wp-content\/uploads\/2026\/08\/ok-staging-search-replace.webp\" alt=\"WP-CLI search-replace dry run listing tables and the number of replacements per table\" loading=\"lazy\" width=\"2296\" height=\"664\"><figcaption>Always dry run first. It shows exactly which tables would change and how much.<\/figcaption><\/figure>\n<p>Use WP-CLI rather than editing the SQL file. WordPress stores serialised PHP in the options and meta tables, and a plain text replace leaves the recorded string lengths wrong, which silently breaks widgets and theme settings.<\/p>\n<p>Drop <code>--dry-run<\/code> once the counts look sensible.<\/p>\n<h2 id=\"hide\">Keeping it out of Google<\/h2>\n<p>A staging copy that gets indexed is a duplicate of your entire site competing with the original. Use two layers, because either one alone can fail.<\/p>\n<p><strong>A password on the whole subdomain.<\/strong> This is the reliable one, since a crawler that cannot load the page cannot index it:<\/p>\n<pre><code>htpasswd -c \/etc\/nginx\/.htpasswd staging<\/code><\/pre>\n<p>Then in the staging server block:<\/p>\n<pre><code>auth_basic \"Staging\";\nauth_basic_user_file \/etc\/nginx\/.htpasswd;\nadd_header X-Robots-Tag \"noindex, nofollow\" always;<\/code><\/pre>\n<p><strong>And<\/strong> tick Discourage search engines in Settings, Reading on the staging copy. That adds a noindex tag as a second layer.<\/p>\n<p>The reason for both is that the WordPress setting is a request, not an enforcement, and it is also the setting that occasionally travels back to production when someone copies staging over live. That mistake removes a live site from Google, which is why the password matters more.<\/p>\n<figure class=\"wp-block-image size-large ok-inline-visual\"><img decoding=\"async\" src=\"https:\/\/offshorekaka.in\/blog\/wp-content\/uploads\/2026\/08\/ok-gui-wp-reading.webp\" alt=\"WordPress Settings Reading page with Discourage search engines\" loading=\"lazy\" width=\"2508\" height=\"1356\"><figcaption>The second layer. The password on the subdomain is the one that actually holds.<\/figcaption><\/figure>\n<h2 id=\"push\">Pushing changes live<\/h2>\n<p>Do not copy the staging database over the live one. Live has comments, orders and posts created since you took the copy, and overwriting it loses all of them.<\/p>\n<p>Push only what you changed:<\/p>\n<ul>\n<li><strong>Theme and plugin file changes:<\/strong> copy the files across, or deploy from git.<\/li>\n<li><strong>Plugin updates:<\/strong> having tested on staging, run the same update on live through the dashboard.<\/li>\n<li><strong>Settings changes:<\/strong> make them again by hand on live. It sounds crude and it is the safest way.<\/li>\n<\/ul>\n<p>Take a backup of live before any of it. Our <a href=\"https:\/\/offshorekaka.in\/blog\/backup-strategy-for-offshore-servers\/\">backup guide<\/a> covers making one you can actually restore.<\/p>\n<h2 id=\"test\">How to test<\/h2>\n<ol>\n<li>Staging loads over HTTPS and asks for the password.<\/li>\n<li>Change the staging site title and confirm live is unaffected.<\/li>\n<li>Images on staging load from the staging domain, not from live.<\/li>\n<li>Log in to staging and confirm you are not redirected to live.<\/li>\n<li><code>curl -sI https:\/\/staging.yourdomain.com<\/code> returns 401 without credentials.<\/li>\n<li>Check <code>site:staging.yourdomain.com<\/code> in Google after a week and expect nothing.<\/li>\n<\/ol>\n<h2 id=\"trouble\">When something breaks<\/h2>\n<table>\n<thead>\n<tr>\n<th>What you see<\/th>\n<th>Why<\/th>\n<th>Fix<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Editing staging changes the live site<\/td>\n<td>Both are on the same database<\/td>\n<td>Stop, create a separate database, update wp-config.php<\/td>\n<\/tr>\n<tr>\n<td>Logging in to staging redirects to live<\/td>\n<td>siteurl and home still point at live<\/td>\n<td>Run the search-replace<\/td>\n<\/tr>\n<tr>\n<td>Images load from the live domain<\/td>\n<td>Same cause, URLs in the database<\/td>\n<td>Same fix<\/td>\n<\/tr>\n<tr>\n<td>Staging showing up in Google<\/td>\n<td>No password and no noindex<\/td>\n<td>Add basic auth, then request removal in Search Console<\/td>\n<\/tr>\n<tr>\n<td>Permalinks broken on staging only<\/td>\n<td><code>.htaccess<\/code> not copied<\/td>\n<td>Copy with <code>cp -a source\/.<\/code> including the dot<\/td>\n<\/tr>\n<tr>\n<td>Live site deindexed after a push<\/td>\n<td>Discourage search engines copied from staging<\/td>\n<td>Untick it on live and request reindexing<\/td>\n<\/tr>\n<tr>\n<td>Widgets empty on staging<\/td>\n<td>URLs replaced with sed instead of WP-CLI<\/td>\n<td>Reimport the database and use search-replace<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2 id=\"checklist\">Checklist<\/h2>\n<ul>\n<li>Staging on its own subdomain with its own certificate.<\/li>\n<li>Files copied with <code>-a<\/code> and the trailing dot.<\/li>\n<li>Separate database, and it has been proven separate.<\/li>\n<li>URLs rewritten with WP-CLI search-replace.<\/li>\n<li>Basic auth password on the whole subdomain.<\/li>\n<li>Discourage search engines ticked as a second layer.<\/li>\n<li>Live backed up before anything is pushed.<\/li>\n<li>Only changed files pushed, never the whole database.<\/li>\n<\/ul>\n<div class=\"ok-cta-box\">\n<p><strong>Want staging without building it?<\/strong><\/p>\n<p>OffshoreKaka hosting includes cPanel with subdomains, free certificates and one click WordPress, so a staging copy is a few minutes of clicking.<\/p>\n<p><a class=\"btn ok-cta-link\" href=\"https:\/\/offshorekaka.in\/offshore-web-hosting\/\">See the hosting plans<\/a><\/p>\n<\/div>\n<h2 id=\"faq\">FAQ<\/h2>\n<h3>Does having a staging site hurt my ranking?<\/h3>\n<p>Only if Google can reach it. An indexed staging copy is duplicate content competing with your real pages. Behind a password it is invisible and harmless, which is why the password matters more than the noindex tag.<\/p>\n<h3>Can a plugin do this instead?<\/h3>\n<p>Yes. WP Staging and similar plugins handle the copy and the URL rewriting for you, and for a small site they are easier. They can struggle on large sites because everything runs through PHP and hits time limits. The manual route also teaches you what actually moved, which helps when something goes wrong.<\/p>\n<h3>How often should I refresh staging from live?<\/h3>\n<p>Before any significant piece of work. A staging copy from three months ago has drifted so far from live that testing on it proves very little. Refresh, test, push, and treat staging as disposable.<\/p>\n<h3>Can I use a subdirectory instead of a subdomain?<\/h3>\n<p>You can, but a subdomain is cleaner. A subdirectory shares the parent&#8217;s rewrite rules and certificate, which causes odd interactions, and it is harder to password protect without affecting the live site. If you are moving hosts anyway, our <a href=\"https:\/\/offshorekaka.in\/blog\/how-to-migrate-wordpress-to-offshore-hosting\/\">migration guide<\/a> covers the same copy steps.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A copy of your site with its own database, invisible to Google. The two things that make it useful rather than a liability.<\/p>\n","protected":false},"author":1,"featured_media":1019,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-220","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials"],"_links":{"self":[{"href":"https:\/\/offshorekaka.in\/blog\/wp-json\/wp\/v2\/posts\/220","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/offshorekaka.in\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/offshorekaka.in\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/offshorekaka.in\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/offshorekaka.in\/blog\/wp-json\/wp\/v2\/comments?post=220"}],"version-history":[{"count":5,"href":"https:\/\/offshorekaka.in\/blog\/wp-json\/wp\/v2\/posts\/220\/revisions"}],"predecessor-version":[{"id":998,"href":"https:\/\/offshorekaka.in\/blog\/wp-json\/wp\/v2\/posts\/220\/revisions\/998"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/offshorekaka.in\/blog\/wp-json\/wp\/v2\/media\/1019"}],"wp:attachment":[{"href":"https:\/\/offshorekaka.in\/blog\/wp-json\/wp\/v2\/media?parent=220"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/offshorekaka.in\/blog\/wp-json\/wp\/v2\/categories?post=220"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/offshorekaka.in\/blog\/wp-json\/wp\/v2\/tags?post=220"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}