Discuss project

Staging vs Production: Keep Disallow: / Off Your Live Site

Staging sites get blocked with robots.txt, and Disallow: / ends up in production. How to password-protect staging and what to check in the first five minutes after a release.

Vladislav KrivorutskoSeptember 14, 20269 min read
Contents

TL;DR - key points

  • Block a staging site from indexing with a server-level password (basic auth), not a Disallow: / line in robots.txt: the password never enters the repository and never ships to production with the code
  • Disallow: / does not remove URLs from the index: Google can show a blocked URL without a description if links point to it
  • An indexing block hides in five places: the robots.txt file, the robots meta tag, the X-Robots-Tag header, a WordPress setting in the database and CDN rules
  • After every release the check takes five minutes: open /robots.txt, search the homepage source for noindex, look at the response headers with curl -I

Short answer

Staging is blocked with a password in the web server config (basic auth in nginx or Apache), not with a Disallow: / line in robots.txt. The password lives on the test server and never enters the repository, so a deploy cannot carry it to the live site. The robots.txt file and the noindex meta tag sit in project code and ship to production with everything else.

After every release the live domain needs three checks: the contents of robots.txt, a search for noindex in the homepage source, and the server response headers via curl. The whole thing takes 5 minutes.

Why Disallow: / does not protect a staging site

Disallow: / on staging has two flaws. First, it does not block indexing. Google Search Central states plainly that robots.txt controls crawling and that a blocked URL can still be indexed if links point to it. Staging subdomains get discovered through a link in an email, a task tracker with a public board, a screenshot, a Referer header. Then dev.site.com shows up in results with a note that no description is available.

The second flaw costs more. The file sits in the repository and gets copied to production on release. The site keeps working: pages load, forms submit, nobody notices anything in the browser. People find out one or two weeks later from the impressions graph in Search Console. In my website migration SEO checklist I called this the most common reason a migration fails, and years of audits have not changed that.

Way to block stagingBlocks indexingCan ship to production on deployHow to check
Disallow: / in robots.txtNo, blocks crawling onlyYes, the file is in the repoOpen /robots.txt
noindex meta tag in the templateYesYes, unless tied to an environment variableSearch the page source for noindex
X-Robots-Tag: noindex headerYesYes, if the server config is copied wholesalecurl -I https://site.com/
Basic auth on the web serverYes, the crawler gets 401No, the staging server config is separateOpen the site in a private window
IP allowlistYes, the crawler gets 403NoOpen the site over mobile data

How to lock staging so the block cannot travel

I use one setup: basic auth at the nginx level, only in the staging environment config. A Next.js or WordPress project then has no indexing-related line that differs between staging and production.

server {
    server_name dev.example.com;
    auth_basic "Staging";
    auth_basic_user_file /etc/nginx/.htpasswd-staging;
    # ...
}

If the site runs on Cloudflare, Cloudflare Access on the staging subdomain does the same job: access by email or one-time code, again outside the repository. On Vercel, preview deployments send X-Robots-Tag: noindex by default, but the header is not added on your production custom domain. The risk there is of a different kind: the preview you sent the client as an "almost finished site" is the one that is blocked.

When a meta tag in code is unavoidable (say, one Next.js template builds every environment), noindex should switch on only with an explicit variable such as SITE_ENV=staging. A missing variable must mean "index", not the other way round. With inverted logic, the first server where somebody forgot to set the variable ends up blocked.

Where the block hides after a release

When traffic drops after a release, I check 5 places in this order: from the most frequent to the least visible, from robots.txt to Cloudflare rules.

StagingProduction after deployrobots.txt: Disallow: /noindex meta tag in templateX-Robots-Tag in server configWordPress: blog_public = 0basic auth in staging nginxcode, config,database copyall four blockstravelled with the codeand the databaseno password in productionRed lives in the project or the database and travels. Green stays on the staging server.
  1. The robots.txt file. I open https://site.com/robots.txt in a browser and look for Disallow: / with nothing after the slash. If the file is generated (in Next.js that is app/robots.ts), I also read the generator: environment conditions with swapped branches turn up there.
  2. The robots meta tag. I search for noindex in the source of the homepage and one inner page, using "View page source" rather than DevTools. Different templates can behave differently.
  3. The X-Robots-Tag header. curl -I https://site.com/ prints the response headers. This block is invisible in the page source and in the browser, which is why people look for it last, even though checking takes a second.
  4. The WordPress setting. The "Discourage search engines from indexing this site" checkbox is stored in the database as the blog_public option. Copy the database from staging to production and it comes along, even if the project files were uploaded clean. Since version 5.7 WordPress outputs a noindex meta tag in that case, so check 2 catches it too.
  5. CDN and hosting rules. Cloudflare Transform Rules, headers set in a hosting panel, security plugins. If curl -I shows X-Robots-Tag and your nginx config does not contain it, look here.

One case people forget: robots.txt is open, but it blocks the path to CSS and JS. The site is indexed, yet Google renders it without styles. What to block in that file and what to leave open is covered in my article on sitemap.xml and robots.txt best practices.

The five-minute post-release check

I run these 5 steps by hand after any release that touched templates, the nginx config or the database.

  1. https://site.com/robots.txt in a browser: no Disallow: /, a Sitemap: line with a working URL.
  2. Source of the homepage and one product page or article: no noindex, and canonical points to the live domain, not to dev..
  3. curl -I https://site.com/: status 200, no X-Robots-Tag: noindex.
  4. URL Inspection in Search Console for the homepage with "Test live URL": status "URL is available to Google".
  5. Two or three days later, the Page indexing report: neither "Blocked by robots.txt" nor "Excluded by 'noindex' tag" is growing.

The canonical step is not there for completeness. A Next.js or WordPress template built on staging sometimes takes the domain from an environment variable, and production ends up with a canonical pointing at the test address. Google then sees two versions of the same text and picks the canonical itself; how that looks in the reports is described in my guide to duplicate content.

If the block already reached production

What to do depends on which block fired: robots.txt or noindex. So diagnose with the 5 steps above first, then fix it in Search Console.

  • Disallow: / shipped. Fix the file, open the robots.txt report in Search Console and request a recrawl. Google caches robots.txt for up to 24 hours, and without the request the crawler may keep following the old version for another day.
  • noindex shipped. Remove it and send the homepage and main sections to URL Inspection. The rest follows during normal crawling.
  • In both cases resubmit sitemap.xml: it gives the crawler a list of URLs to revisit.

Recovery time cannot be predicted; it depends on how often the specific site gets crawled. On a site Googlebot visits daily, the first pages return sooner than on a 30-page site the crawler visits once a week. If after two or three weeks pages still sit in "Crawled – currently not indexed", the block is no longer the cause, and the next step is my breakdown of why Google is not indexing your pages.

When basic auth is the wrong choice

A staging password gets in the way in 3 situations, and there I pick Cloudflare Access, an IP allowlist or a separate test URL instead.

  • A client or content editor reviews staging on a phone and gets stuck at the login prompt. Cloudflare Access with email login works better here.
  • An external service must reach staging without a password: a payment gateway sends webhooks, uptime monitoring checks availability. Then use an IP allowlist with an exception for the service's addresses rather than removing protection.
  • You need to see how Google renders a page before release. URL Inspection cannot get past a password. That test runs on a separate open URL with noindex that is deleted right after the test, and it is the only place where a meta tag on a test environment makes sense.

Frequently asked questions

How do I block a staging site from Google indexing?
The most reliable way is a password at the web server level: basic auth in nginx or Apache, or an IP allowlist. Googlebot does not log in, so it sees no pages and cannot index anything. Robots.txt is the wrong tool: it blocks crawling, not indexing, and the file itself easily travels to the live site together with the code.
My staging subdomain got indexed by Google. What now?
First put the subdomain behind a password so no new URLs reach the results. Then verify the subdomain in Search Console and file a request in the Removals tool: it hides the URLs from results for about six months. In that window Google recrawls the password-protected pages, gets a 401 response and drops them from the index for good.
How long does it take a site to come back after removing Disallow: /?
There is no fixed timeframe; it depends on how often Googlebot crawls the site. Google caches robots.txt for up to 24 hours, so the crawler first has to refetch the file and only then recrawl the pages. You can speed it up by requesting a recrawl of robots.txt in Search Console, resubmitting sitemap.xml and running URL Inspection on the most important pages.
Why does Search Console say "Indexed, though blocked by robots.txt"?
Because robots.txt stops Google from downloading a page but does not stop it from indexing the URL. If links point to that URL, Google can add it to the index and show it without a description. To get the page out of results, open it to crawling and serve noindex, put it behind a password, or delete it with a 404 or 410 response.
Do I need noindex on staging if it is already password-protected?
No, and I deliberately leave it out. The password already keeps the crawler away, while a noindex meta tag in the template is exactly the thing that ships to production on release. If you cannot avoid the meta tag, switch it on with an environment variable that simply does not exist on the production server.

Conclusion

Staging protection belongs where a deploy cannot reach it: in the web server config of the test environment, not in project files. Then moving code to production physically cannot carry the block along, and the post-release check becomes a five-minute formality. If a site has already gone live with indexing blocked and impressions in Search Console dropped, the fix is quick, but only once you know exactly where the block comes from. I run that kind of diagnosis as part of [SEO services](/en/seo), and I set up password-protected staging from day one when I build a site under [web development](/en/web-development).

About the author

Vladislav Krivorutsko — founder of ADLAB
Vladislav Krivorutsko

Founder of ADLAB OÜ · SEO and Google Ads

Over 20 years in search traffic and monetization, and on the Estonian market since 2017. I work solo: I run the audit, build the strategy and deliver the project myself — no subcontractors, no templates. I only write about what I have tested on my own and client sites.

  • 20+ years in search traffic
  • 50+ end-to-end projects
  • Own sites in competitive niches
  • SEO for ru/et/en in one market
More about me

Read next